Line data Source code
1 : import type * as core from '@ankoh/dashql-core';
2 :
3 : import type { ModifySessionScripts } from '../scripts/session_scripts_registry.js';
4 : import { CREATE_SCRIPT, DELETE_SCRIPT, SET_SCRIPT_TEXT, type ScriptData } from '../scripts/session_scripts.js';
5 : import { scriptDisplayName, type FlowPosition } from '../scripts/script_types.js';
6 :
7 : /// The placement of the `@flow` tag of a script, null without a tag
8 1 : function placementOf(flow: FlowPosition | null | undefined): core.DataflowPlacement | null {
9 1 : if (flow == null) return null;
10 1 : return { position: flow.x != null && flow.y != null ? { x: flow.x, y: flow.y } : null, width: flow.width, height: flow.height };
11 1 : }
12 :
13 : /// Write a placement into the `@flow` tag of a script, the tag is removed if the placement is empty. Returns whether
14 : /// the script has the placement, a script that does not parse or has no statement can't be placed.
15 1 : function writeFlow(script: ScriptData, placement: core.DataflowPlacement, modifySessionScripts: ModifySessionScripts): boolean {
16 1 : const round = (value: number | null | undefined) => (value == null ? null : Math.round(value));
17 1 : const next = {
18 1 : position: placement.position == null ? null : { x: Math.round(placement.position.x), y: Math.round(placement.position.y) },
19 1 : width: round(placement.width),
20 1 : height: round(placement.height),
21 1 : };
22 1 : const empty = next.position == null && next.width == null && next.height == null;
23 1 : const current = placementOf(script.annotations.flowPosition);
24 1 : const unchanged = current == null
25 1 : ? empty
26 1 : : current.position?.x === next.position?.x && current.position?.y === next.position?.y
27 1 : && (current.width ?? null) === next.width && (current.height ?? null) === next.height;
28 1 : if (unchanged) return true;
29 1 : let text: string;
30 1 : try {
31 1 : text = empty ? script.scriptSession.removeVisFlow() : script.scriptSession.rewriteVisFlow(next);
32 1 : } catch (e) {
33 0 : console.warn('cannot write the placement of the script', e);
34 0 : return false;
35 0 : }
36 1 : modifySessionScripts({ type: SET_SCRIPT_TEXT, value: { scriptKey: script.scriptKey, text } });
37 1 : return true;
38 1 : }
39 :
40 : /// Write the position of a script into its `@flow` tag, the size of the box stays. Returns whether the script has
41 : /// the position.
42 : export function persistFlowPosition(script: ScriptData, x: number, y: number, modifySessionScripts: ModifySessionScripts): boolean {
43 : const current = placementOf(script.annotations.flowPosition);
44 : return writeFlow(script, { ...current, position: { x, y } }, modifySessionScripts);
45 : }
46 :
47 : /// Write the position and the size of a box into the `@flow` tag of its script at once: its width and the height of
48 : /// its chart, null for the default. Returns whether the script has the placement.
49 1 : export function persistFlowPlacement(
50 1 : script: ScriptData,
51 1 : position: { x: number; y: number },
52 1 : width: number | null,
53 1 : height: number | null,
54 1 : modifySessionScripts: ModifySessionScripts,
55 1 : ): boolean {
56 1 : return writeFlow(script, { position, width, height }, modifySessionScripts);
57 1 : }
58 :
59 : /// Write the size of a box into the `@flow` tag of its script: its width and the height of its chart, null for the
60 : /// default. The position stays, a box without one is still placed by the layout. Returns whether the script has
61 : /// the size.
62 : export function persistFlowSize(script: ScriptData, width: number | null, height: number | null, modifySessionScripts: ModifySessionScripts): boolean {
63 : const current = placementOf(script.annotations.flowPosition);
64 : return writeFlow(script, { position: current?.position ?? null, width, height }, modifySessionScripts);
65 : }
66 :
67 : /// Remove the positions from the `@flow` tags of scripts, the layout places them again. The sizes of the boxes stay.
68 1 : export function removeFlowPositions(scripts: ScriptData[], modifySessionScripts: ModifySessionScripts): void {
69 1 : for (const script of scripts) {
70 1 : const current = placementOf(script.annotations.flowPosition);
71 1 : if (current?.position == null) continue;
72 1 : writeFlow(script, { ...current, position: null }, modifySessionScripts);
73 1 : }
74 1 : }
75 :
76 : /// The text of a new script, at a position of the canvas if given, a query of the result of `producer` if given
77 1 : export function newFlowScriptText(position: { x: number; y: number } | null, producer: ScriptData | null, quoteIdentifier: (name: string) => string): string {
78 1 : const tag = position != null ? `/** @flow x=${Math.round(position.x)} y=${Math.round(position.y)} */\n` : '';
79 1 : if (producer == null) return `${tag}select 1`;
80 1 : return `${tag}select * from :${quoteIdentifier(scriptDisplayName(producer.fileName))}`;
81 1 : }
82 :
83 : /// Create a script: a query of the result of `producer` on its connection, or a query on the connection of the last
84 : /// script. A script without a position is placed by the layout, one that reads `producer` right of it.
85 1 : export function createFlowScript(
86 1 : position: { x: number; y: number } | null,
87 1 : producer: ScriptData | null,
88 1 : quoteIdentifier: (name: string) => string,
89 1 : modifySessionScripts: ModifySessionScripts,
90 1 : ): ReturnType<ModifySessionScripts> {
91 1 : return modifySessionScripts({
92 1 : type: CREATE_SCRIPT,
93 1 : value: {
94 1 : index: null,
95 1 : text: newFlowScriptText(position, producer, quoteIdentifier),
96 1 : ...(producer != null ? { connection: producer.connection } : {}),
97 1 : },
98 1 : });
99 1 : }
100 :
101 : /// Delete a script
102 1 : export function deleteFlowScript(script: ScriptData, modifySessionScripts: ModifySessionScripts): void {
103 1 : modifySessionScripts({ type: DELETE_SCRIPT, value: script.fileName });
104 1 : }
|