pict-section-flow 0.0.2 → 0.0.3
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/.claude/launch.json +11 -0
- package/docs/README.md +51 -0
- package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +105 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Comment.js +36 -0
- package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +42 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Each.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +1 -1
- package/example_applications/simple_cards/source/cards/FlowCard-Sparkline.js +98 -0
- package/example_applications/simple_cards/source/cards/FlowCard-StatusMonitor.js +44 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +1 -1
- package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +9 -1
- package/package.json +2 -2
- package/source/Pict-Section-Flow.js +8 -1
- package/source/PictFlowCard.js +49 -1
- package/source/providers/PictProvider-Flow-CSS.js +1440 -0
- package/source/providers/PictProvider-Flow-ConnectorShapes.js +413 -0
- package/source/providers/PictProvider-Flow-Geometry.js +43 -0
- package/source/providers/PictProvider-Flow-Icons.js +335 -0
- package/source/providers/PictProvider-Flow-Layouts.js +214 -2
- package/source/providers/PictProvider-Flow-NodeTypes.js +30 -7
- package/source/providers/PictProvider-Flow-Noise.js +241 -0
- package/source/providers/PictProvider-Flow-PanelChrome.js +19 -0
- package/source/providers/PictProvider-Flow-Theme.js +755 -0
- package/source/services/PictService-Flow-ConnectionRenderer.js +95 -32
- package/source/services/PictService-Flow-PanelManager.js +188 -0
- package/source/services/PictService-Flow-SelectionManager.js +109 -0
- package/source/services/PictService-Flow-Tether.js +52 -25
- package/source/services/PictService-Flow-ViewportManager.js +176 -0
- package/source/views/PictView-Flow-FloatingToolbar.js +352 -0
- package/source/views/PictView-Flow-Node.js +654 -169
- package/source/views/PictView-Flow-PropertiesPanel.js +176 -1
- package/source/views/PictView-Flow-Toolbar.js +846 -379
- package/source/views/PictView-Flow.js +279 -671
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PictService-Flow-ViewportManager
|
|
5
|
+
*
|
|
6
|
+
* Manages viewport transforms (pan/zoom), coordinate conversion between
|
|
7
|
+
* screen and SVG space, zoom-to-fit calculations, and fullscreen toggling
|
|
8
|
+
* for the flow diagram.
|
|
9
|
+
*/
|
|
10
|
+
class PictServiceFlowViewportManager extends libFableServiceProviderBase
|
|
11
|
+
{
|
|
12
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
13
|
+
{
|
|
14
|
+
super(pFable, pOptions, pServiceHash);
|
|
15
|
+
|
|
16
|
+
this.serviceType = 'PictServiceFlowViewportManager';
|
|
17
|
+
|
|
18
|
+
this._FlowView = (pOptions && pOptions.FlowView) ? pOptions.FlowView : null;
|
|
19
|
+
|
|
20
|
+
this._IsFullscreen = false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Update the viewport transform (pan and zoom)
|
|
25
|
+
*/
|
|
26
|
+
updateViewportTransform()
|
|
27
|
+
{
|
|
28
|
+
if (!this._FlowView._ViewportElement) return;
|
|
29
|
+
let tmpVS = this._FlowView._FlowData.ViewState;
|
|
30
|
+
this._FlowView._ViewportElement.setAttribute('transform',
|
|
31
|
+
`translate(${tmpVS.PanX}, ${tmpVS.PanY}) scale(${tmpVS.Zoom})`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Set zoom level
|
|
37
|
+
* @param {number} pZoom - The zoom level
|
|
38
|
+
* @param {number} [pFocusX] - X coordinate to zoom toward (SVG space)
|
|
39
|
+
* @param {number} [pFocusY] - Y coordinate to zoom toward (SVG space)
|
|
40
|
+
*/
|
|
41
|
+
setZoom(pZoom, pFocusX, pFocusY)
|
|
42
|
+
{
|
|
43
|
+
let tmpNewZoom = Math.max(this._FlowView.options.MinZoom, Math.min(this._FlowView.options.MaxZoom, pZoom));
|
|
44
|
+
let tmpOldZoom = this._FlowView._FlowData.ViewState.Zoom;
|
|
45
|
+
|
|
46
|
+
if (typeof pFocusX === 'number' && typeof pFocusY === 'number')
|
|
47
|
+
{
|
|
48
|
+
// Zoom toward focus point
|
|
49
|
+
let tmpVS = this._FlowView._FlowData.ViewState;
|
|
50
|
+
tmpVS.PanX = pFocusX - (pFocusX - tmpVS.PanX) * (tmpNewZoom / tmpOldZoom);
|
|
51
|
+
tmpVS.PanY = pFocusY - (pFocusY - tmpVS.PanY) * (tmpNewZoom / tmpOldZoom);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this._FlowView._FlowData.ViewState.Zoom = tmpNewZoom;
|
|
55
|
+
this.updateViewportTransform();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Zoom to fit all nodes in the viewport
|
|
60
|
+
*/
|
|
61
|
+
zoomToFit()
|
|
62
|
+
{
|
|
63
|
+
if (this._FlowView._FlowData.Nodes.length === 0) return;
|
|
64
|
+
if (!this._FlowView._SVGElement) return;
|
|
65
|
+
|
|
66
|
+
let tmpMinX = Infinity, tmpMinY = Infinity;
|
|
67
|
+
let tmpMaxX = -Infinity, tmpMaxY = -Infinity;
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < this._FlowView._FlowData.Nodes.length; i++)
|
|
70
|
+
{
|
|
71
|
+
let tmpNode = this._FlowView._FlowData.Nodes[i];
|
|
72
|
+
tmpMinX = Math.min(tmpMinX, tmpNode.X);
|
|
73
|
+
tmpMinY = Math.min(tmpMinY, tmpNode.Y);
|
|
74
|
+
tmpMaxX = Math.max(tmpMaxX, tmpNode.X + tmpNode.Width);
|
|
75
|
+
tmpMaxY = Math.max(tmpMaxY, tmpNode.Y + tmpNode.Height);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let tmpPadding = 50;
|
|
79
|
+
let tmpFlowWidth = tmpMaxX - tmpMinX + tmpPadding * 2;
|
|
80
|
+
let tmpFlowHeight = tmpMaxY - tmpMinY + tmpPadding * 2;
|
|
81
|
+
|
|
82
|
+
let tmpSVGRect = this._FlowView._SVGElement.getBoundingClientRect();
|
|
83
|
+
let tmpScaleX = tmpSVGRect.width / tmpFlowWidth;
|
|
84
|
+
let tmpScaleY = tmpSVGRect.height / tmpFlowHeight;
|
|
85
|
+
let tmpZoom = Math.min(tmpScaleX, tmpScaleY, 1.0); // Don't zoom in past 1.0
|
|
86
|
+
tmpZoom = Math.max(this._FlowView.options.MinZoom, Math.min(this._FlowView.options.MaxZoom, tmpZoom));
|
|
87
|
+
|
|
88
|
+
let tmpCenterX = (tmpMinX + tmpMaxX) / 2;
|
|
89
|
+
let tmpCenterY = (tmpMinY + tmpMaxY) / 2;
|
|
90
|
+
|
|
91
|
+
this._FlowView._FlowData.ViewState.Zoom = tmpZoom;
|
|
92
|
+
this._FlowView._FlowData.ViewState.PanX = (tmpSVGRect.width / 2) - (tmpCenterX * tmpZoom);
|
|
93
|
+
this._FlowView._FlowData.ViewState.PanY = (tmpSVGRect.height / 2) - (tmpCenterY * tmpZoom);
|
|
94
|
+
|
|
95
|
+
this.updateViewportTransform();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Convert screen coordinates to SVG viewport coordinates
|
|
100
|
+
* @param {number} pScreenX
|
|
101
|
+
* @param {number} pScreenY
|
|
102
|
+
* @returns {{x: number, y: number}}
|
|
103
|
+
*/
|
|
104
|
+
screenToSVGCoords(pScreenX, pScreenY)
|
|
105
|
+
{
|
|
106
|
+
if (!this._FlowView._SVGElement)
|
|
107
|
+
{
|
|
108
|
+
return { x: pScreenX, y: pScreenY };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let tmpPoint = this._FlowView._SVGElement.createSVGPoint();
|
|
112
|
+
tmpPoint.x = pScreenX;
|
|
113
|
+
tmpPoint.y = pScreenY;
|
|
114
|
+
|
|
115
|
+
let tmpCTM = this._FlowView._SVGElement.getScreenCTM();
|
|
116
|
+
if (tmpCTM)
|
|
117
|
+
{
|
|
118
|
+
let tmpInverse = tmpCTM.inverse();
|
|
119
|
+
let tmpTransformed = tmpPoint.matrixTransform(tmpInverse);
|
|
120
|
+
// Account for viewport pan/zoom
|
|
121
|
+
let tmpVS = this._FlowView._FlowData.ViewState;
|
|
122
|
+
return {
|
|
123
|
+
x: (tmpTransformed.x - tmpVS.PanX) / tmpVS.Zoom,
|
|
124
|
+
y: (tmpTransformed.y - tmpVS.PanY) / tmpVS.Zoom
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { x: pScreenX, y: pScreenY };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Toggle fullscreen mode on the flow editor container.
|
|
133
|
+
* Uses a CSS fixed-position overlay instead of the Fullscreen API.
|
|
134
|
+
* @returns {boolean} The new fullscreen state
|
|
135
|
+
*/
|
|
136
|
+
toggleFullscreen()
|
|
137
|
+
{
|
|
138
|
+
let tmpViewIdentifier = this._FlowView.options.ViewIdentifier;
|
|
139
|
+
let tmpContainerElements = this._FlowView.pict.ContentAssignment.getElement(`#Flow-Wrapper-${tmpViewIdentifier}`);
|
|
140
|
+
if (tmpContainerElements.length < 1) return this._IsFullscreen;
|
|
141
|
+
|
|
142
|
+
let tmpContainer = tmpContainerElements[0];
|
|
143
|
+
|
|
144
|
+
this._IsFullscreen = !this._IsFullscreen;
|
|
145
|
+
|
|
146
|
+
if (this._IsFullscreen)
|
|
147
|
+
{
|
|
148
|
+
tmpContainer.classList.add('pict-flow-fullscreen');
|
|
149
|
+
}
|
|
150
|
+
else
|
|
151
|
+
{
|
|
152
|
+
tmpContainer.classList.remove('pict-flow-fullscreen');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return this._IsFullscreen;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Exit fullscreen mode if currently active.
|
|
160
|
+
*/
|
|
161
|
+
exitFullscreen()
|
|
162
|
+
{
|
|
163
|
+
if (!this._IsFullscreen) return;
|
|
164
|
+
|
|
165
|
+
let tmpViewIdentifier = this._FlowView.options.ViewIdentifier;
|
|
166
|
+
let tmpContainerElements = this._FlowView.pict.ContentAssignment.getElement(`#Flow-Wrapper-${tmpViewIdentifier}`);
|
|
167
|
+
if (tmpContainerElements.length > 0)
|
|
168
|
+
{
|
|
169
|
+
tmpContainerElements[0].classList.remove('pict-flow-fullscreen');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this._IsFullscreen = false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = PictServiceFlowViewportManager;
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
const _DefaultConfiguration =
|
|
4
|
+
{
|
|
5
|
+
ViewIdentifier: 'Flow-FloatingToolbar',
|
|
6
|
+
|
|
7
|
+
DefaultRenderable: 'Flow-FloatingToolbar-Content',
|
|
8
|
+
DefaultDestinationAddress: '#Flow-FloatingToolbar-Container',
|
|
9
|
+
|
|
10
|
+
AutoRender: false,
|
|
11
|
+
|
|
12
|
+
FlowViewIdentifier: 'Pict-Flow',
|
|
13
|
+
|
|
14
|
+
CSS: false,
|
|
15
|
+
|
|
16
|
+
Templates:
|
|
17
|
+
[
|
|
18
|
+
{
|
|
19
|
+
Hash: 'Flow-FloatingToolbar-Template',
|
|
20
|
+
Template: /*html*/`
|
|
21
|
+
<div class="pict-flow-floating-toolbar" id="Flow-FloatingToolbar-{~D:Record.FlowViewIdentifier~}">
|
|
22
|
+
<div class="pict-flow-floating-grip" id="Flow-FloatingGrip-{~D:Record.FlowViewIdentifier~}" title="Drag to move · Double-click to collapse">
|
|
23
|
+
<span id="Flow-FloatingIcon-grip-{~D:Record.FlowViewIdentifier~}"></span>
|
|
24
|
+
</div>
|
|
25
|
+
<button class="pict-flow-floating-btn" data-flow-action="add-node" title="Add Node">
|
|
26
|
+
<span id="Flow-FloatingIcon-plus-{~D:Record.FlowViewIdentifier~}"></span>
|
|
27
|
+
</button>
|
|
28
|
+
<button class="pict-flow-floating-btn danger" data-flow-action="delete-selected" title="Delete Selected">
|
|
29
|
+
<span id="Flow-FloatingIcon-trash-{~D:Record.FlowViewIdentifier~}"></span>
|
|
30
|
+
</button>
|
|
31
|
+
<div class="pict-flow-floating-separator"></div>
|
|
32
|
+
<button class="pict-flow-floating-btn" data-flow-action="zoom-in" title="Zoom In">
|
|
33
|
+
<span id="Flow-FloatingIcon-zoom-in-{~D:Record.FlowViewIdentifier~}"></span>
|
|
34
|
+
</button>
|
|
35
|
+
<button class="pict-flow-floating-btn" data-flow-action="zoom-out" title="Zoom Out">
|
|
36
|
+
<span id="Flow-FloatingIcon-zoom-out-{~D:Record.FlowViewIdentifier~}"></span>
|
|
37
|
+
</button>
|
|
38
|
+
<button class="pict-flow-floating-btn" data-flow-action="zoom-fit" title="Fit to View">
|
|
39
|
+
<span id="Flow-FloatingIcon-zoom-fit-{~D:Record.FlowViewIdentifier~}"></span>
|
|
40
|
+
</button>
|
|
41
|
+
<div class="pict-flow-floating-separator"></div>
|
|
42
|
+
<button class="pict-flow-floating-btn" data-flow-action="auto-layout" title="Auto Layout">
|
|
43
|
+
<span id="Flow-FloatingIcon-auto-layout-{~D:Record.FlowViewIdentifier~}"></span>
|
|
44
|
+
</button>
|
|
45
|
+
<button class="pict-flow-floating-btn" data-flow-action="cards-popup" title="Cards">
|
|
46
|
+
<span id="Flow-FloatingIcon-cards-{~D:Record.FlowViewIdentifier~}"></span>
|
|
47
|
+
</button>
|
|
48
|
+
<button class="pict-flow-floating-btn" data-flow-action="layout-popup" title="Layout">
|
|
49
|
+
<span id="Flow-FloatingIcon-layout-{~D:Record.FlowViewIdentifier~}"></span>
|
|
50
|
+
</button>
|
|
51
|
+
<button class="pict-flow-floating-btn" data-flow-action="fullscreen" title="Toggle Fullscreen">
|
|
52
|
+
<span id="Flow-FloatingIcon-fullscreen-{~D:Record.FlowViewIdentifier~}"></span>
|
|
53
|
+
</button>
|
|
54
|
+
<div class="pict-flow-floating-separator"></div>
|
|
55
|
+
<button class="pict-flow-floating-btn" data-flow-action="dock-toolbar" title="Dock Toolbar">
|
|
56
|
+
<span id="Flow-FloatingIcon-dock-{~D:Record.FlowViewIdentifier~}"></span>
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
`
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
|
|
63
|
+
Renderables:
|
|
64
|
+
[
|
|
65
|
+
{
|
|
66
|
+
RenderableHash: 'Flow-FloatingToolbar-Content',
|
|
67
|
+
TemplateHash: 'Flow-FloatingToolbar-Template',
|
|
68
|
+
DestinationAddress: '#Flow-FloatingToolbar-Container',
|
|
69
|
+
RenderMethod: 'replace'
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
class PictViewFlowFloatingToolbar extends libPictView
|
|
75
|
+
{
|
|
76
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
77
|
+
{
|
|
78
|
+
let tmpOptions = Object.assign({}, JSON.parse(JSON.stringify(_DefaultConfiguration)), pOptions);
|
|
79
|
+
super(pFable, tmpOptions, pServiceHash);
|
|
80
|
+
|
|
81
|
+
this.serviceType = 'PictViewFlowFloatingToolbar';
|
|
82
|
+
|
|
83
|
+
this._ToolbarView = null;
|
|
84
|
+
this._FlowView = null;
|
|
85
|
+
|
|
86
|
+
this._IsCollapsed = false;
|
|
87
|
+
|
|
88
|
+
this._IsDragging = false;
|
|
89
|
+
this._DragStartX = 0;
|
|
90
|
+
this._DragStartY = 0;
|
|
91
|
+
this._DragStartLeft = 0;
|
|
92
|
+
this._DragStartTop = 0;
|
|
93
|
+
|
|
94
|
+
this._BoundMouseMove = null;
|
|
95
|
+
this._BoundMouseUp = null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
render(pRenderableHash, pRenderDestinationAddress, pTemplateRecordAddress)
|
|
99
|
+
{
|
|
100
|
+
return super.render(pRenderableHash, pRenderDestinationAddress, this.options);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent)
|
|
104
|
+
{
|
|
105
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
106
|
+
|
|
107
|
+
// Bind click delegation for action buttons
|
|
108
|
+
let tmpFloatingToolbar = this.pict.ContentAssignment.getElement(`#Flow-FloatingToolbar-${tmpFlowViewIdentifier}`);
|
|
109
|
+
if (tmpFloatingToolbar.length > 0)
|
|
110
|
+
{
|
|
111
|
+
tmpFloatingToolbar[0].addEventListener('click', (pEvent) =>
|
|
112
|
+
{
|
|
113
|
+
let tmpTarget = pEvent.target;
|
|
114
|
+
if (!tmpTarget) return;
|
|
115
|
+
|
|
116
|
+
let tmpButton = tmpTarget.closest('[data-flow-action]');
|
|
117
|
+
if (!tmpButton) return;
|
|
118
|
+
|
|
119
|
+
let tmpAction = tmpButton.getAttribute('data-flow-action');
|
|
120
|
+
if (tmpAction === 'dock-toolbar')
|
|
121
|
+
{
|
|
122
|
+
if (this._ToolbarView)
|
|
123
|
+
{
|
|
124
|
+
this._ToolbarView._setToolbarMode('docked');
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Delegate all other actions to the docked toolbar
|
|
130
|
+
if (this._ToolbarView)
|
|
131
|
+
{
|
|
132
|
+
this._ToolbarView._handleToolbarAction(tmpAction);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Bind drag behavior on the grip
|
|
138
|
+
let tmpGrip = this.pict.ContentAssignment.getElement(`#Flow-FloatingGrip-${tmpFlowViewIdentifier}`);
|
|
139
|
+
if (tmpGrip.length > 0)
|
|
140
|
+
{
|
|
141
|
+
tmpGrip[0].addEventListener('mousedown', (pEvent) =>
|
|
142
|
+
{
|
|
143
|
+
this._startDrag(pEvent);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Double-click grip to toggle collapsed state
|
|
147
|
+
tmpGrip[0].addEventListener('dblclick', (pEvent) =>
|
|
148
|
+
{
|
|
149
|
+
pEvent.preventDefault();
|
|
150
|
+
pEvent.stopPropagation();
|
|
151
|
+
this._toggleCollapse();
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Populate icons
|
|
156
|
+
this._populateIcons();
|
|
157
|
+
|
|
158
|
+
return super.onAfterRender(pRenderable, pRenderDestinationAddress, pRecord, pContent);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Populate SVG icons into all floating toolbar button spans.
|
|
163
|
+
*/
|
|
164
|
+
_populateIcons()
|
|
165
|
+
{
|
|
166
|
+
let tmpIconProvider = this._FlowView ? this._FlowView._IconProvider : null;
|
|
167
|
+
if (!tmpIconProvider) return;
|
|
168
|
+
|
|
169
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
170
|
+
|
|
171
|
+
let tmpIconMap =
|
|
172
|
+
{
|
|
173
|
+
'grip': 'grip',
|
|
174
|
+
'plus': 'plus',
|
|
175
|
+
'trash': 'trash',
|
|
176
|
+
'zoom-in': 'zoom-in',
|
|
177
|
+
'zoom-out': 'zoom-out',
|
|
178
|
+
'zoom-fit': 'zoom-fit',
|
|
179
|
+
'auto-layout': 'auto-layout',
|
|
180
|
+
'cards': 'cards',
|
|
181
|
+
'layout': 'layout',
|
|
182
|
+
'fullscreen': 'fullscreen',
|
|
183
|
+
'dock': 'dock'
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
let tmpKeys = Object.keys(tmpIconMap);
|
|
187
|
+
for (let i = 0; i < tmpKeys.length; i++)
|
|
188
|
+
{
|
|
189
|
+
let tmpElementId = `Flow-FloatingIcon-${tmpKeys[i]}-${tmpFlowViewIdentifier}`;
|
|
190
|
+
let tmpElements = this.pict.ContentAssignment.getElement(`#${tmpElementId}`);
|
|
191
|
+
if (tmpElements.length > 0)
|
|
192
|
+
{
|
|
193
|
+
tmpElements[0].innerHTML = tmpIconProvider.getIconSVGMarkup(tmpIconMap[tmpKeys[i]], 16);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Toggle the floating toolbar between expanded and collapsed (grip-only square) states.
|
|
200
|
+
*/
|
|
201
|
+
_toggleCollapse()
|
|
202
|
+
{
|
|
203
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
204
|
+
let tmpToolbar = this.pict.ContentAssignment.getElement(`#Flow-FloatingToolbar-${tmpFlowViewIdentifier}`);
|
|
205
|
+
if (tmpToolbar.length < 1) return;
|
|
206
|
+
|
|
207
|
+
this._IsCollapsed = !this._IsCollapsed;
|
|
208
|
+
|
|
209
|
+
if (this._IsCollapsed)
|
|
210
|
+
{
|
|
211
|
+
tmpToolbar[0].classList.add('collapsed');
|
|
212
|
+
}
|
|
213
|
+
else
|
|
214
|
+
{
|
|
215
|
+
tmpToolbar[0].classList.remove('collapsed');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Start dragging the floating toolbar.
|
|
221
|
+
* @param {MouseEvent} pEvent
|
|
222
|
+
*/
|
|
223
|
+
_startDrag(pEvent)
|
|
224
|
+
{
|
|
225
|
+
pEvent.preventDefault();
|
|
226
|
+
|
|
227
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
228
|
+
let tmpToolbar = this.pict.ContentAssignment.getElement(`#Flow-FloatingToolbar-${tmpFlowViewIdentifier}`);
|
|
229
|
+
if (tmpToolbar.length < 1) return;
|
|
230
|
+
|
|
231
|
+
let tmpEl = tmpToolbar[0];
|
|
232
|
+
this._IsDragging = true;
|
|
233
|
+
this._DragStartX = pEvent.clientX;
|
|
234
|
+
this._DragStartY = pEvent.clientY;
|
|
235
|
+
this._DragStartLeft = tmpEl.offsetLeft;
|
|
236
|
+
this._DragStartTop = tmpEl.offsetTop;
|
|
237
|
+
|
|
238
|
+
this._BoundMouseMove = (pMoveEvent) => { this._onDragMove(pMoveEvent); };
|
|
239
|
+
this._BoundMouseUp = () => { this._onDragEnd(); };
|
|
240
|
+
|
|
241
|
+
document.addEventListener('mousemove', this._BoundMouseMove);
|
|
242
|
+
document.addEventListener('mouseup', this._BoundMouseUp);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Handle drag movement.
|
|
247
|
+
* @param {MouseEvent} pEvent
|
|
248
|
+
*/
|
|
249
|
+
_onDragMove(pEvent)
|
|
250
|
+
{
|
|
251
|
+
if (!this._IsDragging) return;
|
|
252
|
+
|
|
253
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
254
|
+
let tmpToolbar = this.pict.ContentAssignment.getElement(`#Flow-FloatingToolbar-${tmpFlowViewIdentifier}`);
|
|
255
|
+
if (tmpToolbar.length < 1) return;
|
|
256
|
+
|
|
257
|
+
let tmpEl = tmpToolbar[0];
|
|
258
|
+
let tmpDeltaX = pEvent.clientX - this._DragStartX;
|
|
259
|
+
let tmpDeltaY = pEvent.clientY - this._DragStartY;
|
|
260
|
+
|
|
261
|
+
let tmpNewLeft = this._DragStartLeft + tmpDeltaX;
|
|
262
|
+
let tmpNewTop = this._DragStartTop + tmpDeltaY;
|
|
263
|
+
|
|
264
|
+
// Clamp to parent bounds
|
|
265
|
+
let tmpParent = tmpEl.parentElement;
|
|
266
|
+
if (tmpParent)
|
|
267
|
+
{
|
|
268
|
+
let tmpMaxLeft = tmpParent.clientWidth - tmpEl.offsetWidth;
|
|
269
|
+
let tmpMaxTop = tmpParent.clientHeight - tmpEl.offsetHeight;
|
|
270
|
+
tmpNewLeft = Math.max(0, Math.min(tmpNewLeft, tmpMaxLeft));
|
|
271
|
+
tmpNewTop = Math.max(0, Math.min(tmpNewTop, tmpMaxTop));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
tmpEl.style.left = tmpNewLeft + 'px';
|
|
275
|
+
tmpEl.style.top = tmpNewTop + 'px';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* End dragging.
|
|
280
|
+
*/
|
|
281
|
+
_onDragEnd()
|
|
282
|
+
{
|
|
283
|
+
this._IsDragging = false;
|
|
284
|
+
|
|
285
|
+
if (this._BoundMouseMove)
|
|
286
|
+
{
|
|
287
|
+
document.removeEventListener('mousemove', this._BoundMouseMove);
|
|
288
|
+
this._BoundMouseMove = null;
|
|
289
|
+
}
|
|
290
|
+
if (this._BoundMouseUp)
|
|
291
|
+
{
|
|
292
|
+
document.removeEventListener('mouseup', this._BoundMouseUp);
|
|
293
|
+
this._BoundMouseUp = null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Save position
|
|
297
|
+
if (this._ToolbarView)
|
|
298
|
+
{
|
|
299
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
300
|
+
let tmpToolbar = this.pict.ContentAssignment.getElement(`#Flow-FloatingToolbar-${tmpFlowViewIdentifier}`);
|
|
301
|
+
if (tmpToolbar.length > 0)
|
|
302
|
+
{
|
|
303
|
+
this._ToolbarView._FloatingPosition.X = tmpToolbar[0].offsetLeft;
|
|
304
|
+
this._ToolbarView._FloatingPosition.Y = tmpToolbar[0].offsetTop;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Show the floating toolbar at the saved position.
|
|
311
|
+
*/
|
|
312
|
+
show()
|
|
313
|
+
{
|
|
314
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
315
|
+
let tmpContainer = this.pict.ContentAssignment.getElement(`#Flow-FloatingToolbar-Container-${tmpFlowViewIdentifier}`);
|
|
316
|
+
if (tmpContainer.length > 0)
|
|
317
|
+
{
|
|
318
|
+
tmpContainer[0].style.display = 'block';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let tmpToolbar = this.pict.ContentAssignment.getElement(`#Flow-FloatingToolbar-${tmpFlowViewIdentifier}`);
|
|
322
|
+
if (tmpToolbar.length > 0 && this._ToolbarView)
|
|
323
|
+
{
|
|
324
|
+
tmpToolbar[0].style.left = this._ToolbarView._FloatingPosition.X + 'px';
|
|
325
|
+
tmpToolbar[0].style.top = this._ToolbarView._FloatingPosition.Y + 'px';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Restore expanded state when showing
|
|
329
|
+
if (this._IsCollapsed && tmpToolbar.length > 0)
|
|
330
|
+
{
|
|
331
|
+
this._IsCollapsed = false;
|
|
332
|
+
tmpToolbar[0].classList.remove('collapsed');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Hide the floating toolbar.
|
|
338
|
+
*/
|
|
339
|
+
hide()
|
|
340
|
+
{
|
|
341
|
+
let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
|
|
342
|
+
let tmpContainer = this.pict.ContentAssignment.getElement(`#Flow-FloatingToolbar-Container-${tmpFlowViewIdentifier}`);
|
|
343
|
+
if (tmpContainer.length > 0)
|
|
344
|
+
{
|
|
345
|
+
tmpContainer[0].style.display = 'none';
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
module.exports = PictViewFlowFloatingToolbar;
|
|
351
|
+
|
|
352
|
+
module.exports.default_configuration = _DefaultConfiguration;
|