pict-editor-timeline 0.0.1
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/DESIGN.md +307 -0
- package/package.json +32 -0
- package/source/Pict-Section-Timeline.js +24 -0
- package/source/providers/Pict-Provider-TimelineDragDrop.js +177 -0
- package/source/providers/Pict-Provider-TimelineOps.js +310 -0
- package/source/views/PictView-Timeline-CSS.js +247 -0
- package/source/views/PictView-Timeline-DefaultConfiguration.js +44 -0
- package/source/views/PictView-Timeline.js +329 -0
- package/test/Pict-Timeline_tests.js +531 -0
package/DESIGN.md
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# pict-editor-timeline — Design Spec
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
A standalone Pict view library that renders a visual timeline editor
|
|
6
|
+
for building multi-beat video storyboards. Users add, remove, reorder,
|
|
7
|
+
and configure "cuts" — each cut has a text prompt, a duration, and
|
|
8
|
+
optional start/end frame image slots. The editor exports a JSON array
|
|
9
|
+
that any downstream system (retold-labs, ComfyUI, a CLI tool) can
|
|
10
|
+
consume without knowing about the editor.
|
|
11
|
+
|
|
12
|
+
The library has **zero knowledge** of retold-labs, Ultravisor, model
|
|
13
|
+
weights, or video generation. It is a pure UI component for
|
|
14
|
+
assembling a sequence of annotated time segments with optional image
|
|
15
|
+
references.
|
|
16
|
+
|
|
17
|
+
## Module location
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
retold/modules/pict/pict-editor-timeline/
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Standard Pict ecosystem module alongside `pict-section-form`,
|
|
24
|
+
`pict-section-formeditor`, `pict-provider-list`, etc.
|
|
25
|
+
|
|
26
|
+
## Data model
|
|
27
|
+
|
|
28
|
+
The timeline editor operates on a single data structure: an ordered
|
|
29
|
+
array of **cuts**. Each cut is a plain object:
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
{
|
|
33
|
+
// Identity
|
|
34
|
+
id: "cut-0", // Auto-assigned, stable across reorders
|
|
35
|
+
|
|
36
|
+
// Content
|
|
37
|
+
prompt: "A woman walks through a garden at golden hour",
|
|
38
|
+
target_seconds: 3,
|
|
39
|
+
|
|
40
|
+
// Image references (opaque strings — could be file paths,
|
|
41
|
+
// data URLs, LabAsset GUIDs, or anything the host app provides)
|
|
42
|
+
start_image: "", // First frame reference
|
|
43
|
+
end_image: "", // Last frame reference (optional)
|
|
44
|
+
|
|
45
|
+
// UI state (not exported)
|
|
46
|
+
_collapsed: false,
|
|
47
|
+
_dragOver: false
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Export format
|
|
52
|
+
|
|
53
|
+
The `getStoryboard()` method returns a clean JSON array with only the
|
|
54
|
+
fields the storyboard workflow consumes:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
[
|
|
58
|
+
{
|
|
59
|
+
"prompt": "A woman walks through a garden at golden hour",
|
|
60
|
+
"target_seconds": 3,
|
|
61
|
+
"beat_image": "/path/to/garden.jpg"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"prompt": "She bends down to pick a flower",
|
|
65
|
+
"target_seconds": 2
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Mapping: `start_image` → `beat_image` (the storyboard worker's
|
|
71
|
+
field name). `end_image` is advisory — it helps the user visualize
|
|
72
|
+
the transition but doesn't affect generation today. When VACE
|
|
73
|
+
extension mode eventually supports end-frame targeting, the field
|
|
74
|
+
is ready.
|
|
75
|
+
|
|
76
|
+
### Import format
|
|
77
|
+
|
|
78
|
+
The `loadStoryboard(jsonArray)` method accepts the same format and
|
|
79
|
+
populates the timeline from it. Round-trip: export → import →
|
|
80
|
+
export produces identical JSON.
|
|
81
|
+
|
|
82
|
+
## Architecture
|
|
83
|
+
|
|
84
|
+
### File structure
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
pict-editor-timeline/
|
|
88
|
+
├── package.json
|
|
89
|
+
├── .quackage.json
|
|
90
|
+
├── source/
|
|
91
|
+
│ ├── Pict-Section-Timeline.js # Main export
|
|
92
|
+
│ ├── views/
|
|
93
|
+
│ │ ├── PictView-Timeline.js # Container view: renders the full editor
|
|
94
|
+
│ │ ├── PictView-Timeline-Cut.js # Per-cut row: prompt, duration, image slots
|
|
95
|
+
│ │ └── PictView-Timeline-Toolbar.js # Top bar: add cut, import/export, total duration
|
|
96
|
+
│ ├── providers/
|
|
97
|
+
│ │ ├── Pict-Provider-TimelineDragDrop.js # HTML5 drag-and-drop reordering
|
|
98
|
+
│ │ └── Pict-Provider-TimelineOps.js # Data mutations (add, remove, reorder, update)
|
|
99
|
+
│ └── templates/
|
|
100
|
+
│ ├── timeline-container.html
|
|
101
|
+
│ ├── timeline-cut.html
|
|
102
|
+
│ └── timeline-toolbar.html
|
|
103
|
+
├── test/
|
|
104
|
+
│ └── Pict-Timeline_tests.js
|
|
105
|
+
└── docs/
|
|
106
|
+
└── README.md
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### View hierarchy
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
PictView-Timeline (container)
|
|
113
|
+
├── PictView-Timeline-Toolbar
|
|
114
|
+
│ ├── [+ Add Cut] button
|
|
115
|
+
│ ├── [Import JSON] button
|
|
116
|
+
│ ├── [Export JSON] button
|
|
117
|
+
│ └── Total duration display (sum of all cuts' target_seconds)
|
|
118
|
+
│
|
|
119
|
+
├── PictView-Timeline-Cut (repeated per cut, vertical list)
|
|
120
|
+
│ ├── Drag handle (≡)
|
|
121
|
+
│ ├── Cut number badge (#1, #2, ...)
|
|
122
|
+
│ ├── Start frame image slot (drop zone / upload / paste)
|
|
123
|
+
│ ├── Prompt textarea
|
|
124
|
+
│ ├── Duration control (target_seconds, number input with +/- steppers)
|
|
125
|
+
│ ├── End frame image slot (drop zone / upload / paste, optional)
|
|
126
|
+
│ ├── [Duplicate] [Delete] buttons
|
|
127
|
+
│ └── Collapse/expand toggle
|
|
128
|
+
│
|
|
129
|
+
└── Visual timeline strip (bottom, read-only)
|
|
130
|
+
└── Proportional-width color blocks per cut showing relative durations
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Provider pattern
|
|
134
|
+
|
|
135
|
+
Following `pict-section-formeditor` exactly:
|
|
136
|
+
|
|
137
|
+
**PictView-Timeline** (main view):
|
|
138
|
+
- Extends `libPictView`
|
|
139
|
+
- In constructor: creates `TimelineDragDrop` and `TimelineOps`
|
|
140
|
+
providers via `this.pict.addProvider()`
|
|
141
|
+
- Stores the cut array in `this.pict.AppData.Timeline.Cuts`
|
|
142
|
+
- Renders by iterating over cuts and emitting per-cut HTML
|
|
143
|
+
- Exposes `getStoryboard()` and `loadStoryboard()` on `window`
|
|
144
|
+
|
|
145
|
+
**Pict-Provider-TimelineDragDrop**:
|
|
146
|
+
- Extends `libPictProvider`
|
|
147
|
+
- Tracks `_DragState` with source cut index
|
|
148
|
+
- Implements `onDragStart`, `onDragOver`, `onDrop`, `onDragEnd`
|
|
149
|
+
- Uses top/bottom half detection for insert-before/insert-after
|
|
150
|
+
- Calls `this._ParentTimeline.render()` after reorder
|
|
151
|
+
|
|
152
|
+
**Pict-Provider-TimelineOps**:
|
|
153
|
+
- Extends `libPictProvider`
|
|
154
|
+
- `addCut(afterIndex)` — insert a new cut with defaults
|
|
155
|
+
- `removeCut(index)` — remove and re-index
|
|
156
|
+
- `duplicateCut(index)` — deep-copy + insert after
|
|
157
|
+
- `updateCut(index, field, value)` — update a single field
|
|
158
|
+
- `moveCut(fromIndex, toIndex)` — reorder (called by drag-drop)
|
|
159
|
+
- Always calls `this._ParentTimeline.render()` after mutations
|
|
160
|
+
|
|
161
|
+
### Image slot design
|
|
162
|
+
|
|
163
|
+
Each cut has two image slots: start frame and end frame. The slots
|
|
164
|
+
are generic drop zones that support three input methods:
|
|
165
|
+
|
|
166
|
+
1. **Click to browse** — triggers a hidden `<input type="file">`
|
|
167
|
+
2. **Drag-and-drop** — accepts image files dropped from Finder/Explorer
|
|
168
|
+
3. **Paste** — accepts clipboard image data (Ctrl/Cmd+V when focused)
|
|
169
|
+
|
|
170
|
+
When an image is provided via any method, the slot:
|
|
171
|
+
- Shows a thumbnail preview (resized client-side for display)
|
|
172
|
+
- Stores the image reference in the cut data
|
|
173
|
+
|
|
174
|
+
**The image reference format depends on the host app's adapter:**
|
|
175
|
+
|
|
176
|
+
| Host | start_image / end_image value |
|
|
177
|
+
|---|---|
|
|
178
|
+
| Standalone (no adapter) | Data URL (`data:image/png;base64,...`) |
|
|
179
|
+
| retold-labs adapter | Materialized asset path (`/path/to/materialized/GUID/file.jpg`) |
|
|
180
|
+
| CLI tool | Filesystem path (`./images/garden.jpg`) |
|
|
181
|
+
|
|
182
|
+
The timeline editor ships a default adapter that uses data URLs. The
|
|
183
|
+
host app overrides this by setting `options.ImageAdapter` to an object
|
|
184
|
+
with:
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
{
|
|
188
|
+
// Called when user drops/selects/pastes an image
|
|
189
|
+
onImageProvided: async (file, cutIndex, slot) => {
|
|
190
|
+
// Upload to storage, return a reference string
|
|
191
|
+
return "/path/to/stored/image.jpg";
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
// Called to render a thumbnail from a reference string
|
|
195
|
+
getThumbnailUrl: (reference) => {
|
|
196
|
+
// Return a URL the browser can display in an <img> tag
|
|
197
|
+
return reference; // file paths, data URLs, and http URLs all work
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### CSS approach
|
|
203
|
+
|
|
204
|
+
Dark theme by default (matching retold-labs). All classes prefixed
|
|
205
|
+
with `pet-` (Pict Editor Timeline). CSS injected via the PictView
|
|
206
|
+
`CSS` option so it's included in the Pict CSS cascade without
|
|
207
|
+
external stylesheets.
|
|
208
|
+
|
|
209
|
+
Key visual elements:
|
|
210
|
+
- Timeline container: vertical stack of cut cards
|
|
211
|
+
- Cut card: horizontal layout with drag handle | image | prompt | duration | image
|
|
212
|
+
- Image slots: dashed-border drop zones, thumbnail preview when populated
|
|
213
|
+
- Duration strip: horizontal bar at the bottom, each cut proportional to its duration
|
|
214
|
+
- Drag feedback: ghost opacity on drag source, insertion indicator on target
|
|
215
|
+
|
|
216
|
+
### Default configuration
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
module.exports = {
|
|
220
|
+
ViewIdentifier: 'Pict-Editor-Timeline',
|
|
221
|
+
DefaultRenderable: 'Timeline-Container',
|
|
222
|
+
DefaultDestinationAddress: '#PictEditorTimeline',
|
|
223
|
+
AutoInitialize: false,
|
|
224
|
+
AutoRender: false,
|
|
225
|
+
CSS: '/* ... */',
|
|
226
|
+
CSSHash: 'View-Editor-Timeline',
|
|
227
|
+
CSSPriority: 500,
|
|
228
|
+
DefaultCut: {
|
|
229
|
+
prompt: '',
|
|
230
|
+
target_seconds: 2,
|
|
231
|
+
start_image: '',
|
|
232
|
+
end_image: ''
|
|
233
|
+
},
|
|
234
|
+
MaxCuts: 50,
|
|
235
|
+
MinTargetSeconds: 0.5,
|
|
236
|
+
MaxTargetSeconds: 30,
|
|
237
|
+
ImageAdapter: null // null = use built-in data-URL adapter
|
|
238
|
+
};
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## retold-labs integration (separate from pict-editor-timeline)
|
|
242
|
+
|
|
243
|
+
When retold-labs imports the timeline editor, it provides:
|
|
244
|
+
|
|
245
|
+
1. An `ImageAdapter` that wires image slots to the existing
|
|
246
|
+
asset-picker widget (upload → Parime → materialized path)
|
|
247
|
+
2. A "Generate" button in the toolbar that serializes the timeline
|
|
248
|
+
to the storyboard format and POSTs it to the storyboard API
|
|
249
|
+
3. A route (`#/timeline`) and nav item ("Timeline") in the sidebar
|
|
250
|
+
|
|
251
|
+
This integration code lives in retold-labs, NOT in
|
|
252
|
+
pict-editor-timeline. The library stays decoupled.
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
// In Pict-Application-RetoldLabs.js:
|
|
256
|
+
const libPictEditorTimeline = require('pict-editor-timeline');
|
|
257
|
+
|
|
258
|
+
this.pict.addView('Timeline-Editor', Object.assign(
|
|
259
|
+
{},
|
|
260
|
+
libPictEditorTimeline.default_configuration,
|
|
261
|
+
{
|
|
262
|
+
DefaultDestinationAddress: '#RetoldLabs-View-Timeline',
|
|
263
|
+
ImageAdapter: {
|
|
264
|
+
onImageProvided: async (file, cutIndex, slot) => {
|
|
265
|
+
// Upload to Parime, return materialized path
|
|
266
|
+
},
|
|
267
|
+
getThumbnailUrl: (ref) => ref
|
|
268
|
+
}
|
|
269
|
+
}),
|
|
270
|
+
libPictEditorTimeline);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Build and test
|
|
274
|
+
|
|
275
|
+
### Build
|
|
276
|
+
```bash
|
|
277
|
+
npx quack build
|
|
278
|
+
```
|
|
279
|
+
Produces `dist/pict-editor-timeline.js` (UMD bundle) for direct
|
|
280
|
+
`<script>` inclusion, plus the npm-requireable source tree.
|
|
281
|
+
|
|
282
|
+
### Test
|
|
283
|
+
```bash
|
|
284
|
+
npm test
|
|
285
|
+
```
|
|
286
|
+
Mocha TDD tests covering:
|
|
287
|
+
- Data model: add/remove/reorder/duplicate/update cuts
|
|
288
|
+
- Export: getStoryboard() produces correct JSON
|
|
289
|
+
- Import: loadStoryboard() round-trips cleanly
|
|
290
|
+
- Duration math: total duration updates on add/remove/change
|
|
291
|
+
- Validation: prompt required, target_seconds in range
|
|
292
|
+
|
|
293
|
+
No browser tests in the library itself — the visual testing happens
|
|
294
|
+
in retold-labs' browser integration suite after integration.
|
|
295
|
+
|
|
296
|
+
## Implementation order
|
|
297
|
+
|
|
298
|
+
1. **Scaffold**: package.json, .quackage.json, directory structure
|
|
299
|
+
2. **Data model + ops provider**: cut CRUD, reorder, export/import
|
|
300
|
+
3. **Unit tests**: data model round-trip, duration math
|
|
301
|
+
4. **Container view + toolbar**: render shell, add/export buttons
|
|
302
|
+
5. **Cut view**: prompt textarea, duration control, image slots
|
|
303
|
+
6. **Drag-and-drop provider**: reorder cuts by dragging
|
|
304
|
+
7. **Image adapter**: default data-URL adapter, slot thumbnails
|
|
305
|
+
8. **Duration strip**: proportional-width visual timeline at bottom
|
|
306
|
+
9. **CSS**: dark theme, drag feedback, responsive layout
|
|
307
|
+
10. **retold-labs integration**: ImageAdapter, route, nav item, Generate button
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pict-editor-timeline",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Pict visual timeline editor for building multi-beat video storyboards. Add, reorder, and configure cuts with text prompts, durations, and optional start/end frame image slots. Exports clean JSON consumable by any downstream video generation system.",
|
|
5
|
+
"main": "source/Pict-Section-Timeline.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node source/Pict-Section-Timeline.js",
|
|
8
|
+
"test": "npx mocha -u tdd --exit --timeout 5000 test/Pict-Timeline_tests.js",
|
|
9
|
+
"tests": "npx mocha -u tdd --exit --timeout 5000 -g",
|
|
10
|
+
"coverage": "npx nyc --reporter=lcov --reporter=text npm test",
|
|
11
|
+
"build": "npx quack build"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/stevenvelozo/pict-editor-timeline.git"
|
|
16
|
+
},
|
|
17
|
+
"author": "steven velozo <steven@velozo.com>",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/stevenvelozo/pict-editor-timeline/issues"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/stevenvelozo/pict-editor-timeline#readme",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"pict-view": "^1.0.68",
|
|
25
|
+
"pict-provider": "^1.0.12"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"fable": "^3.0.0",
|
|
29
|
+
"mocha": "^10.2.0",
|
|
30
|
+
"quackage": "^1.1.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pict-editor-timeline — Main Export
|
|
3
|
+
*
|
|
4
|
+
* Visual timeline editor for building multi-beat video storyboards.
|
|
5
|
+
* Exports the PictView-Timeline view class as the default export,
|
|
6
|
+
* plus individual provider classes for advanced usage.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const libTimeline = require('pict-editor-timeline');
|
|
10
|
+
* pict.addView('MyTimeline', libTimeline.default_configuration, libTimeline);
|
|
11
|
+
*
|
|
12
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
13
|
+
* @license MIT
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const libPictViewTimeline = require('./views/PictView-Timeline.js');
|
|
17
|
+
|
|
18
|
+
// Default export: the main view class
|
|
19
|
+
module.exports = libPictViewTimeline;
|
|
20
|
+
|
|
21
|
+
// Re-export configuration and provider classes for advanced usage
|
|
22
|
+
module.exports.default_configuration = libPictViewTimeline.default_configuration;
|
|
23
|
+
module.exports.TimelineOpsProvider = require('./providers/Pict-Provider-TimelineOps.js');
|
|
24
|
+
module.exports.TimelineDragDropProvider = require('./providers/Pict-Provider-TimelineDragDrop.js');
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pict Provider: Timeline Drag and Drop
|
|
3
|
+
*
|
|
4
|
+
* Handles HTML5 drag-and-drop reordering of cuts in the timeline.
|
|
5
|
+
* Follows the same pattern as pict-section-formeditor's
|
|
6
|
+
* Pict-Provider-FormEditorDragDrop: tracks drag state, detects
|
|
7
|
+
* insert position (before/after based on cursor Y), and delegates
|
|
8
|
+
* the actual array mutation to TimelineOps.moveCut().
|
|
9
|
+
*
|
|
10
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
11
|
+
* @license MIT
|
|
12
|
+
*/
|
|
13
|
+
const libPictProvider = require('pict-provider');
|
|
14
|
+
|
|
15
|
+
class PictProviderTimelineDragDrop extends libPictProvider
|
|
16
|
+
{
|
|
17
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
18
|
+
{
|
|
19
|
+
super(pFable, pOptions, pServiceHash);
|
|
20
|
+
this.serviceType = 'PictProviderTimelineDragDrop';
|
|
21
|
+
|
|
22
|
+
// Set by parent view after construction
|
|
23
|
+
this._ParentTimeline = null;
|
|
24
|
+
|
|
25
|
+
// Current drag state
|
|
26
|
+
this._DragState =
|
|
27
|
+
{
|
|
28
|
+
Active: false,
|
|
29
|
+
SourceIndex: -1,
|
|
30
|
+
TargetIndex: -1,
|
|
31
|
+
InsertPosition: 'after' // 'before' | 'after'
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Called from ondragstart on a cut's drag handle.
|
|
37
|
+
*/
|
|
38
|
+
onDragStart(pEvent, pCutIndex)
|
|
39
|
+
{
|
|
40
|
+
this._DragState.Active = true;
|
|
41
|
+
this._DragState.SourceIndex = pCutIndex;
|
|
42
|
+
|
|
43
|
+
if (pEvent && pEvent.dataTransfer)
|
|
44
|
+
{
|
|
45
|
+
pEvent.dataTransfer.effectAllowed = 'move';
|
|
46
|
+
// Required for Firefox
|
|
47
|
+
pEvent.dataTransfer.setData('text/plain', String(pCutIndex));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (pEvent && pEvent.currentTarget)
|
|
51
|
+
{
|
|
52
|
+
pEvent.currentTarget.classList.add('pet-dragging');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Called from ondragover on each cut row. Detects whether the
|
|
58
|
+
* cursor is in the top or bottom half to determine insert
|
|
59
|
+
* position.
|
|
60
|
+
*/
|
|
61
|
+
onDragOver(pEvent, pCutIndex)
|
|
62
|
+
{
|
|
63
|
+
if (!this._DragState.Active)
|
|
64
|
+
{
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (pEvent)
|
|
69
|
+
{
|
|
70
|
+
pEvent.preventDefault();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this._DragState.TargetIndex = pCutIndex;
|
|
74
|
+
|
|
75
|
+
// Detect top/bottom half for insert indicator
|
|
76
|
+
if (pEvent && pEvent.currentTarget)
|
|
77
|
+
{
|
|
78
|
+
let tmpRect = pEvent.currentTarget.getBoundingClientRect();
|
|
79
|
+
let tmpMidpoint = tmpRect.top + (tmpRect.height / 2);
|
|
80
|
+
this._DragState.InsertPosition = pEvent.clientY < tmpMidpoint ? 'before' : 'after';
|
|
81
|
+
|
|
82
|
+
// Visual feedback
|
|
83
|
+
pEvent.currentTarget.classList.remove('pet-drag-insert-before', 'pet-drag-insert-after');
|
|
84
|
+
pEvent.currentTarget.classList.add(
|
|
85
|
+
this._DragState.InsertPosition === 'before'
|
|
86
|
+
? 'pet-drag-insert-before'
|
|
87
|
+
: 'pet-drag-insert-after');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Called from ondragleave on each cut row.
|
|
93
|
+
*/
|
|
94
|
+
onDragLeave(pEvent, pCutIndex)
|
|
95
|
+
{
|
|
96
|
+
if (pEvent && pEvent.currentTarget)
|
|
97
|
+
{
|
|
98
|
+
pEvent.currentTarget.classList.remove(
|
|
99
|
+
'pet-drag-insert-before',
|
|
100
|
+
'pet-drag-insert-after');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Called from ondrop on each cut row.
|
|
106
|
+
*/
|
|
107
|
+
onDrop(pEvent, pCutIndex)
|
|
108
|
+
{
|
|
109
|
+
if (pEvent)
|
|
110
|
+
{
|
|
111
|
+
pEvent.preventDefault();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!this._DragState.Active)
|
|
115
|
+
{
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let tmpFrom = this._DragState.SourceIndex;
|
|
120
|
+
let tmpTo = pCutIndex;
|
|
121
|
+
|
|
122
|
+
// Adjust for insert position
|
|
123
|
+
if (this._DragState.InsertPosition === 'after')
|
|
124
|
+
{
|
|
125
|
+
tmpTo = tmpTo + 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Adjust if moving downward (source removal shifts indices)
|
|
129
|
+
if (tmpFrom < tmpTo)
|
|
130
|
+
{
|
|
131
|
+
tmpTo = tmpTo - 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (this._ParentTimeline && this._ParentTimeline._TimelineOps)
|
|
135
|
+
{
|
|
136
|
+
this._ParentTimeline._TimelineOps.moveCut(tmpFrom, tmpTo);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this._resetDragState();
|
|
140
|
+
|
|
141
|
+
// Re-render the timeline
|
|
142
|
+
if (this._ParentTimeline)
|
|
143
|
+
{
|
|
144
|
+
this._ParentTimeline.render();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Called from ondragend on the drag handle (fires even if drop
|
|
150
|
+
* was cancelled).
|
|
151
|
+
*/
|
|
152
|
+
onDragEnd(pEvent)
|
|
153
|
+
{
|
|
154
|
+
this._resetDragState();
|
|
155
|
+
|
|
156
|
+
// Clean up any lingering CSS classes
|
|
157
|
+
if (typeof document !== 'undefined')
|
|
158
|
+
{
|
|
159
|
+
let tmpEls = document.querySelectorAll('.pet-dragging, .pet-drag-insert-before, .pet-drag-insert-after');
|
|
160
|
+
for (let i = 0; i < tmpEls.length; i++)
|
|
161
|
+
{
|
|
162
|
+
tmpEls[i].classList.remove('pet-dragging', 'pet-drag-insert-before', 'pet-drag-insert-after');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
_resetDragState()
|
|
168
|
+
{
|
|
169
|
+
this._DragState.Active = false;
|
|
170
|
+
this._DragState.SourceIndex = -1;
|
|
171
|
+
this._DragState.TargetIndex = -1;
|
|
172
|
+
this._DragState.InsertPosition = 'after';
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = PictProviderTimelineDragDrop;
|
|
177
|
+
module.exports.default_configuration = {};
|