@arcanewizards/timecode-toolbox 0.1.0 → 0.1.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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/dist/components/frontend/index.js +865 -272
  3. package/dist/components/frontend/index.mjs +855 -262
  4. package/dist/entrypoint.css +163 -53
  5. package/dist/entrypoint.js +1474 -493
  6. package/dist/entrypoint.js.map +4 -4
  7. package/dist/frontend.js +1474 -493
  8. package/dist/frontend.js.map +4 -4
  9. package/dist/index.d.mts +3 -1
  10. package/dist/index.d.ts +3 -1
  11. package/dist/index.js +308 -37
  12. package/dist/index.mjs +329 -49
  13. package/dist/start.d.mts +1 -2
  14. package/dist/start.d.ts +1 -2
  15. package/dist/start.js +311 -38
  16. package/dist/start.mjs +332 -50
  17. package/package.json +12 -6
  18. package/.turbo/turbo-build.log +0 -58
  19. package/.turbo/turbo-lint.log +0 -4
  20. package/CHANGELOG.md +0 -40
  21. package/eslint.config.mjs +0 -49
  22. package/src/app.tsx +0 -147
  23. package/src/components/backend/index.ts +0 -6
  24. package/src/components/backend/toolbox-root.ts +0 -119
  25. package/src/components/frontend/constants.ts +0 -81
  26. package/src/components/frontend/entrypoint.ts +0 -12
  27. package/src/components/frontend/frontend.css +0 -108
  28. package/src/components/frontend/index.tsx +0 -46
  29. package/src/components/frontend/toolbox/content.tsx +0 -45
  30. package/src/components/frontend/toolbox/context.tsx +0 -63
  31. package/src/components/frontend/toolbox/core/size-aware-div.tsx +0 -51
  32. package/src/components/frontend/toolbox/core/timecode-display.tsx +0 -592
  33. package/src/components/frontend/toolbox/generators.tsx +0 -318
  34. package/src/components/frontend/toolbox/inputs.tsx +0 -484
  35. package/src/components/frontend/toolbox/outputs.tsx +0 -581
  36. package/src/components/frontend/toolbox/preferences.ts +0 -25
  37. package/src/components/frontend/toolbox/root.tsx +0 -335
  38. package/src/components/frontend/toolbox/settings.tsx +0 -54
  39. package/src/components/frontend/toolbox/types.ts +0 -28
  40. package/src/components/frontend/toolbox/util.tsx +0 -61
  41. package/src/components/proto.ts +0 -420
  42. package/src/config.ts +0 -7
  43. package/src/generators/clock.tsx +0 -206
  44. package/src/generators/index.tsx +0 -15
  45. package/src/index.ts +0 -38
  46. package/src/inputs/artnet.tsx +0 -305
  47. package/src/inputs/index.tsx +0 -13
  48. package/src/inputs/tcnet.tsx +0 -272
  49. package/src/outputs/artnet.tsx +0 -170
  50. package/src/outputs/index.tsx +0 -11
  51. package/src/start.ts +0 -47
  52. package/src/tree.ts +0 -133
  53. package/src/types.ts +0 -12
  54. package/src/urls.ts +0 -49
  55. package/src/util.ts +0 -82
  56. package/tailwind.config.cjs +0 -7
  57. package/tsconfig.json +0 -10
  58. package/tsup.config.ts +0 -10
