From bfe0351af76ba01c4df51d3f37bc12db22f40a42 Mon Sep 17 00:00:00 2001 From: Colin Rudd Date: Wed, 17 Dec 2025 14:21:29 -0500 Subject: [PATCH 1/7] Reimplement dash canvas panel demo to incorporate drop in widgets example. --- .../layout/dashCanvas/DashCanvasPanel.scss | 26 ++ .../layout/dashCanvas/DashCanvasPanel.tsx | 220 ++++------------ .../dashCanvas/DashCanvasPanelModel.tsx | 240 ++++++++++++++++++ .../tabs/layout/widgets/ChartWidget.ts | 31 ++- 4 files changed, 332 insertions(+), 185 deletions(-) create mode 100644 client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.scss create mode 100644 client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx diff --git a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.scss b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.scss new file mode 100644 index 000000000..91bb6c605 --- /dev/null +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.scss @@ -0,0 +1,26 @@ +.dash-canvas-droppable-demo { + .xh-dash-canvas { + .react-grid-layout { + min-height: 100%; + } + } + + .draggable-widget { + border: var(--xh-border-dotted); + background-color: var(--xh-bg-alt); + padding: var(--xh-pad-half-px); + margin: var(--xh-pad-half-px); + cursor: grab; + + &.is-dragging { + cursor: grabbing; + // lighten background color of left behind placeholder + // when dragging + opacity: 0.25; + } + + &:active { + cursor: grabbing; + } + } +} diff --git a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx index 502e0aa18..5f1184eec 100644 --- a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx @@ -1,25 +1,18 @@ import {switchInput, numberInput} from '@xh/hoist/desktop/cmp/input'; import {toolbar} from '@xh/hoist/desktop/cmp/toolbar'; import React from 'react'; -import {creates, hoistCmp, HoistModel, managed, XH} from '@xh/hoist/core'; -import {bindable, makeObservable} from '@xh/hoist/mobx'; +import {creates, hoistCmp} from '@xh/hoist/core'; import {Icon} from '@xh/hoist/icon'; -import {filler, frame} from '@xh/hoist/cmp/layout'; +import {div, filler, frame, hframe, vframe} from '@xh/hoist/cmp/layout'; import {panel} from '@xh/hoist/desktop/cmp/panel'; import {button, refreshButton} from '@xh/hoist/desktop/cmp/button'; -import {dashCanvas, DashCanvasModel} from '@xh/hoist/desktop/cmp/dash'; -import { - buttonWidget, - chartWidget, - errorWidget, - gridWidget, - panelWidget, - treeGridWidget -} from '../widgets'; +import {dashCanvas} from '@xh/hoist/desktop/cmp/dash'; + import {wrapper} from '../../../common'; +import {DashCanvasPanelModel} from './DashCanvasPanelModel'; export const dashCanvasPanel = hoistCmp.factory({ - model: creates(() => Model), + model: creates(() => DashCanvasPanelModel), render({model}) { return wrapper({ @@ -42,14 +35,34 @@ export const dashCanvasPanel = hoistCmp.factory({ title: 'Layout › DashCanvas', icon: Icon.layout(), headerItems: [refreshButton({minimal: true, intent: null})], + className: 'dash-canvas-droppable-demo', height: '80%', width: '80%', - item: model.renderDashboard - ? dashCanvas() - : frame({ - item: 'The Dashboard is not rendered now and has been unmounted. When rendered again, its previous state will be restored.', - padding: 10 - }), + item: hframe( + model.renderDashboard + ? dashCanvas({ + omit: !model.dashCanvasModel + }) + : frame({ + item: 'The Dashboard is not rendered now and has been unmounted. When rendered again, its previous state will be restored.', + padding: 10 + }), + panel({ + icon: Icon.arrowsLeftRight(), + title: 'Single Series Charts', + width: 250, + modelConfig: { + side: 'right', + defaultSize: 250 + }, + item: vframe({ + className: 'scrollable-div vertical-only', + items: model.unusedSymbols.map(it => + draggableWidget({symbol: it, key: it}) + ) + }) + }) + ), bbar: bbar() }), links: [ @@ -78,7 +91,7 @@ export const dashCanvasPanel = hoistCmp.factory({ } }); -const bbar = hoistCmp.factory(({model}) => +const bbar = hoistCmp.factory(({model}) => toolbar({ enableOverflowMenu: true, children: [ @@ -143,159 +156,14 @@ const bbar = hoistCmp.factory(({model}) => }) ); -class Model extends HoistModel { - @bindable renderDashboard = true; - - @managed - dashCanvasModel = new DashCanvasModel({ - persistWith: {localStorageKey: 'dashCanvasExampleState'}, - initialState, - viewSpecDefaults: { - icon: Icon.gridPanel() - }, - viewSpecs: [ - { - id: 'grid', - title: 'Grid', - unique: true, - content: gridWidget, - width: 6, - height: 5, - groupName: 'Grid Widgets' - }, - { - id: 'treeGrid', - title: 'Tree Grid', - content: treeGridWidget, - width: 12, - height: 8, - groupName: 'Grid Widgets' - }, - { - id: 'buttons', - title: 'Buttons', - icon: Icon.stop(), - content: buttonWidget, - width: 4, - height: 2, - allowRename: false, - hideMenuButton: true - }, - { - id: 'chart', - title: 'Chart', - icon: Icon.chartLine(), - unique: true, - content: chartWidget, - width: 12, - height: 5 - }, - { - id: 'panel', - title: 'Panel', - icon: Icon.window(), - content: panelWidget - }, - { - id: 'error', - title: 'Error Example', - icon: Icon.skull(), - unique: true, - content: errorWidget({componentName: 'DashCanvas'}) - } - ] - }); - - constructor() { - super(); - makeObservable(this); - } - - clearCanvas() { - this.dashCanvasModel.viewModels.forEach(it => this.dashCanvasModel.removeView(it.id)); - XH.toast({message: 'All views removed.'}); - } - - resetState() { - this.dashCanvasModel.restoreDefaults(); - XH.toast({message: 'Dash state reset to default'}); - } -} - -const initialState = [ - { - layout: { - x: 0, - y: 0, - w: 12, - h: 5 - }, - viewSpecId: 'chart' - }, - { - layout: { - x: 0, - y: 5, - w: 4, - h: 3 - }, - viewSpecId: 'buttons', - title: 'Buttons 1', - state: { - value: 'Button 1' - } - }, - { - layout: { - x: 4, - y: 5, - w: 4, - h: 3 - }, - viewSpecId: 'buttons', - title: 'Buttons 2', - state: { - value: 'Button 2' - } - }, - { - layout: { - x: 8, - y: 5, - w: 4, - h: 3 - }, - viewSpecId: 'buttons', - title: 'Buttons 3', - state: { - value: 'Button 3' - } - }, - { - layout: { - x: 9, - y: 8, - w: 3, - h: 4 - }, - viewSpecId: 'panel' - }, - { - layout: { - x: 0, - y: 8, - w: 9, - h: 7 - }, - viewSpecId: 'treeGrid' - }, - { - layout: { - x: 0, - y: 15, - w: 6, - h: 6 - }, - viewSpecId: 'error' - } -]; +const draggableWidget = hoistCmp.factory(({model, symbol}) => + div({ + id: 'draggableFor-singleSeriesChart-' + symbol, + className: 'draggable-widget', + draggable: true, + unselectable: 'on', + onDragStart: e => model.onDragStart(e), + onDragEnd: e => model.onDragEnd(e), + items: [Icon.chartLine(), ' ', symbol] + }) +); diff --git a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx new file mode 100644 index 000000000..3a93455c0 --- /dev/null +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx @@ -0,0 +1,240 @@ +import {DragEvent} from 'react'; +import {difference, isEmpty} from 'lodash'; +import {HoistModel, managed, XH} from '@xh/hoist/core'; +import {bindable, makeObservable, observable, runInAction} from '@xh/hoist/mobx'; +import {Icon} from '@xh/hoist/icon'; +import {DashCanvasModel} from '@xh/hoist/desktop/cmp/dash'; +import { + buttonWidget, + chartWidget, + errorWidget, + gridWidget, + panelWidget, + treeGridWidget +} from '../widgets'; + +import './DashCanvasPanel.scss'; + +export class DashCanvasPanelModel extends HoistModel { + @bindable renderDashboard = true; + @observable.ref allSymbols: string[] = []; + + @managed + @observable.ref + dashCanvasModel: DashCanvasModel; + + onDragStart(evt: DragEvent) { + const target = evt.target as HTMLElement; + if (!target) return; + + this.dashCanvasModel.showAddViewButtonWhenEmpty = false; + evt.dataTransfer.effectAllowed = 'move'; + target.classList.add('is-dragging'); + + const viewSpecId: string = target.getAttribute('id').split('draggableFor-')[1], + viewSpec = this.dashCanvasModel.viewSpecs.find(it => it.id === viewSpecId), + {width, height} = viewSpec, + widget = { + viewSpecId, + layout: { + x: 0, + y: 0, + w: width, + h: height + } + }; + + this.dashCanvasModel.setDraggedInView(widget); + } + + onDragEnd(evt: DragEvent) { + this.dashCanvasModel.showAddViewButtonWhenEmpty = true; + + const target = evt.target as HTMLElement; + if (!target) return; + + target.classList.remove('is-dragging'); + } + + get unusedSymbols() { + const usedSymbols = this.dashCanvasModel?.viewModels + .filter(it => it.viewSpec.id.startsWith('singleSeriesChart')) + .map(it => it.viewSpec.id.split('-')[1]); + + return difference(this.allSymbols, usedSymbols); + } + override async doLoadAsync(loadSpec) { + if (isEmpty(this.allSymbols)) { + const symbols = await XH.portfolioService.getSymbolsAsync({loadSpec}); + runInAction(() => { + this.allSymbols = symbols.slice(0, 5); + this.dashCanvasModel = this.createDashCanvasModel(); + }); + } + } + + constructor() { + super(); + makeObservable(this); + } + + clearCanvas() { + this.dashCanvasModel.viewModels.forEach(it => this.dashCanvasModel.removeView(it.id)); + XH.toast({message: 'All views removed.'}); + } + + resetState() { + this.dashCanvasModel.restoreDefaults(); + XH.toast({message: 'Dash state reset to default'}); + } + + private createDashCanvasModel() { + return new DashCanvasModel({ + persistWith: {localStorageKey: 'dashCanvasExampleState'}, + droppable: true, + initialState, + viewSpecDefaults: { + icon: Icon.gridPanel() + }, + viewSpecs: [ + { + id: 'grid', + title: 'Grid', + unique: true, + content: gridWidget, + width: 6, + height: 5, + groupName: 'Grid Widgets' + }, + { + id: 'treeGrid', + title: 'Tree Grid', + content: treeGridWidget, + width: 12, + height: 8, + groupName: 'Grid Widgets' + }, + { + id: 'buttons', + title: 'Buttons', + icon: Icon.stop(), + content: buttonWidget, + width: 4, + height: 2, + allowRename: false, + hideMenuButton: true + }, + { + id: 'chart', + title: 'Multi-Chart', + icon: Icon.chartLine(), + unique: true, + content: chartWidget, + width: 12, + height: 5 + }, + ...this.allSymbols.map(symbol => ({ + id: 'singleSeriesChart-' + symbol, + title: symbol + ' Chart', + groupName: 'Single Series Charts', + icon: Icon.chartLine(), + unique: true, + content: chartWidget, + width: 12, + height: 5 + })), + { + id: 'panel', + title: 'Panel', + icon: Icon.window(), + content: panelWidget + }, + { + id: 'error', + title: 'Error Example', + icon: Icon.skull(), + unique: true, + content: errorWidget({componentName: 'DashCanvas'}) + } + ] + }); + } +} + +const initialState = [ + { + layout: { + x: 0, + y: 0, + w: 12, + h: 5 + }, + viewSpecId: 'chart' + }, + { + layout: { + x: 0, + y: 5, + w: 4, + h: 3 + }, + viewSpecId: 'buttons', + title: 'Buttons 1', + state: { + value: 'Button 1' + } + }, + { + layout: { + x: 4, + y: 5, + w: 4, + h: 3 + }, + viewSpecId: 'buttons', + title: 'Buttons 2', + state: { + value: 'Button 2' + } + }, + { + layout: { + x: 8, + y: 5, + w: 4, + h: 3 + }, + viewSpecId: 'buttons', + title: 'Buttons 3', + state: { + value: 'Button 3' + } + }, + { + layout: { + x: 9, + y: 8, + w: 3, + h: 4 + }, + viewSpecId: 'panel' + }, + { + layout: { + x: 0, + y: 8, + w: 9, + h: 7 + }, + viewSpecId: 'treeGrid' + }, + { + layout: { + x: 0, + y: 15, + w: 6, + h: 6 + }, + viewSpecId: 'error' + } +]; diff --git a/client-app/src/desktop/tabs/layout/widgets/ChartWidget.ts b/client-app/src/desktop/tabs/layout/widgets/ChartWidget.ts index 3d55ec854..b158a3967 100644 --- a/client-app/src/desktop/tabs/layout/widgets/ChartWidget.ts +++ b/client-app/src/desktop/tabs/layout/widgets/ChartWidget.ts @@ -25,14 +25,17 @@ export const chartWidget = hoistCmp.factory({ model: panelModel, ...(panelModel.isModal ? modalOpts : {}), item: chart(), - bbar: [ - box('Symbol: '), - select({ - bind: 'currentSymbol', - options: symbols, - enableFilter: false - }) - ] + bbar: !model.presetSymbol + ? [ + box('Symbol: '), + select({ + bind: 'currentSymbols', + options: symbols, + enableFilter: false, + enableMulti: true + }) + ] + : null }); } }); @@ -60,6 +63,10 @@ class ChartWidgetModel extends LineChartModel { resizable: false }); + get presetSymbol() { + return this.dashViewModel?.viewSpec.id.split('-')[1]; + } + constructor() { super(); makeObservable(this); @@ -67,9 +74,10 @@ class ChartWidgetModel extends LineChartModel { this.addReaction({ track: () => [this.range, this.chartModel.series], run: () => { - if (!this.chartModel.series[0]) return; + if (!this.chartModel.series[0] || !this.chartModel.highchart) return; const endDate = new Date(), startDate = new Date(endDate.getTime() - ONE_DAY * this.range); + this.chartModel.highchart.xAxis[0].setExtremes( startDate.getTime(), endDate.getTime() @@ -81,6 +89,11 @@ class ChartWidgetModel extends LineChartModel { override onLinked() { const {dashViewModel, panelModel} = this; + if (this.presetSymbol) { + dashViewModel.title = this.presetSymbol; + this.currentSymbols = [this.presetSymbol]; + } + dashViewModel.extraMenuItems = [ { text: 'Print chart', From 53e5e29a908a8740ccbae853d87777428cd687ab Mon Sep 17 00:00:00 2001 From: Colin Rudd Date: Tue, 23 Dec 2025 11:18:23 -0500 Subject: [PATCH 2/7] support 2 compact strategies + support toggle grid background --- .../tabs/layout/dashCanvas/DashCanvasPanel.tsx | 16 ++++++++++++---- client-app/tsconfig.json | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx index 5f1184eec..1f3af7040 100644 --- a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx @@ -1,4 +1,4 @@ -import {switchInput, numberInput} from '@xh/hoist/desktop/cmp/input'; +import {switchInput, numberInput, select} from '@xh/hoist/desktop/cmp/input'; import {toolbar} from '@xh/hoist/desktop/cmp/toolbar'; import React from 'react'; import {creates, hoistCmp} from '@xh/hoist/core'; @@ -122,6 +122,13 @@ const bbar = hoistCmp.factory(({model}) => model: model.dashCanvasModel }), '-', + switchInput({ + label: 'Show Background', + bind: 'showGridBackground', + labelSide: 'left', + model: model.dashCanvasModel + }), + '-', 'Columns', numberInput({ width: 80, @@ -136,10 +143,11 @@ const bbar = hoistCmp.factory(({model}) => model: model.dashCanvasModel }), '-', - switchInput({ + 'Compact Views', + select({ bind: 'compact', - label: 'Compact Views', - model: model.dashCanvasModel + model: model.dashCanvasModel, + options: ['vertical', 'horizontal', false] }), filler(), button({ diff --git a/client-app/tsconfig.json b/client-app/tsconfig.json index 4512baa98..5167b5039 100644 --- a/client-app/tsconfig.json +++ b/client-app/tsconfig.json @@ -5,7 +5,7 @@ "jsx": "react", "lib": ["dom", "es2022"], "module": "ES2022", - "moduleResolution": "Node", + "moduleResolution": "bundler", "noEmit": true, "noImplicitOverride": true, "resolveJsonModule": true, From bf0b291b4aeccab43ce7bc17098efee5b39533c6 Mon Sep 17 00:00:00 2001 From: Colin Rudd Date: Tue, 23 Dec 2025 11:47:10 -0500 Subject: [PATCH 3/7] replace droppable with allowsDrop --- .../src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx index 3a93455c0..93b4bb0bf 100644 --- a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx @@ -91,7 +91,7 @@ export class DashCanvasPanelModel extends HoistModel { private createDashCanvasModel() { return new DashCanvasModel({ persistWith: {localStorageKey: 'dashCanvasExampleState'}, - droppable: true, + allowsDrop: true, initialState, viewSpecDefaults: { icon: Icon.gridPanel() From b4e5bc004a8e7cbc9a03aa3656f8b0a32e1c7401 Mon Sep 17 00:00:00 2001 From: Colin Rudd Date: Tue, 23 Dec 2025 14:50:52 -0500 Subject: [PATCH 4/7] move css import --- .../src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx | 2 ++ .../src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx index 1f3af7040..a67f4dd3c 100644 --- a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx @@ -11,6 +11,8 @@ import {dashCanvas} from '@xh/hoist/desktop/cmp/dash'; import {wrapper} from '../../../common'; import {DashCanvasPanelModel} from './DashCanvasPanelModel'; +import './DashCanvasPanel.scss'; + export const dashCanvasPanel = hoistCmp.factory({ model: creates(() => DashCanvasPanelModel), diff --git a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx index 93b4bb0bf..904e9319d 100644 --- a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx @@ -13,8 +13,6 @@ import { treeGridWidget } from '../widgets'; -import './DashCanvasPanel.scss'; - export class DashCanvasPanelModel extends HoistModel { @bindable renderDashboard = true; @observable.ref allSymbols: string[] = []; From 9d6df9167d3fc4eeee4703d61ba3440255810eab Mon Sep 17 00:00:00 2001 From: Colin Rudd Date: Thu, 8 Jan 2026 11:45:35 -0500 Subject: [PATCH 5/7] working widget well --- .../layout/dashCanvas/DashCanvasPanel.tsx | 25 +++-------- .../dashCanvas/DashCanvasPanelModel.tsx | 43 +------------------ 2 files changed, 6 insertions(+), 62 deletions(-) diff --git a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx index a67f4dd3c..5ccc56471 100644 --- a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx @@ -3,10 +3,11 @@ import {toolbar} from '@xh/hoist/desktop/cmp/toolbar'; import React from 'react'; import {creates, hoistCmp} from '@xh/hoist/core'; import {Icon} from '@xh/hoist/icon'; -import {div, filler, frame, hframe, vframe} from '@xh/hoist/cmp/layout'; +import {filler, frame, hframe} from '@xh/hoist/cmp/layout'; import {panel} from '@xh/hoist/desktop/cmp/panel'; import {button, refreshButton} from '@xh/hoist/desktop/cmp/button'; import {dashCanvas} from '@xh/hoist/desktop/cmp/dash'; +import {dashCanvasWidgetWell} from '@xh/hoist/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell'; import {wrapper} from '../../../common'; import {DashCanvasPanelModel} from './DashCanvasPanelModel'; @@ -50,19 +51,15 @@ export const dashCanvasPanel = hoistCmp.factory({ padding: 10 }), panel({ + omit: !model.renderDashboard, icon: Icon.arrowsLeftRight(), - title: 'Single Series Charts', + title: 'Add Widgets...', width: 250, modelConfig: { side: 'right', defaultSize: 250 }, - item: vframe({ - className: 'scrollable-div vertical-only', - items: model.unusedSymbols.map(it => - draggableWidget({symbol: it, key: it}) - ) - }) + item: dashCanvasWidgetWell({dashCanvasModel: model.dashCanvasModel}) }) ), bbar: bbar() @@ -165,15 +162,3 @@ const bbar = hoistCmp.factory(({model}) => ] }) ); - -const draggableWidget = hoistCmp.factory(({model, symbol}) => - div({ - id: 'draggableFor-singleSeriesChart-' + symbol, - className: 'draggable-widget', - draggable: true, - unselectable: 'on', - onDragStart: e => model.onDragStart(e), - onDragEnd: e => model.onDragEnd(e), - items: [Icon.chartLine(), ' ', symbol] - }) -); diff --git a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx index 904e9319d..c931917c0 100644 --- a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx @@ -1,5 +1,4 @@ -import {DragEvent} from 'react'; -import {difference, isEmpty} from 'lodash'; +import {isEmpty} from 'lodash'; import {HoistModel, managed, XH} from '@xh/hoist/core'; import {bindable, makeObservable, observable, runInAction} from '@xh/hoist/mobx'; import {Icon} from '@xh/hoist/icon'; @@ -21,46 +20,6 @@ export class DashCanvasPanelModel extends HoistModel { @observable.ref dashCanvasModel: DashCanvasModel; - onDragStart(evt: DragEvent) { - const target = evt.target as HTMLElement; - if (!target) return; - - this.dashCanvasModel.showAddViewButtonWhenEmpty = false; - evt.dataTransfer.effectAllowed = 'move'; - target.classList.add('is-dragging'); - - const viewSpecId: string = target.getAttribute('id').split('draggableFor-')[1], - viewSpec = this.dashCanvasModel.viewSpecs.find(it => it.id === viewSpecId), - {width, height} = viewSpec, - widget = { - viewSpecId, - layout: { - x: 0, - y: 0, - w: width, - h: height - } - }; - - this.dashCanvasModel.setDraggedInView(widget); - } - - onDragEnd(evt: DragEvent) { - this.dashCanvasModel.showAddViewButtonWhenEmpty = true; - - const target = evt.target as HTMLElement; - if (!target) return; - - target.classList.remove('is-dragging'); - } - - get unusedSymbols() { - const usedSymbols = this.dashCanvasModel?.viewModels - .filter(it => it.viewSpec.id.startsWith('singleSeriesChart')) - .map(it => it.viewSpec.id.split('-')[1]); - - return difference(this.allSymbols, usedSymbols); - } override async doLoadAsync(loadSpec) { if (isEmpty(this.allSymbols)) { const symbols = await XH.portfolioService.getSymbolsAsync({loadSpec}); From a5ba0a4365a1e8725450b046cf6794d8beaae158 Mon Sep 17 00:00:00 2001 From: Colin Rudd Date: Thu, 8 Jan 2026 14:25:29 -0500 Subject: [PATCH 6/7] new hoist components: DashCanvasWidgetWell CollapsibleFieldSet FieldsetCollapseButton --- .../layout/dashCanvas/DashCanvasPanel.scss | 19 ------------------- .../layout/dashCanvas/DashCanvasPanel.tsx | 4 +++- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.scss b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.scss index 91bb6c605..46c6046fc 100644 --- a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.scss +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.scss @@ -4,23 +4,4 @@ min-height: 100%; } } - - .draggable-widget { - border: var(--xh-border-dotted); - background-color: var(--xh-bg-alt); - padding: var(--xh-pad-half-px); - margin: var(--xh-pad-half-px); - cursor: grab; - - &.is-dragging { - cursor: grabbing; - // lighten background color of left behind placeholder - // when dragging - opacity: 0.25; - } - - &:active { - cursor: grabbing; - } - } } diff --git a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx index 5ccc56471..73a9b5be6 100644 --- a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx @@ -59,7 +59,9 @@ export const dashCanvasPanel = hoistCmp.factory({ side: 'right', defaultSize: 250 }, - item: dashCanvasWidgetWell({dashCanvasModel: model.dashCanvasModel}) + item: dashCanvasWidgetWell({ + dashCanvasModel: model.dashCanvasModel + }) }) ), bbar: bbar() From 53d574aebb605d196ae005ed71926a519535babe Mon Sep 17 00:00:00 2001 From: Colin Rudd Date: Fri, 9 Jan 2026 14:09:34 -0500 Subject: [PATCH 7/7] add .vscode/tasks.json.template to help get project running in VSCode --- .gitignore | 1 + .vscode/tasks.json.template | 52 +++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 .vscode/tasks.json.template diff --git a/.gitignore b/.gitignore index 8a83e5b50..758e1f69d 100755 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .settings .project .classpath +.vscode/tasks.json # Local instance configs w/secrets toolbox.yml diff --git a/.vscode/tasks.json.template b/.vscode/tasks.json.template new file mode 100644 index 000000000..95c2f522d --- /dev/null +++ b/.vscode/tasks.json.template @@ -0,0 +1,52 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + // Copy this .template file to .vscode/tasks.json and modify JAVA_HOME as needed + // Your .vscode/tasks.json file is ignored by git to avoid committing local settings + + "version": "2.0.0", + "tasks": [ + // use these if your default shell in VS Code is ZSH on Mac/Linux + { + "label": "ZSH: Run Grails Server", + "type": "shell", + "command": "./gradlew", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "JAVA_HOME": "${userHome}/Library/Java/JavaVirtualMachines/jbr-17.0.14/Contents/Home" + } + }, + "args": [ + "bootRun", + "-D", + "user.timezone=UTC" + ], + "problemMatcher": [] + }, + { + "label": "ZSH: Clean Build", + "type": "shell", + "command": "./gradlew 'clean'", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "JAVA_HOME": "${userHome}/Library/Java/JavaVirtualMachines/jbr-17.0.14/Contents/Home" + } + }, + "problemMatcher": [] + }, + { + "label": "ZSH: Build", + "type": "shell", + "command": "./gradlew 'build' --refresh-dependencies", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "JAVA_HOME": "${userHome}/Library/Java/JavaVirtualMachines/jbr-17.0.14/Contents/Home" + } + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file