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 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..46c6046fc --- /dev/null +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.scss @@ -0,0 +1,7 @@ +.dash-canvas-droppable-demo { + .xh-dash-canvas { + .react-grid-layout { + min-height: 100%; + } + } +} diff --git a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx index 3bb5c05a3..73a9b5be6 100644 --- a/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanel.tsx @@ -1,25 +1,21 @@ 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, 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 {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, 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 {dashCanvasWidgetWell} from '@xh/hoist/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell'; + import {wrapper} from '../../../common'; +import {DashCanvasPanelModel} from './DashCanvasPanelModel'; + +import './DashCanvasPanel.scss'; export const dashCanvasPanel = hoistCmp.factory({ - model: creates(() => Model), + model: creates(() => DashCanvasPanelModel), render({model}) { return wrapper({ @@ -42,14 +38,32 @@ 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({ + omit: !model.renderDashboard, + icon: Icon.arrowsLeftRight(), + title: 'Add Widgets...', + width: 250, + modelConfig: { + side: 'right', + defaultSize: 250 + }, + item: dashCanvasWidgetWell({ + dashCanvasModel: model.dashCanvasModel + }) + }) + ), bbar: bbar() }), links: [ @@ -78,7 +92,7 @@ export const dashCanvasPanel = hoistCmp.factory({ } }); -const bbar = hoistCmp.factory(({model}) => +const bbar = hoistCmp.factory(({model}) => toolbar({ enableOverflowMenu: true, children: [ @@ -150,160 +164,3 @@ 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' - } -]; 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..c931917c0 --- /dev/null +++ b/client-app/src/desktop/tabs/layout/dashCanvas/DashCanvasPanelModel.tsx @@ -0,0 +1,197 @@ +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'; +import {DashCanvasModel} from '@xh/hoist/desktop/cmp/dash'; +import { + buttonWidget, + chartWidget, + errorWidget, + gridWidget, + panelWidget, + treeGridWidget +} from '../widgets'; + +export class DashCanvasPanelModel extends HoistModel { + @bindable renderDashboard = true; + @observable.ref allSymbols: string[] = []; + + @managed + @observable.ref + dashCanvasModel: DashCanvasModel; + + 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'}, + allowsDrop: 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 b8dba6b3a..b158a3967 100644 --- a/client-app/src/desktop/tabs/layout/widgets/ChartWidget.ts +++ b/client-app/src/desktop/tabs/layout/widgets/ChartWidget.ts @@ -25,15 +25,17 @@ export const chartWidget = hoistCmp.factory({ model: panelModel, ...(panelModel.isModal ? modalOpts : {}), item: chart(), - bbar: [ - box('Symbol: '), - select({ - bind: 'currentSymbols', - options: symbols, - enableFilter: false, - enableMulti: true - }) - ] + bbar: !model.presetSymbol + ? [ + box('Symbol: '), + select({ + bind: 'currentSymbols', + options: symbols, + enableFilter: false, + enableMulti: true + }) + ] + : null }); } }); @@ -61,6 +63,10 @@ class ChartWidgetModel extends LineChartModel { resizable: false }); + get presetSymbol() { + return this.dashViewModel?.viewSpec.id.split('-')[1]; + } + constructor() { super(); makeObservable(this); @@ -83,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',