From 73360bb0d2b845fcf350d258007ff74e3667e5db Mon Sep 17 00:00:00 2001 From: Brian Stafford Date: Fri, 17 Feb 2023 18:52:26 -0600 Subject: [PATCH 1/4] improve active order interactions Allow sweeping of fully-executed orders from active orders list on markets view. Shows a broom icon in the button menu. Show (a copy of) the button menu on hover, allowing the user to perform actions without expanding the active order entry. --- client/webserver/locales/en-us.go | 2 + client/webserver/site/src/css/market.scss | 29 +++++ .../webserver/site/src/css/market_dark.scss | 5 + client/webserver/site/src/html/markets.tmpl | 16 ++- client/webserver/site/src/js/markets.ts | 123 ++++++++++++++++-- client/webserver/site/src/js/orderutil.ts | 4 + dex/testing/dcr/harness.sh | 3 +- 7 files changed, 162 insertions(+), 20 deletions(-) diff --git a/client/webserver/locales/en-us.go b/client/webserver/locales/en-us.go index 62e98cc8c0..2a178aadbb 100644 --- a/client/webserver/locales/en-us.go +++ b/client/webserver/locales/en-us.go @@ -94,6 +94,8 @@ var EnUS = map[string]string{ "fee balance": "fee balance", "Sell Orders": "Sell Orders", "Your Orders": "Your Orders", + "sweep_orders": "Hide fully executed orders", + "sweep_order": "Hide this fully executed order", "Recent Matches": "Recent Matches", "Type": "Type", "Side": "Side", diff --git a/client/webserver/site/src/css/market.scss b/client/webserver/site/src/css/market.scss index a80192adac..e9a67b3bd3 100644 --- a/client/webserver/site/src/css/market.scss +++ b/client/webserver/site/src/css/market.scss @@ -121,6 +121,15 @@ div[data-handler=markets] { } } + #sweepOrders { + position: absolute; + right: 15px; + top: 50%; + transform: translateY(-50%); + line-height: 1; + padding: 6px; + } + #unreadyOrdersMsg { color: red; } @@ -895,3 +904,23 @@ div[data-handler=markets] { max-height: none; } } + +.user-order-floaty-menu { + position: absolute; + display: flex; + align-items: center; + z-index: 5; + border: 1px solid; + border-color: $light_border_color $light_input_border $light_input_border $light_input_border; + padding: 0 10px; + background-color: $light_body_bg; + cursor: pointer; + overflow: hidden; + + & > span, + & > a { + &:hover { + background-color: #7775; + } + } +} diff --git a/client/webserver/site/src/css/market_dark.scss b/client/webserver/site/src/css/market_dark.scss index bd78086ae0..f5d3bc4f45 100644 --- a/client/webserver/site/src/css/market_dark.scss +++ b/client/webserver/site/src/css/market_dark.scss @@ -184,6 +184,11 @@ body.dark { border-color: $dark_border_color; background-color: $dark_body_bg; } + + .user-order-floaty-menu { + border-color: $dark_border_color $dark_input_border $dark_input_border $dark_input_border; + background-color: $dark_body_bg; + } } @include media-breakpoint-up(lg) { diff --git a/client/webserver/site/src/html/markets.tmpl b/client/webserver/site/src/html/markets.tmpl index 9ef28a181d..c8f0b765f4 100644 --- a/client/webserver/site/src/html/markets.tmpl +++ b/client/webserver/site/src/html/markets.tmpl @@ -407,12 +407,15 @@ {{- /* USER ORDERS */ -}} -
[[[Your Orders]]]
+
+ [[[Your Orders]]] +
+
[[[unready_wallets_msg]]]
no active orders
-
+
@@ -428,10 +431,11 @@
-
- - - +
+ + + +
[[[Type]]] diff --git a/client/webserver/site/src/js/markets.ts b/client/webserver/site/src/js/markets.ts index 99b95d1bc7..554243ad42 100644 --- a/client/webserver/site/src/js/markets.ts +++ b/client/webserver/site/src/js/markets.ts @@ -445,6 +445,15 @@ export default class MarketsPage extends BasePage { this.activeMarkerRate = null this.setDepthMarkers() }) + Doc.bind(page.sweepOrders, 'click', () => { + for (const [orderID, mord] of Object.entries(this.metaOrders)) { + if (sweepable(mord.ord)) { + mord.div.remove() + delete this.metaOrders[orderID] + } + } + Doc.hide(page.sweepOrders) + }) const stats0 = page.marketStatsV1 const stats1 = stats0.cloneNode(true) as PageElement @@ -1496,9 +1505,11 @@ export default class MarketsPage extends BasePage { ord: ord } + const orderID = ord.id + // No need to track in-flight orders here. We've already added it to // display. - if (ord.id) metaOrders[ord.id] = mord + if (orderID) metaOrders[orderID] = mord if (!ord.readyToTick) { headerEl.classList.add('unready-user-order') @@ -1519,30 +1530,46 @@ export default class MarketsPage extends BasePage { this.setDepthMarkers() }) - if (!ord.id) { + const showCancel = (e: Event) => { + e.stopPropagation() + this.showCancel(div, orderID) + } + + const showAccelerate = (e: Event) => { + e.stopPropagation() + this.showAccelerate(ord) + } + + const sweepOrder = (e: Event) => { + e.stopPropagation() + delete this.metaOrders[orderID] + div.remove() + this.setMasterSweeperVis() + } + + if (!orderID) { Doc.hide(details.accelerateBttn) Doc.hide(details.cancelBttn) Doc.hide(details.link) } else { - if (ord.type === OrderUtil.Limit && (ord.tif === OrderUtil.StandingTiF && ord.status < OrderUtil.StatusExecuted)) { + if (OrderUtil.isCancellable(ord)) { Doc.show(details.cancelBttn) - bind(details.cancelBttn, 'click', e => { - e.stopPropagation() - this.showCancel(div, ord.id) - }) + bind(details.cancelBttn, 'click', (e: Event) => { showCancel(e) }) } - bind(details.accelerateBttn, 'click', e => { - e.stopPropagation() - this.showAccelerate(ord) - }) + bind(details.accelerateBttn, 'click', (e: Event) => { showAccelerate(e) }) if (app().canAccelerateOrder(ord)) { Doc.show(details.accelerateBttn) } - details.link.href = `order/${ord.id}` + Doc.bind(details.sweepBttn, 'click', (e: Event) => { sweepOrder(e) }) + if (sweepable(ord)) { + Doc.show(details.sweepBttn) + } + + details.link.href = `order/${orderID}` app().bindInternalNavigation(div) } - + let currentFloater: (PageElement | null) Doc.bind(headerEl, 'click', () => { if (Doc.isDisplayed(detailsDiv)) { Doc.hide(detailsDiv) @@ -1553,6 +1580,55 @@ export default class MarketsPage extends BasePage { Doc.show(detailsDiv) header.expander.classList.remove('ico-arrowdown') header.expander.classList.add('ico-arrowup') + if (currentFloater) currentFloater.remove() + }) + /** + * We'll show the button menu when they hover over the header. To avoid + * pushing the layout around, we'll show the buttons as an absolutely + * positioned copy of the button menu. + */ + Doc.bind(headerEl, 'mouseenter', () => { + // Don't show the copy if the details are already displayed. + if (Doc.isDisplayed(detailsDiv)) return + // Create and position the element based on the position of the header. + const floater = document.createElement('div') + currentFloater = floater + document.body.appendChild(floater) + floater.className = 'user-order-floaty-menu' + const m = Doc.layoutMetrics(headerEl) + floater.style.width = `${m.width}px` + floater.style.top = `${m.bodyTop + m.height}px` + floater.style.left = `${m.bodyLeft}px` + // Get the updated version of the order + const mord = this.metaOrders[orderID] + const ord = mord.ord + + const addButton = (baseBttn: PageElement, cb: ((e: Event) => void)) => { + const icon = baseBttn.cloneNode(true) as PageElement + floater.appendChild(icon) + Doc.show(icon) + Doc.bind(icon, 'click', (e: Event) => { cb(e) }) + } + + if (OrderUtil.isCancellable(ord)) addButton(details.cancelBttn, (e: Event) => { showCancel(e) }) + if (app().canAccelerateOrder(ord)) addButton(details.accelerateBttn, (e: Event) => { showAccelerate(e) }) + if (sweepable(ord)) { + addButton(details.sweepBttn, (e: Event) => { + sweepOrder(e) + floater.remove() + this.setMasterSweeperVis() + }) + } + floater.appendChild(details.link.cloneNode(true)) + + // Set up the hover interactions. + const moved = (e: MouseEvent) => { + if (Doc.mouseInElement(e, floater) || Doc.mouseInElement(e, div)) return + floater.remove() + currentFloater = null + document.removeEventListener('mousemove', moved) + } + document.addEventListener('mousemove', moved) }) app().bindTooltips(div) } @@ -1571,6 +1647,7 @@ export default class MarketsPage extends BasePage { details.age.textContent = Doc.timeSince(ord.submitTime) details.filled.textContent = `${(OrderUtil.filled(ord) / ord.qty * 100).toFixed(1)}%` details.settled.textContent = `${(OrderUtil.settled(ord) / ord.qty * 100).toFixed(1)}%` + if (ord.status >= OrderUtil.StatusExecuted && !OrderUtil.hasActiveMatches(ord)) Doc.show(details.sweepBttn) } /* setMarkers sets the depth chart markers for booked orders. */ @@ -2266,6 +2343,21 @@ export default class MarketsPage extends BasePage { else Doc.hide(mord.details.accelerateBttn) } + /* + * setMasterSweeperVis sets the visibility of the button that deletes away all + * fully-executted active order elements. + */ + setMasterSweeperVis () { + let showMasterSweeper = false + for (const m of Object.values(this.metaOrders)) { + if (sweepable(m.ord)) { + showMasterSweeper = true + break + } + } + Doc.setVis(showMasterSweeper, this.page.sweepOrders) + } + /* * handleOrderNote is the handler for the 'order'-type notification, which are * used to update a user's order's status. @@ -2291,6 +2383,7 @@ export default class MarketsPage extends BasePage { if (app().canAccelerateOrder(order)) Doc.show(mord.details.accelerateBttn) else Doc.hide(mord.details.accelerateBttn) this.updateMetaOrder(mord) + this.setMasterSweeperVis() // Only reset markers if there is a change, since the chart is redrawn. if ((oldStatus === OrderUtil.StatusEpoch && order.status === OrderUtil.StatusBooked) || (oldStatus === OrderUtil.StatusBooked && order.status > OrderUtil.StatusBooked)) this.setDepthMarkers() @@ -3327,3 +3420,7 @@ function hostColor (host: string): string { hosts.sort() return generateHue(hosts.indexOf(host)) } + +function sweepable (ord: Order): boolean { + return ord.status >= OrderUtil.StatusExecuted && !OrderUtil.hasActiveMatches(ord) +} diff --git a/client/webserver/site/src/js/orderutil.ts b/client/webserver/site/src/js/orderutil.ts index 53cf7afe0a..844cfbce95 100644 --- a/client/webserver/site/src/js/orderutil.ts +++ b/client/webserver/site/src/js/orderutil.ts @@ -210,3 +210,7 @@ export function optionElement (opt: OrderOption, order: TradeForm, change: () => function dexAssetSymbol (host: string, assetID: number): string { return app().exchanges[host].assets[assetID].symbol } + +export function isCancellable (ord: Order): boolean { + return ord.type === Limit && ord.tif === StandingTiF && ord.status < StatusExecuted +} diff --git a/dex/testing/dcr/harness.sh b/dex/testing/dcr/harness.sh index 3d7938f98e..15e92188a0 100755 --- a/dex/testing/dcr/harness.sh +++ b/dex/testing/dcr/harness.sh @@ -360,7 +360,6 @@ tmux send-keys -t $SESSION:0 "./alpha getmasterpubkey server_fees${WAIT}" C-m\; # Prepare the wallets ################################################################################ -tmux select-window -t $SESSION:0 for WALLET in alpha beta trading1 trading2; do tmux send-keys -t $SESSION:0 "./${WALLET} getnewaddress${WAIT}" C-m\; wait-for donedcr tmux send-keys -t $SESSION:0 "./${WALLET} getnewaddress${WAIT}" C-m\; wait-for donedcr @@ -408,6 +407,8 @@ if [ -z "$NOMINER" ] ; then tmux send-keys -t $SESSION:9 "watch -n 15 ./mine-alpha 1" C-m fi +tmux select-window -t $SESSION:0 + # Reenable history and attach to the control session. tmux send-keys -t $SESSION:0 "set -o history" C-m tmux select-window -t $SESSION:0 From d094af4fedd7695d023bd584c06ca05892717ec6 Mon Sep 17 00:00:00 2001 From: Brian Stafford Date: Mon, 31 Jul 2023 08:16:47 -0500 Subject: [PATCH 2/4] remove sweeper. show inactive orders. handle unsupported assets --- client/core/core.go | 13 ++ client/core/types.go | 4 + client/db/bolt/db.go | 7 + client/db/types.go | 7 + client/webserver/site/src/css/market.scss | 25 ++-- .../webserver/site/src/html/bodybuilder.tmpl | 4 +- client/webserver/site/src/html/markets.tmpl | 18 ++- client/webserver/site/src/js/markets.ts | 125 ++++++++---------- client/webserver/site/src/js/orders.ts | 11 +- client/webserver/site/src/js/registry.ts | 12 +- 10 files changed, 121 insertions(+), 105 deletions(-) diff --git a/client/core/core.go b/client/core/core.go index 981d71c829..ad41b934e4 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -4847,11 +4847,20 @@ func (c *Core) Orders(filter *OrderFilter) ([]*Order, error) { copy(oid[:], filter.Offset) } + var mkt *db.OrderFilterMarket + if filter.Market != nil { + mkt = &db.OrderFilterMarket{ + Base: filter.Market.Base, + Quote: filter.Market.Quote, + } + } + ords, err := c.db.Orders(&db.OrderFilter{ N: filter.N, Offset: oid, Hosts: filter.Hosts, Assets: filter.Assets, + Market: mkt, Statuses: filter.Statuses, }) if err != nil { @@ -4864,6 +4873,10 @@ func (c *Core) Orders(filter *OrderFilter) ([]*Order, error) { if err != nil { return nil, err } + baseWallet, baseOK := c.wallet(corder.BaseID) + quoteWallet, quoteOK := c.wallet(corder.QuoteID) + corder.ReadyToTick = baseOK && baseWallet.connected() && baseWallet.unlocked() && + quoteOK && quoteWallet.connected() && quoteWallet.unlocked() cords = append(cords, corder) } diff --git a/client/core/types.go b/client/core/types.go index 792cd2e36f..102b56798b 100644 --- a/client/core/types.go +++ b/client/core/types.go @@ -1092,6 +1092,10 @@ type OrderFilter struct { Hosts []string `json:"hosts"` Assets []uint32 `json:"assets"` Statuses []order.OrderStatus `json:"statuses"` + Market *struct { + Base uint32 `json:"baseID"` + Quote uint32 `json:"quoteID"` + } `json:"market"` } // Account holds data returned from AccountExport. diff --git a/client/db/bolt/db.go b/client/db/bolt/db.go index 24be9ea44e..d1b5cefa8a 100644 --- a/client/db/bolt/db.go +++ b/client/db/bolt/db.go @@ -1162,6 +1162,13 @@ func (db *BoltDB) Orders(orderFilter *dexdb.OrderFilter) (ords []*dexdb.MetaOrde } } + if orderFilter.Market != nil { + filters = append(filters, func(_ []byte, oBkt *bbolt.Bucket) bool { + baseID, quoteID := intCoder.Uint32(oBkt.Get(baseKey)), intCoder.Uint32(oBkt.Get(quoteKey)) + return orderFilter.Market.Base == baseID && orderFilter.Market.Quote == quoteID + }) + } + if !orderFilter.Offset.IsZero() { offsetOID := orderFilter.Offset var stampB []byte diff --git a/client/db/types.go b/client/db/types.go index 78a80c7554..adbe1a40cd 100644 --- a/client/db/types.go +++ b/client/db/types.go @@ -1192,6 +1192,11 @@ func (n *Notification) Encode() []byte { AddData([]byte(n.TopicID)) } +type OrderFilterMarket struct { + Base uint32 + Quote uint32 +} + // OrderFilter is used to limit the results returned by a query to (DB).Orders. type OrderFilter struct { // N is the number of orders to return in the set. @@ -1206,6 +1211,8 @@ type OrderFilter struct { // Assets is a list of BIP IDs for acceptable assets. A zero-length Assets // means all assets are accepted. Assets []uint32 + // Market limits results to a specific market. + Market *OrderFilterMarket // Statuses is a list of acceptable statuses. A zero-length Statuses means // all statuses are accepted. Statuses []order.OrderStatus diff --git a/client/webserver/site/src/css/market.scss b/client/webserver/site/src/css/market.scss index e9a67b3bd3..777422568b 100644 --- a/client/webserver/site/src/css/market.scss +++ b/client/webserver/site/src/css/market.scss @@ -121,15 +121,6 @@ div[data-handler=markets] { } } - #sweepOrders { - position: absolute; - right: 15px; - top: 50%; - transform: translateY(-50%); - line-height: 1; - padding: 6px; - } - #unreadyOrdersMsg { color: red; } @@ -139,7 +130,6 @@ div[data-handler=markets] { } .user-order { - margin: 0 20px; border: 1px solid $light_border_color; &:not(:last-child) { @@ -173,6 +163,10 @@ div[data-handler=markets] { &.sell { background-color: $sellcolor_light; } + + &.inactive { + opacity: 0.5; + } } .active-indicator { @@ -196,6 +190,7 @@ div[data-handler=markets] { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; row-gap: 10px; + column-gap: 5px; line-height: 1; .user-order-datum { @@ -910,15 +905,19 @@ div[data-handler=markets] { display: flex; align-items: center; z-index: 5; - border: 1px solid; - border-color: $light_border_color $light_input_border $light_input_border $light_input_border; - padding: 0 10px; + border-style: none solid solid; + border-width: 0 2px 2px 1px; + border-color: $light_input_border; background-color: $light_body_bg; cursor: pointer; overflow: hidden; & > span, & > a { + margin: 0 5px; + padding-right: 10px; + padding-left: 10px; + &:hover { background-color: #7775; } diff --git a/client/webserver/site/src/html/bodybuilder.tmpl b/client/webserver/site/src/html/bodybuilder.tmpl index 025470f024..9469118089 100644 --- a/client/webserver/site/src/html/bodybuilder.tmpl +++ b/client/webserver/site/src/html/bodybuilder.tmpl @@ -9,7 +9,7 @@ {{.Title}} - +