@stocksharp/diagram 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -11
- package/dist/esm/canvas-renderer.js +25 -20
- package/dist/esm/canvas-renderer.js.map +1 -1
- package/dist/esm/diagram/context-menu.js +66 -19
- package/dist/esm/diagram/context-menu.js.map +1 -1
- package/dist/esm/diagram/stocksharp-diagram.js +42 -8
- package/dist/esm/diagram/stocksharp-diagram.js.map +1 -1
- package/dist/esm/embed.js +7 -4
- package/dist/esm/embed.js.map +1 -1
- package/dist/esm/i18n.js +15 -4
- package/dist/esm/i18n.js.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/ssdiagram.js +124 -49
- package/dist/ssdiagram.js.map +2 -2
- package/dist/types/canvas-renderer.d.ts +1 -0
- package/dist/types/canvas-renderer.d.ts.map +1 -1
- package/dist/types/diagram/api.d.ts +7 -1
- package/dist/types/diagram/api.d.ts.map +1 -1
- package/dist/types/diagram/context-menu.d.ts +2 -0
- package/dist/types/diagram/context-menu.d.ts.map +1 -1
- package/dist/types/diagram/stocksharp-diagram.d.ts +7 -0
- package/dist/types/diagram/stocksharp-diagram.d.ts.map +1 -1
- package/dist/types/embed.d.ts.map +1 -1
- package/dist/types/i18n.d.ts +8 -0
- package/dist/types/i18n.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/canvas-renderer.ts +17 -11
- package/src/diagram/api.ts +7 -0
- package/src/diagram/context-menu.ts +83 -18
- package/src/diagram/stocksharp-diagram.ts +45 -8
- package/src/embed.ts +7 -4
- package/src/i18n.ts +34 -6
- package/src/index.ts +5 -0
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from '../canvas-renderer.js';
|
|
29
29
|
import { StockSharpCatalog } from './catalog.js';
|
|
30
30
|
import { ContextMenuView } from './context-menu.js';
|
|
31
|
+
import { t } from '../i18n.js';
|
|
31
32
|
import { EventEmitter } from './event-emitter.js';
|
|
32
33
|
import type {
|
|
33
34
|
DiagramEvents,
|
|
@@ -80,10 +81,16 @@ const CONTEXT_MENU: readonly ContextMenuEntry[] = [
|
|
|
80
81
|
'open',
|
|
81
82
|
'delete',
|
|
82
83
|
{ group: 'export', commands: ['exportDocument', 'exportPng', 'exportSvg'] },
|
|
84
|
+
'overview',
|
|
83
85
|
'properties',
|
|
84
86
|
'help',
|
|
85
87
|
];
|
|
86
88
|
|
|
89
|
+
/** Commands that toggle rather than do, so the menu reports their state instead of just enabling them. */
|
|
90
|
+
const CONTEXT_CHECKED: Partial<Record<ContextCommand, (diagram: StockSharpDiagram) => boolean>> = {
|
|
91
|
+
overview: (diagram) => diagram.getViewState().overviewVisible,
|
|
92
|
+
};
|
|
93
|
+
|
|
87
94
|
export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
88
95
|
private readonly div: HTMLElement;
|
|
89
96
|
private readonly catalog: StockSharpCatalog;
|
|
@@ -101,7 +108,10 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
101
108
|
private destroyed = false;
|
|
102
109
|
private fullscreen = false;
|
|
103
110
|
private fullscreenButtonVisible = true;
|
|
104
|
-
|
|
111
|
+
// null means the host never named the button, so its wording comes from the shared bundle.
|
|
112
|
+
// Keeping the two apart is what lets an explicit label win while an untouched control still
|
|
113
|
+
// follows the page's language instead of being stuck in English.
|
|
114
|
+
private fullscreenLabels: DiagramFullscreenLabels | null = null;
|
|
105
115
|
private readonly contextActions = new DiagramActionRegistry<ContextCommand, ContextActionContext>();
|
|
106
116
|
private contextMenu: ContextMenuView | null = null;
|
|
107
117
|
|
|
@@ -119,7 +129,7 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
119
129
|
gridSize: options.gridSize,
|
|
120
130
|
});
|
|
121
131
|
this.fullscreenButtonVisible = options.showFullscreenButton ?? true;
|
|
122
|
-
this.fullscreenLabels = options.fullscreenLabels ??
|
|
132
|
+
this.fullscreenLabels = options.fullscreenLabels ?? null;
|
|
123
133
|
this.prepareFullscreenButtonHost();
|
|
124
134
|
this.fullscreenButton = this.createFullscreenButton();
|
|
125
135
|
this.updateFullscreenButton();
|
|
@@ -443,8 +453,24 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
443
453
|
this.updateFullscreenButton();
|
|
444
454
|
}
|
|
445
455
|
|
|
456
|
+
/**
|
|
457
|
+
* Re-reads the shared bundle for text the control renders itself. Call it after changing
|
|
458
|
+
* the page language: the context menu is rebuilt on every open and needs nothing, but the
|
|
459
|
+
* fullscreen button is drawn once and would otherwise keep the old wording.
|
|
460
|
+
*/
|
|
461
|
+
refreshLabels(): void {
|
|
462
|
+
this.updateFullscreenButton();
|
|
463
|
+
}
|
|
464
|
+
|
|
446
465
|
getFullscreenLabels(): DiagramSnapshot<DiagramFullscreenLabels> {
|
|
447
|
-
return { ...this.
|
|
466
|
+
return { ...this.resolveFullscreenLabels() };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private resolveFullscreenLabels(): DiagramFullscreenLabels {
|
|
470
|
+
return this.fullscreenLabels ?? {
|
|
471
|
+
enter: t('fullscreenEnter', 'Enter fullscreen'),
|
|
472
|
+
exit: t('fullscreenExit', 'Exit fullscreen'),
|
|
473
|
+
};
|
|
448
474
|
}
|
|
449
475
|
|
|
450
476
|
/** Updates only the control's state after the host changed its own layout. */
|
|
@@ -565,10 +591,14 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
565
591
|
|
|
566
592
|
getContextCommands(): ContextMenuItemState[] {
|
|
567
593
|
const context = this.contextActionContext();
|
|
568
|
-
const state = (command: ContextCommand): ContextCommandState =>
|
|
569
|
-
command
|
|
570
|
-
|
|
571
|
-
|
|
594
|
+
const state = (command: ContextCommand): ContextCommandState => {
|
|
595
|
+
const checked = CONTEXT_CHECKED[command];
|
|
596
|
+
return {
|
|
597
|
+
command,
|
|
598
|
+
enabled: this.contextActions.canExecute(command, context),
|
|
599
|
+
...(checked === undefined ? {} : { checked: checked(this) }),
|
|
600
|
+
};
|
|
601
|
+
};
|
|
572
602
|
return CONTEXT_MENU.map((entry) => {
|
|
573
603
|
if (typeof entry === 'string') return state(entry);
|
|
574
604
|
const commands = entry.commands.map(state);
|
|
@@ -709,7 +739,8 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
709
739
|
|
|
710
740
|
private updateFullscreenButton(): void {
|
|
711
741
|
const fullscreen = this.fullscreen;
|
|
712
|
-
const
|
|
742
|
+
const labels = this.resolveFullscreenLabels();
|
|
743
|
+
const label = fullscreen ? labels.exit : labels.enter;
|
|
713
744
|
const path = fullscreen
|
|
714
745
|
? 'M4 9h5V4M20 9h-5V4M4 15h5v5M20 15h-5v5'
|
|
715
746
|
: 'M9 4H4v5M15 4h5v5M9 20H4v-5M15 20h5v-5';
|
|
@@ -886,6 +917,12 @@ export class StockSharpDiagram extends EventEmitter<DiagramEvents> {
|
|
|
886
917
|
canExecute: ({ document }) => document.nodes.length > 0,
|
|
887
918
|
execute: () => this.emit('exportRequested', { format: 'svg' }),
|
|
888
919
|
},
|
|
920
|
+
// Always available: it shows the overview as readily as it hides it, and an empty
|
|
921
|
+
// diagram is exactly when someone may want the panel out of the way.
|
|
922
|
+
overview: {
|
|
923
|
+
canExecute: () => true,
|
|
924
|
+
execute: () => this.setOverviewVisible(!this.getViewState().overviewVisible),
|
|
925
|
+
},
|
|
889
926
|
properties: {
|
|
890
927
|
canExecute: ({ nodes }) => nodes.length > 0,
|
|
891
928
|
execute: ({ nodes }) => this.emit('nodeProperties', { nodes }),
|
package/src/embed.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t } from './i18n.js';
|
|
1
2
|
// Shared read-only diagram embed layer. Rendering, palette loading and theming
|
|
2
3
|
// live next to the diagram engine so web applications do not copy that logic.
|
|
3
4
|
//
|
|
@@ -407,7 +408,7 @@ async function renderSchemeAtRevision(
|
|
|
407
408
|
diagram.setLinkValidator(() => true);
|
|
408
409
|
diagram.setOverviewVisible(false);
|
|
409
410
|
const missingTypeTemplate = div.dataset.diagramMissingElement
|
|
410
|
-
?? 'Element type "{typeId}" is missing from the palette.';
|
|
411
|
+
?? t('embedMissingElement', 'Element type "{typeId}" is missing from the palette.');
|
|
411
412
|
const converted = toDiagramNodes(
|
|
412
413
|
scheme,
|
|
413
414
|
catalog,
|
|
@@ -508,10 +509,12 @@ interface EmbedErrorTexts {
|
|
|
508
509
|
// default would never fire and the note would render blank.
|
|
509
510
|
function errorTexts(div: HTMLElement): EmbedErrorTexts {
|
|
510
511
|
const parts = (div.dataset.diagramErrors ?? '').split('|');
|
|
512
|
+
// The attribute stays the override; without it the wording comes from the shared bundle, so
|
|
513
|
+
// a page that already translated the menu does not have to repeat itself per embed.
|
|
511
514
|
return {
|
|
512
|
-
load: parts[0] || 'Diagram source could not be loaded.',
|
|
513
|
-
empty: parts[1] || 'Diagram is empty or malformed.',
|
|
514
|
-
draw: parts[2] || 'Diagram could not be rendered.',
|
|
515
|
+
load: parts[0] || t('embedErrorLoad', 'Diagram source could not be loaded.'),
|
|
516
|
+
empty: parts[1] || t('embedErrorEmpty', 'Diagram is empty or malformed.'),
|
|
517
|
+
draw: parts[2] || t('embedErrorDraw', 'Diagram could not be rendered.'),
|
|
515
518
|
};
|
|
516
519
|
}
|
|
517
520
|
|
package/src/i18n.ts
CHANGED
|
@@ -95,6 +95,11 @@ export interface DesignerI18n {
|
|
|
95
95
|
copy?: string;
|
|
96
96
|
paste?: string;
|
|
97
97
|
ctxOpen?: string;
|
|
98
|
+
// Deletes the selected nodes and links. Deliberately not the solution explorer's
|
|
99
|
+
// `delete`, which removes a whole strategy: same English word, but the object differs,
|
|
100
|
+
// and a language that declines it -- or a host that writes "Delete strategy..." -- cannot
|
|
101
|
+
// serve both from one key.
|
|
102
|
+
ctxDelete?: string;
|
|
98
103
|
// Submenu title, and deliberately not `ctxExport` -- that one is the solution
|
|
99
104
|
// explorer's "export this strategy", a different action that a host may well word
|
|
100
105
|
// differently. Reads as its own item too: "Export as > SVG image".
|
|
@@ -102,8 +107,23 @@ export interface DesignerI18n {
|
|
|
102
107
|
ctxExportDocument?: string;
|
|
103
108
|
ctxExportPng?: string;
|
|
104
109
|
ctxExportSvg?: string;
|
|
110
|
+
ctxOverview?: string;
|
|
105
111
|
properties?: string;
|
|
106
112
|
ctxHelp?: string;
|
|
113
|
+
|
|
114
|
+
// Everything else the control renders by itself. Each of these also has a per-instance
|
|
115
|
+
// override -- the fullscreenLabels option, or a data-diagram-* attribute on an embedded
|
|
116
|
+
// host -- and that override wins; these are the defaults when none was given.
|
|
117
|
+
fullscreenEnter?: string;
|
|
118
|
+
fullscreenExit?: string;
|
|
119
|
+
embedErrorLoad?: string;
|
|
120
|
+
embedErrorEmpty?: string;
|
|
121
|
+
embedErrorDraw?: string;
|
|
122
|
+
embedMissingElement?: string;
|
|
123
|
+
|
|
124
|
+
// Not the diagram menu: it has no collapse/expand command. Kept for hosts already
|
|
125
|
+
// shipping them, but moved out of the block above so nobody translates them expecting
|
|
126
|
+
// to see them in the context menu.
|
|
107
127
|
collapse?: string;
|
|
108
128
|
expand?: string;
|
|
109
129
|
|
|
@@ -205,13 +225,21 @@ export interface DesignerI18n {
|
|
|
205
225
|
opterr_plotly_missing?: string;
|
|
206
226
|
}
|
|
207
227
|
|
|
208
|
-
const i18n: DesignerI18n =
|
|
209
|
-
typeof window === 'undefined'
|
|
210
|
-
? {}
|
|
211
|
-
: (window as unknown as { __designerI18n?: DesignerI18n }).__designerI18n ?? {};
|
|
212
|
-
|
|
213
228
|
/// Localized string for `key`, or `fallback` (English) when the host
|
|
214
229
|
/// didn't provide it.
|
|
230
|
+
///
|
|
231
|
+
/// The bundle is read on every call rather than captured when this module loads. Capturing
|
|
232
|
+
/// it meant the host had to assign `window.__designerI18n` before the bundle evaluated --
|
|
233
|
+
/// the opposite of the usual order, and impossible for a lazily loaded chunk -- and a host
|
|
234
|
+
/// that assigned it afterwards silently got English with nothing to explain why. Reading it
|
|
235
|
+
/// live also makes assigning and mutating the object behave the same, and lets the language
|
|
236
|
+
/// change mid-session: the only thing reading these keys is the context menu, and it is
|
|
237
|
+
/// built fresh on every right-click.
|
|
238
|
+
///
|
|
239
|
+
/// An empty string counts as missing. Half-finished translation exports routinely emit one,
|
|
240
|
+
/// and a blank menu entry is worse than an English one.
|
|
215
241
|
export function t(key: keyof DesignerI18n, fallback: string): string {
|
|
216
|
-
|
|
242
|
+
if (typeof window === 'undefined') return fallback;
|
|
243
|
+
const value = (window as unknown as { __designerI18n?: DesignerI18n }).__designerI18n?.[key];
|
|
244
|
+
return value === undefined || value === '' ? fallback : value;
|
|
217
245
|
}
|
package/src/index.ts
CHANGED
|
@@ -85,6 +85,11 @@ export {
|
|
|
85
85
|
StockSharpDiagram,
|
|
86
86
|
} from './diagram/stocksharp-diagram.js';
|
|
87
87
|
|
|
88
|
+
// The menu resolves its labels through this, so a host needs the type to write a bundle the
|
|
89
|
+
// compiler can check. Without it the key names get hand-copied, and a typo shows up only as an
|
|
90
|
+
// English label on screen.
|
|
91
|
+
export type { DesignerI18n } from './i18n.js';
|
|
92
|
+
|
|
88
93
|
export type {
|
|
89
94
|
ContextCommand,
|
|
90
95
|
ContextCommandGroup,
|