@@ -1,581 +0,0 @@
1
- import {
2
- Dispatch,
3
- FC,
4
- SetStateAction,
5
- useCallback,
6
- useContext,
7
- useEffect,
8
- useMemo,
9
- useState,
10
- } from 'react';
11
- import { STRINGS } from '../constants';
12
- import { PrimaryToolboxSection } from './util';
13
- import { DialogMode, SettingsProps } from './types';
14
- import {
15
- ControlButton,
16
- ControlColorSelect,
17
- ControlDialog,
18
- ControlDialogButtons,
19
- ControlInput,
20
- ControlLabel,
21
- ControlSelect,
22
- } from '@arcanewizards/sigil/frontend/controls';
23
- import { ConfigContext, NetworkContext, useApplicationState } from './context';
24
- import {
25
- OutputConfig,
26
- OutputDefinition,
27
- TimecodeInstance,
28
- ToolboxRootGetNetworkInterfacesReturn,
29
- } from '../../proto';
30
- import { TimecodeTreeDisplay } from './core/timecode-display';
31
- import { Icon } from '@arcanejs/toolkit-frontend/components/core';
32
- import {
33
- ChangeCommitContext,
34
- useChangeCommitBoundary,
35
- } from '@arcanewizards/sigil/frontend/context';
36
- import { v4 as uuidv4 } from 'uuid';
37
- import { cn } from '@arcanejs/toolkit-frontend/util';
38
- import { ARTNET_PORT, TimecodeMode } from '@arcanewizards/artnet/constants';
39
- import { SizeAwareDiv } from './core/size-aware-div';
40
- import { TooltipWrapper } from '@arcanewizards/sigil/frontend/tooltip';
41
- import {
42
- cssSigilColorUsageVariables,
43
- sigilColorUsage,
44
- } from '@arcanewizards/sigil/frontend/styling';
45
- import {
46
- augmentUpstreamTimecodeWithOutputMetadata,
47
- getTimecodeInstance,
48
- } from '../../../util';
49
- import { NoToolboxChildren } from './content';
50
-
51
- const DmxConnectionSettings: FC<SettingsProps<OutputDefinition>> = ({
52
- data,
53
- updateSettings,
54
- }) => {
55
- const { commitChanges } = useContext(ChangeCommitContext);
56
- const { getNetworkInterfaces } = useContext(NetworkContext);
57
- const [interfaces, setInterfaces] =
58
- useState<ToolboxRootGetNetworkInterfacesReturn | null>(null);
59
-
60
- const refreshInterfaces = useCallback(() => {
61
- setInterfaces(null);
62
- getNetworkInterfaces().then((ifs) => setInterfaces(ifs));
63
- }, [getNetworkInterfaces]);
64
-
65
- useEffect(() => {
66
- refreshInterfaces();
67
- }, [refreshInterfaces]);
68
-
69
- if (data.type !== 'artnet') {
70
- return null;
71
- }
72
-
73
- return (
74
- <>
75
- <ControlLabel>Target Type</ControlLabel>
76
- <ControlSelect
77
- value={data.target.type}
78
- options={[
79
- {
80
- value: 'interface',
81
- label: 'Broadcast',
82
- },
83
- {
84
- value: 'host',
85
- label: 'IP Address / Hostname',
86
- },
87
- ]}
88
- position="both"
89
- variant="large"
90
- onChange={(type) => {
91
- updateSettings((current) => ({
92
- ...current,
93
- target:
94
- type === 'interface'
95
- ? {
96
- type: 'interface',
97
- interface: '',
98
- }
99
- : {
100
- type: 'host',
101
- host: 'localhost',
102
- },
103
- }));
104
- }}
105
- />
106
- {data.target.type === 'interface' ? (
107
- <>
108
- <ControlLabel>Interface</ControlLabel>
109
- <ControlButton
110
- onClick={refreshInterfaces}
111
- title="Refresh Interfaces"
112
- position="first"
113
- variant="large"
114
- >
115
- <Icon icon="refresh" className="text-arcane-normal" />
116
- </ControlButton>
117
- <ControlSelect
118
- value={data.target.interface ?? null}
119
- options={
120
- !interfaces
121
- ? []
122
- : Object.values(interfaces).map((iface) => ({
123
- label: `${iface.name} (${iface.address})`,
124
- value: iface.name,
125
- }))
126
- }
127
- placeholder="No Interface Selected"
128
- onChange={(value) => {
129
- updateSettings((current) => ({
130
- ...current,
131
- target: {
132
- type: 'interface',
133
- interface: value ?? '',
134
- },
135
- }));
136
- }}
137
- position="second"
138
- variant="large"
139
- triggerClassName={cn('text-sigil-control')}
140
- />
141
- </>
142
- ) : (
143
- <>
144
- <ControlLabel>Hostname / IP</ControlLabel>
145
- <ControlInput
146
- position="both"
147
- type="string"
148
- value={data.target.host ?? ''}
149
- onChange={(value) => {
150
- updateSettings((current) => ({
151
- ...current,
152
- target: {
153
- type: 'host',
154
- host: value,
155
- },
156
- }));
157
- }}
158
- />
159
- </>
160
- )}
161
-
162
- <ControlLabel>Port</ControlLabel>
163
- <ControlInput
164
- position="both"
165
- type="string"
166
- value={data.target.port?.toString() ?? ''}
167
- placeholder={`Default (${ARTNET_PORT})`}
168
- onChange={(value, enterPressed) => {
169
- const port = value ? parseInt(value, 10) : undefined;
170
- if (port !== undefined && isNaN(port)) {
171
- return;
172
- }
173
- updateSettings((current) => ({
174
- ...current,
175
- target: {
176
- ...current.target,
177
- port,
178
- },
179
- }));
180
- if (enterPressed) {
181
- commitChanges();
182
- }
183
- }}
184
- />
185
- <ControlLabel>FPS</ControlLabel>
186
- <ControlSelect<TimecodeMode>
187
- position="both"
188
- variant="large"
189
- value={data.mode}
190
- options={(
191
- Object.entries(STRINGS.smtpeModeOptions) as [TimecodeMode, string][]
192
- ).map(([mode, label]) => ({
193
- label,
194
- value: mode,
195
- }))}
196
- onChange={(mode) => {
197
- updateSettings((current) => ({ ...current, mode }));
198
- }}
199
- />
200
- </>
201
- );
202
- };
203
-
204
- type OutputSettingsDialogProps = {
205
- target: DialogMode['target'];
206
- output: OutputDefinition['type'];
207
- close: () => void;
208
- };
209
-
210
- export const OutputSettingsDialog: FC<OutputSettingsDialogProps> = ({
211
- target,
212
- output,
213
- close,
214
- }) => {
215
- const { config, updateConfig } = useContext(ConfigContext);
216
- const [newData, setNewData] = useState<OutputConfig>({
217
- name: '',
218
- enabled: true,
219
- definition: {
220
- type: 'artnet',
221
- target: {
222
- type: 'host',
223
- host: 'localhost',
224
- },
225
- mode: 'SMPTE',
226
- },
227
- link: null,
228
- });
229
-
230
- const updateSettings: SettingsProps<OutputConfig>['updateSettings'] =
231
- useCallback(
232
- (change) => {
233
- if (target.type === 'add') {
234
- setNewData(change);
235
- } else {
236
- updateConfig((current) => {
237
- const existing = current.outputs?.[target.uuid];
238
- if (!existing) {
239
- return current;
240
- }
241
- return {
242
- ...current,
243
- outputs: {
244
- ...current.outputs,
245
- [target.uuid]: change(existing),
246
- },
247
- };
248
- });
249
- }
250
- },
251
- [target, updateConfig],
252
- );
253
-
254
- const updateDefinition = useCallback(
255
- (change: (current: OutputDefinition) => OutputDefinition) => {
256
- updateSettings((current) => ({
257
- ...current,
258
- definition: change(current.definition),
259
- }));
260
- },
261
- [updateSettings],
262
- );
263
-
264
- const addOutput = useCallback(() => {
265
- updateConfig((current) => {
266
- return {
267
- ...current,
268
- outputs: {
269
- ...current.outputs,
270
- [uuidv4()]: newData,
271
- },
272
- };
273
- });
274
- close();
275
- }, [newData, close, updateConfig]);
276
-
277
- const resolvedTarget =
278
- target.type === 'add' ? 'add' : config.outputs?.[target.uuid];
279
-
280
- const data = resolvedTarget === 'add' ? newData : resolvedTarget;
281
-
282
- const commitChanges = useCallback(() => {
283
- if (target.type === 'add') {
284
- addOutput();
285
- } else {
286
- close();
287
- }
288
- }, [target, addOutput, close]);
289
-
290
- const commitBoundary = useChangeCommitBoundary(data, commitChanges);
291
-
292
- if (!data) {
293
- return null;
294
- }
295
-
296
- return (
297
- <ChangeCommitContext.Provider value={commitBoundary}>
298
- <ControlDialog
299
- dialogClosed={close}
300
- title={
301
- target.type === 'add'
302
- ? STRINGS.outputs.addDialog(STRINGS.protocols[output].short)
303
- : STRINGS.outputs.editDialog(
304
- STRINGS.protocols[output].short,
305
- data.name || '',
306
- )
307
- }
308
- >
309
- <ControlLabel>Name</ControlLabel>
310
- <ControlInput
311
- position="both"
312
- type="string"
313
- value={data.name ?? ''}
314
- placeholder={`No name specified`}
315
- onChange={(name, enterPressed) => {
316
- if (enterPressed) {
317
- commitBoundary.commitChanges();
318
- }
319
- updateSettings((current) => ({
320
- ...current,
321
- name,
322
- }));
323
- }}
324
- />
325
- <ControlLabel>Color</ControlLabel>
326
- <ControlColorSelect
327
- position="both"
328
- color={data.color ?? ''}
329
- variant="standard"
330
- placeholder="Default"
331
- onChange={(color) => {
332
- updateSettings((current) => ({
333
- ...current,
334
- color,
335
- }));
336
- }}
337
- />
338
- {data.definition.type === 'artnet' ? (
339
- <DmxConnectionSettings
340
- data={data.definition}
341
- updateSettings={updateDefinition}
342
- />
343
- ) : null}
344
- <ControlLabel>Delay (ms)</ControlLabel>
345
- <ControlInput
346
- position="both"
347
- type="string"
348
- value={data.delayMs?.toString() ?? ''}
349
- placeholder={`Default (0ms)`}
350
- onChange={(value, enterPressed) => {
351
- const delayMs = value ? parseInt(value, 10) : undefined;
352
- if (delayMs !== undefined && isNaN(delayMs)) {
353
- return;
354
- }
355
- updateSettings((current) => ({
356
- ...current,
357
- delayMs,
358
- }));
359
- if (enterPressed) {
360
- commitChanges();
361
- }
362
- }}
363
- />
364
- {resolvedTarget === 'add' ? (
365
- <ControlDialogButtons>
366
- <ControlButton onClick={close} variant="large">
367
- Cancel
368
- </ControlButton>
369
- <ControlButton onClick={addOutput} variant="large">
370
- Add Output
371
- </ControlButton>
372
- </ControlDialogButtons>
373
- ) : (
374
- <ControlDialogButtons>
375
- <ControlButton onClick={close} variant="large">
376
- Close
377
- </ControlButton>
378
- </ControlDialogButtons>
379
- )}
380
- </ControlDialog>
381
- </ChangeCommitContext.Provider>
382
- );
383
- };
384
-
385
- type OutputDisplayProps = {
386
- uuid: string;
387
- config: OutputConfig;
388
- setDialogMode: (mode: DialogMode | null) => void;
389
- assignToOutput: string | null;
390
- setAssignToOutput: Dispatch<SetStateAction<string | null>>;
391
- };
392
-
393
- const OutputDisplay: FC<OutputDisplayProps> = ({
394
- uuid,
395
- config,
396
- setDialogMode,
397
- assignToOutput,
398
- setAssignToOutput,
399
- }) => {
400
- const applicationState = useApplicationState();
401
- const { updateConfig } = useContext(ConfigContext);
402
- const clearLink = useCallback(() => {
403
- updateConfig((current) => {
404
- const currentOutput = current.outputs?.[uuid];
405
- if (!currentOutput) {
406
- return current;
407
- }
408
- return {
409
- ...current,
410
- outputs: {
411
- ...current.outputs,
412
- [uuid]: {
413
- ...currentOutput,
414
- link: null,
415
- },
416
- },
417
- };
418
- });
419
- setAssignToOutput(null);
420
- }, [updateConfig, uuid, setAssignToOutput]);
421
-
422
- const linkCallback = useCallback(
423
- () => setAssignToOutput((current) => (current === uuid ? null : uuid)),
424
- [uuid, setAssignToOutput],
425
- );
426
-
427
- const timecode: TimecodeInstance = useMemo(() => {
428
- const tc =
429
- config.link && getTimecodeInstance(applicationState, config.link);
430
- return augmentUpstreamTimecodeWithOutputMetadata(tc, config);
431
- }, [applicationState, config]);
432
-
433
- const toggleEnabled = useCallback(() => {
434
- updateConfig((current) => {
435
- const existing = current.outputs?.[uuid];
436
- if (!existing) {
437
- return current;
438
- }
439
- return {
440
- ...current,
441
- outputs: {
442
- ...current.outputs,
443
- [uuid]: {
444
- ...existing,
445
- enabled: !existing.enabled,
446
- },
447
- },
448
- };
449
- });
450
- }, [uuid, updateConfig]);
451
-
452
- return (
453
- <div
454
- className="relative flex flex-col"
455
- style={
456
- config.color &&
457
- cssSigilColorUsageVariables(
458
- 'timecode-usage',
459
- sigilColorUsage(config.color),
460
- )
461
- }
462
- >
463
- <TimecodeTreeDisplay
464
- id={['output', uuid]}
465
- config={{ delayMs: config.delayMs ?? null }}
466
- assignToOutput={null}
467
- type={STRINGS.protocols[config.definition.type].short}
468
- name={config.name ? [config.name] : []}
469
- color={config.color}
470
- timecode={config.enabled ? timecode : null}
471
- namePlaceholder={`Unnamed Output`}
472
- buttons={
473
- <>
474
- <ControlButton
475
- variant="large"
476
- title={config.enabled ? 'Stop Input' : 'Start Input'}
477
- onClick={toggleEnabled}
478
- icon={config.enabled ? 'stop' : 'play_arrow'}
479
- />
480
- <ControlButton
481
- variant="large"
482
- title="Link Output"
483
- active={assignToOutput === uuid}
484
- onClick={linkCallback}
485
- icon={config.link ? 'link' : 'link_off'}
486
- />
487
- <ControlButton
488
- variant="large"
489
- title="Edit Output"
490
- onClick={() =>
491
- setDialogMode({
492
- section: { type: 'outputs', output: config.definition.type },
493
- target: { type: 'edit', uuid },
494
- })
495
- }
496
- icon="edit"
497
- />
498
- </>
499
- }
500
- />
501
- {assignToOutput === uuid && config.link && (
502
- <SizeAwareDiv className="absolute inset-0" onClick={clearLink}>
503
- <TooltipWrapper tooltip="Unlink from Input / Generator">
504
- <div
505
- className="
506
- flex size-full cursor-pointer flex-col items-center
507
- justify-center gap-0.5 bg-timecode-backdrop
508
- text-timecode-usage-text
509
- hover:bg-timecode-backdrop-hover
510
- "
511
- >
512
- <Icon icon="link_off" className="text-block-icon" />
513
- <p>Press ESC to cancel</p>
514
- </div>
515
- </TooltipWrapper>
516
- </SizeAwareDiv>
517
- )}
518
- </div>
519
- );
520
- };
521
-
522
- export type OutputSectionProps = {
523
- setDialogMode: (mode: DialogMode | null) => void;
524
- assignToOutput: string | null;
525
- setAssignToOutput: Dispatch<SetStateAction<string | null>>;
526
- };
527
-
528
- export const OutputsSection: FC<OutputSectionProps> = ({
529
- setDialogMode,
530
- assignToOutput,
531
- setAssignToOutput,
532
- }) => {
533
- const { config } = useContext(ConfigContext);
534
- return (
535
- <PrimaryToolboxSection
536
- title={STRINGS.outputs.title}
537
- buttons={
538
- <>
539
- {(['artnet'] as const).map((type) => (
540
- <ControlButton
541
- key={type}
542
- onClick={() =>
543
- setDialogMode({
544
- section: { type: 'outputs', output: type },
545
- target: { type: 'add' },
546
- })
547
- }
548
- variant="toolbar"
549
- icon="add"
550
- >
551
- {STRINGS.outputs.addButton(STRINGS.protocols[type].long)}
552
- </ControlButton>
553
- ))}
554
- </>
555
- }
556
- >
557
- {Object.entries(config.outputs ?? {}).length === 0 ? (
558
- <NoToolboxChildren text={STRINGS.outputs.noChildren} />
559
- ) : (
560
- <div
561
- className="
562
- grid grow grid-cols-1 gap-px
563
- min-[600px]:grid-cols-2
564
- min-[900px]:grid-cols-3
565
- "
566
- >
567
- {Object.entries(config.outputs).map(([uuid, output]) => (
568
- <OutputDisplay
569
- key={uuid}
570
- uuid={uuid}
571
- config={output}
572
- setDialogMode={setDialogMode}
573
- assignToOutput={assignToOutput}
574
- setAssignToOutput={setAssignToOutput}
575
- />
576
- ))}
577
- </div>
578
- )}
579
- </PrimaryToolboxSection>
580
- );
581
- };
@@ -1,25 +0,0 @@
1
- import {
2
- BrowserPreferencesDefinition,
3
- createBrowserPreferencesHook,
4
- } from '@arcanewizards/sigil/frontend/preferences';
5
- import { SIGIL_COLOR } from '@arcanewizards/sigil/frontend/styling';
6
- import z from 'zod';
7
-
8
- const BROWSER_PREFERENCES_KEY = 'timecode-toolbox-preferences';
9
-
10
- const BROWSER_PREFERENCES = z.object({
11
- color: SIGIL_COLOR,
12
- });
13
-
14
- type BrowserPreferences = z.infer<typeof BROWSER_PREFERENCES>;
15
-
16
- const TOOLBOX_PREFERENCES: BrowserPreferencesDefinition<BrowserPreferences> = {
17
- key: BROWSER_PREFERENCES_KEY,
18
- zodType: BROWSER_PREFERENCES,
19
- defaultValue: {
20
- color: 'orange',
21
- },
22
- };
23
-
24
- export const useBrowserPreferences =
25
- createBrowserPreferencesHook(TOOLBOX_PREFERENCES);