@arcanewizards/timecode-toolbox 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.
Files changed (41) hide show
  1. package/.turbo/turbo-build.log +55 -0
  2. package/CHANGELOG.md +24 -0
  3. package/eslint.config.mjs +49 -0
  4. package/package.json +74 -0
  5. package/src/app.tsx +147 -0
  6. package/src/components/backend/index.ts +6 -0
  7. package/src/components/backend/toolbox-root.ts +119 -0
  8. package/src/components/frontend/constants.ts +81 -0
  9. package/src/components/frontend/entrypoint.ts +12 -0
  10. package/src/components/frontend/frontend.css +108 -0
  11. package/src/components/frontend/index.tsx +46 -0
  12. package/src/components/frontend/toolbox/content.tsx +45 -0
  13. package/src/components/frontend/toolbox/context.tsx +63 -0
  14. package/src/components/frontend/toolbox/core/size-aware-div.tsx +51 -0
  15. package/src/components/frontend/toolbox/core/timecode-display.tsx +592 -0
  16. package/src/components/frontend/toolbox/generators.tsx +318 -0
  17. package/src/components/frontend/toolbox/inputs.tsx +484 -0
  18. package/src/components/frontend/toolbox/outputs.tsx +581 -0
  19. package/src/components/frontend/toolbox/preferences.ts +25 -0
  20. package/src/components/frontend/toolbox/root.tsx +335 -0
  21. package/src/components/frontend/toolbox/settings.tsx +54 -0
  22. package/src/components/frontend/toolbox/types.ts +28 -0
  23. package/src/components/frontend/toolbox/util.tsx +61 -0
  24. package/src/components/proto.ts +420 -0
  25. package/src/config.ts +7 -0
  26. package/src/generators/clock.tsx +206 -0
  27. package/src/generators/index.tsx +15 -0
  28. package/src/index.ts +38 -0
  29. package/src/inputs/artnet.tsx +305 -0
  30. package/src/inputs/index.tsx +13 -0
  31. package/src/inputs/tcnet.tsx +272 -0
  32. package/src/outputs/artnet.tsx +170 -0
  33. package/src/outputs/index.tsx +11 -0
  34. package/src/start.ts +47 -0
  35. package/src/tree.ts +133 -0
  36. package/src/types.ts +12 -0
  37. package/src/urls.ts +49 -0
  38. package/src/util.ts +82 -0
  39. package/tailwind.config.cjs +7 -0
  40. package/tsconfig.json +10 -0
  41. package/tsup.config.ts +10 -0
@@ -0,0 +1,420 @@
1
+ import z from 'zod';
2
+ import {
3
+ AnyComponentProto,
4
+ BaseClientComponentCall,
5
+ BaseClientComponentMessage,
6
+ BaseComponentProto,
7
+ } from '@arcanejs/protocol';
8
+ import { SIGIL_COLOR, SigilColor } from '@arcanewizards/sigil/frontend/styling';
9
+ import { Diff } from '@arcanejs/diff';
10
+ import { NetworkInterface } from '@arcanewizards/net-utils';
11
+ import { TimecodeMode } from '@arcanewizards/artnet/constants';
12
+ import { Tree } from '../tree';
13
+
14
+ /* Shared config & proto definitions */
15
+
16
+ const NET_UTILS_GENERAL_TARGET_DEFINITION = z
17
+ .union([
18
+ z.object({
19
+ type: z.literal('host'),
20
+ host: z.string(),
21
+ }),
22
+ z.object({
23
+ type: z.literal('interface'),
24
+ interface: z.string(),
25
+ }),
26
+ ])
27
+ .and(
28
+ z.object({
29
+ port: z.number().optional(),
30
+ }),
31
+ );
32
+
33
+ const INPUT_ARTNET_DEFINITION = z.object({
34
+ type: z.literal('artnet'),
35
+ iface: z.string(),
36
+ port: z.number().optional(),
37
+ });
38
+
39
+ export type InputArtnetDefinition = z.infer<typeof INPUT_ARTNET_DEFINITION>;
40
+
41
+ export const isInputArtnetDefinition = (
42
+ definition: InputDefinition,
43
+ ): definition is InputArtnetDefinition => definition.type === 'artnet';
44
+
45
+ const OUTPUT_ARTNET_DEFINITION = z.object({
46
+ type: z.literal('artnet'),
47
+ target: NET_UTILS_GENERAL_TARGET_DEFINITION,
48
+ mode: z.enum(['FILM', 'EBU', 'DF', 'SMPTE']),
49
+ });
50
+
51
+ export type OutputArtnetDefinition = z.infer<typeof OUTPUT_ARTNET_DEFINITION>;
52
+
53
+ export const isOutputArtnetDefinition = (
54
+ definition: OutputDefinition,
55
+ ): definition is OutputArtnetDefinition => definition.type === 'artnet';
56
+
57
+ const INPUT_OR_OUTPUT_TCNET_DEFINITION = z.object({
58
+ type: z.literal('tcnet'),
59
+ iface: z.string(),
60
+ nodeName: z.string().optional(),
61
+ });
62
+
63
+ export type InputTcnetDefinition = z.infer<
64
+ typeof INPUT_OR_OUTPUT_TCNET_DEFINITION
65
+ >;
66
+
67
+ export const isInputTcnetDefinition = (
68
+ definition: InputDefinition,
69
+ ): definition is InputTcnetDefinition => definition.type === 'tcnet';
70
+
71
+ const INPUT_DEFINITION = z.union([
72
+ INPUT_ARTNET_DEFINITION,
73
+ INPUT_OR_OUTPUT_TCNET_DEFINITION,
74
+ ]);
75
+
76
+ export type InputDefinition = z.infer<typeof INPUT_DEFINITION>;
77
+
78
+ const INPUT_CONFIG = z.object({
79
+ name: z.string().optional(),
80
+ color: SIGIL_COLOR.optional(),
81
+ definition: INPUT_DEFINITION,
82
+ enabled: z.boolean(),
83
+ delayMs: z.number().optional(),
84
+ });
85
+
86
+ export type InputConfig = z.infer<typeof INPUT_CONFIG>;
87
+
88
+ const GENERATOR_CLOCK_DEFINITION = z.object({
89
+ type: z.literal('clock'),
90
+ speed: z.number(),
91
+ });
92
+
93
+ export type GeneratorClockDefinition = z.infer<
94
+ typeof GENERATOR_CLOCK_DEFINITION
95
+ >;
96
+
97
+ const GENERATOR_DEFINITION = GENERATOR_CLOCK_DEFINITION;
98
+
99
+ export type GeneratorDefinition = z.infer<typeof GENERATOR_DEFINITION>;
100
+
101
+ const GENERATOR_CONFIG = z.object({
102
+ name: z.string().optional(),
103
+ color: SIGIL_COLOR.optional(),
104
+ delayMs: z.number().optional(),
105
+ definition: GENERATOR_DEFINITION,
106
+ });
107
+
108
+ export type GeneratorConfig = z.infer<typeof GENERATOR_CONFIG>;
109
+
110
+ const OUTPUT_DEFINITION = OUTPUT_ARTNET_DEFINITION; // todo expand to other output types in the future
111
+
112
+ export type OutputDefinition = z.infer<typeof OUTPUT_DEFINITION>;
113
+
114
+ export type InputInstanceId = [
115
+ type: 'input',
116
+ rootId: string,
117
+ ...path: string[],
118
+ ];
119
+
120
+ export const isInputInstanceId = (value: string[]): value is InputInstanceId =>
121
+ value.length >= 2 && value[0] === 'input';
122
+
123
+ export const INPUT_INSTANCE_ID = z
124
+ .string()
125
+ .array()
126
+ // TODO: remove type coercion zod/v4
127
+ .refine(isInputInstanceId) as unknown as z.ZodType<InputInstanceId>;
128
+
129
+ export type GeneratorInstanceId = [
130
+ type: 'generator',
131
+ rootId: string,
132
+ ...path: string[],
133
+ ];
134
+
135
+ export const isGeneratorInstanceId = (
136
+ value: string[],
137
+ ): value is GeneratorInstanceId =>
138
+ value.length >= 2 && value[0] === 'generator';
139
+
140
+ export const GENERATOR_INSTANCE_ID = z
141
+ .string()
142
+ .array()
143
+ // TODO: remove type coercion zod/v4
144
+ .refine(isGeneratorInstanceId) as unknown as z.ZodType<GeneratorInstanceId>;
145
+
146
+ export type OutputInstanceId = [
147
+ type: 'output',
148
+ rootId: string,
149
+ ...path: string[],
150
+ ];
151
+
152
+ export const isOutputInstanceId = (
153
+ value: string[],
154
+ ): value is OutputInstanceId => value.length >= 2 && value[0] === 'output';
155
+
156
+ export const OUTPUT_INSTANCE_ID = z
157
+ .string()
158
+ .array()
159
+ // TODO: remove type coercion zod/v4
160
+ .refine(isOutputInstanceId) as unknown as z.ZodType<OutputInstanceId>;
161
+
162
+ export const INPUT_OR_GENERATOR_INSTANCE_ID = z.union([
163
+ INPUT_INSTANCE_ID,
164
+ GENERATOR_INSTANCE_ID,
165
+ ]);
166
+
167
+ export type InputOrGenInstance = z.infer<typeof INPUT_OR_GENERATOR_INSTANCE_ID>;
168
+
169
+ export const TIMECODE_INSTANCE_ID = z.union([
170
+ INPUT_INSTANCE_ID,
171
+ GENERATOR_INSTANCE_ID,
172
+ OUTPUT_INSTANCE_ID,
173
+ ]);
174
+
175
+ export type TimecodeInstanceId = z.infer<typeof TIMECODE_INSTANCE_ID>;
176
+
177
+ const OUTPUT_CONFIG = z.object({
178
+ name: z.string().optional(),
179
+ color: SIGIL_COLOR.optional(),
180
+ definition: OUTPUT_DEFINITION,
181
+ enabled: z.boolean(),
182
+ delayMs: z.number().optional(),
183
+ link: INPUT_OR_GENERATOR_INSTANCE_ID.nullable(),
184
+ });
185
+
186
+ export type OutputConfig = z.infer<typeof OUTPUT_CONFIG>;
187
+
188
+ export const TOOLBOX_CONFIG = z.object({
189
+ inputs: z.record(z.string(), INPUT_CONFIG),
190
+ generators: z.record(z.string(), GENERATOR_CONFIG),
191
+ outputs: z.record(z.string(), OUTPUT_CONFIG),
192
+ });
193
+
194
+ export type ToolboxConfig = z.infer<typeof TOOLBOX_CONFIG>;
195
+
196
+ export const DEFAULT_CONFIG: ToolboxConfig = {
197
+ inputs: {},
198
+ generators: {},
199
+ outputs: {},
200
+ };
201
+
202
+ /* App State */
203
+
204
+ export type TimecodePlayStateNone = {
205
+ state: 'none';
206
+ };
207
+
208
+ export type TimecodePlayStateStopped = {
209
+ state: 'stopped';
210
+ positionMillis: number;
211
+ };
212
+
213
+ export type TimecodePlayStatePlayingOrLagging = {
214
+ state: 'playing' | 'lagging';
215
+ effectiveStartTimeMillis: number;
216
+ /**
217
+ * 1 = real-time, 2 = twice as fast, 0.5 = half as fast, etc.
218
+ */
219
+ speed: number;
220
+ };
221
+
222
+ export type TimecodePlayState =
223
+ | TimecodePlayStateNone
224
+ | TimecodePlayStateStopped
225
+ | TimecodePlayStatePlayingOrLagging;
226
+
227
+ export const isPlaying = (
228
+ state: TimecodePlayState,
229
+ ): state is TimecodePlayStatePlayingOrLagging =>
230
+ state.state === 'playing' || state.state === 'lagging';
231
+
232
+ export const isStopped = (
233
+ state: TimecodePlayState,
234
+ ): state is TimecodePlayStateStopped => state.state === 'stopped';
235
+
236
+ export type TimecodeState = {
237
+ /**
238
+ * Approximate accuracy of the timecode, if available
239
+ */
240
+ accuracyMillis: number | null;
241
+ /**
242
+ * SMPTE mode if known and applicable, otherwise null
243
+ */
244
+ smpteMode: TimecodeMode | null;
245
+ /**
246
+ * If supported, whether the source is currently on air
247
+ * (e.g. have a mixer value high enough)
248
+ */
249
+ onAir: boolean | null;
250
+ } & TimecodePlayState;
251
+
252
+ export type TimecodeTotalTime = {
253
+ timeMillis: number;
254
+ /**
255
+ * How accurate is the totalTimeMillis value,
256
+ * some sources (such as ShowKontrol) are not completely accurate.
257
+ */
258
+ precisionMillis: number;
259
+ };
260
+
261
+ export type TimecodeMetadata = {
262
+ /**
263
+ * If available, the total time of the track loaded in this timecode.
264
+ *
265
+ * Some timecode sources will not have this information.
266
+ */
267
+ totalTime: TimecodeTotalTime | null;
268
+ title: string | null;
269
+ artist: string | null;
270
+ };
271
+
272
+ export type TimecodeInstance = {
273
+ name: string | null;
274
+ errors?: string[];
275
+ warnings?: string[];
276
+ state: TimecodeState;
277
+ metadata: TimecodeMetadata | null;
278
+ };
279
+
280
+ export const isTimecodeInstance = (
281
+ instance: TimecodeInstance | TimecodeGroup | null,
282
+ ): instance is TimecodeInstance =>
283
+ instance !== null && 'state' in instance && 'metadata' in instance;
284
+
285
+ export type TimecodeGroup = {
286
+ name: string | null;
287
+ color: SigilColor | null;
288
+ timecodes: Record<string, TimecodeInstance | TimecodeGroup>;
289
+ };
290
+
291
+ export const isTimecodeGroup = (
292
+ instance: TimecodeInstance | TimecodeGroup | null,
293
+ ): instance is TimecodeGroup => instance !== null && 'timecodes' in instance;
294
+
295
+ export type ConnectedClient = {
296
+ name: string;
297
+ host: string;
298
+ port: number;
299
+ protocolVersion: string;
300
+ details: string[];
301
+ };
302
+
303
+ export type InputState = {
304
+ status: 'disabled' | 'connecting' | 'error' | 'active';
305
+ errors?: string[];
306
+ warnings?: string[];
307
+ timecode: TimecodeInstance | TimecodeGroup | null;
308
+ /**
309
+ * If applicable, details of any connected clients
310
+ */
311
+ clients?: ConnectedClient[];
312
+ };
313
+
314
+ export type GeneratorState = {
315
+ timecode: TimecodeInstance | null;
316
+ };
317
+
318
+ export type OutputState = {
319
+ status: 'disabled' | 'connecting' | 'error' | 'active';
320
+ errors?: string[];
321
+ warnings?: string[];
322
+ /**
323
+ * If applicable, details of any connected clients
324
+ */
325
+ clients?: ConnectedClient[];
326
+ };
327
+
328
+ export type ApplicationState = {
329
+ inputs: Record<string, InputState>;
330
+ generators: Record<string, GeneratorState>;
331
+ outputs: Record<string, OutputState>;
332
+ };
333
+
334
+ export type TimecodeHandlerMethods = {
335
+ play?: () => void;
336
+ pause?: () => void;
337
+ seekRelative?: (deltaMillis: number) => void;
338
+ beginning?: () => void;
339
+ };
340
+
341
+ export type AvailableHandlers = Partial<
342
+ Record<keyof TimecodeHandlerMethods, true>
343
+ >;
344
+
345
+ /* Proto */
346
+
347
+ export const NAMESPACE = 'timecode-toolbox';
348
+
349
+ export type Namespace = typeof NAMESPACE;
350
+
351
+ export type ToolboxRootComponent = BaseComponentProto<
352
+ Namespace,
353
+ 'toolbox-root'
354
+ > & {
355
+ config: ToolboxConfig;
356
+ state: ApplicationState;
357
+ handlers: Tree<AvailableHandlers>;
358
+ };
359
+
360
+ export type TimecodeToolboxComponent = ToolboxRootComponent;
361
+
362
+ export const isTimecodeToolboxComponent = (
363
+ component: AnyComponentProto,
364
+ ): component is TimecodeToolboxComponent => component.namespace === NAMESPACE;
365
+
366
+ export type ToolboxRootGetNetworkInterfaces = BaseClientComponentCall<
367
+ Namespace,
368
+ 'toolbox-root-get-network-interfaces'
369
+ >;
370
+
371
+ export type ToolboxRootGetNetworkInterfacesReturn = Record<
372
+ string,
373
+ NetworkInterface
374
+ >;
375
+
376
+ export type ToolboxRootCallHandler<
377
+ H extends keyof AvailableHandlers = keyof AvailableHandlers,
378
+ > = BaseClientComponentCall<Namespace, 'toolbox-root-call-handler'> & {
379
+ path: string[];
380
+ handler: H;
381
+ args: Parameters<NonNullable<TimecodeHandlerMethods[H]>>;
382
+ };
383
+
384
+ export type ToolboxRootCallHandlerReturn = void;
385
+
386
+ export type TimecodeToolboxComponentCalls = {
387
+ 'toolbox-root-get-network-interfaces': {
388
+ call: ToolboxRootGetNetworkInterfaces;
389
+ return: ToolboxRootGetNetworkInterfacesReturn;
390
+ };
391
+ 'toolbox-root-call-handler': {
392
+ call: ToolboxRootCallHandler;
393
+ return: ToolboxRootCallHandlerReturn;
394
+ };
395
+ };
396
+
397
+ export type ToolboxRootConfigUpdate = BaseClientComponentMessage<Namespace> & {
398
+ component: 'toolbox-root';
399
+ action: 'update-config';
400
+ diff: Diff<ToolboxConfig>;
401
+ };
402
+
403
+ export type TimecodeToolboxComponentMessage = ToolboxRootConfigUpdate;
404
+
405
+ export const isTimecodeToolboxComponentMessage = <
406
+ C extends TimecodeToolboxComponentMessage['component'],
407
+ >(
408
+ message: BaseClientComponentMessage<string>,
409
+ component: C,
410
+ ): message is TimecodeToolboxComponentMessage & { component: C } =>
411
+ message.namespace === NAMESPACE &&
412
+ (message as TimecodeToolboxComponentMessage).component === component;
413
+
414
+ export const isTimecodeToolboxComponentCall = <
415
+ A extends keyof TimecodeToolboxComponentCalls,
416
+ >(
417
+ call: BaseClientComponentCall<string, string>,
418
+ action: A,
419
+ ): call is TimecodeToolboxComponentCalls[A]['call'] =>
420
+ call.namespace === NAMESPACE && call.action === action;
package/src/config.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { createDataFileDefinition } from '@arcanejs/react-toolkit/data';
2
+ import { DEFAULT_CONFIG, TOOLBOX_CONFIG } from './components/proto';
3
+
4
+ export const ToolboxConfigData = createDataFileDefinition({
5
+ schema: TOOLBOX_CONFIG,
6
+ defaultValue: DEFAULT_CONFIG,
7
+ });
@@ -0,0 +1,206 @@
1
+ import {
2
+ FC,
3
+ ReactNode,
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ } from 'react';
9
+ import {
10
+ GeneratorClockDefinition,
11
+ GeneratorConfig,
12
+ InputOrGenInstance,
13
+ isPlaying,
14
+ isStopped,
15
+ TimecodePlayState,
16
+ } from '../components/proto';
17
+ import { useDataFileData } from '@arcanejs/react-toolkit/data';
18
+ import { ToolboxConfigData } from '../config';
19
+ import { HandlersUpdater, StateSensitiveComponentProps } from '../types';
20
+ import { deleteTreePath, updateTreeState } from '../tree';
21
+
22
+ type ClockGeneratorProps = StateSensitiveComponentProps & {
23
+ uuid: string;
24
+ config: GeneratorConfig;
25
+ generator: GeneratorClockDefinition;
26
+ setHandlers: HandlersUpdater;
27
+ };
28
+
29
+ export const ClockGenerator: FC<ClockGeneratorProps> = ({
30
+ uuid,
31
+ config,
32
+ generator,
33
+ setState,
34
+ setHandlers,
35
+ }) => {
36
+ const id: InputOrGenInstance = useMemo(() => ['generator', uuid], [uuid]);
37
+
38
+ const [state, setLocalState] = useState<TimecodePlayState>({
39
+ state: 'stopped',
40
+ positionMillis: 0,
41
+ });
42
+
43
+ const { speed } = generator;
44
+
45
+ const play = useCallback(() => {
46
+ setLocalState((current) => {
47
+ if (isPlaying(current)) {
48
+ return current;
49
+ }
50
+ const positionMillis = isStopped(current) ? current.positionMillis : 0;
51
+ const effectiveStartTimeMillis = Date.now() - positionMillis / speed;
52
+ return {
53
+ state: 'playing',
54
+ effectiveStartTimeMillis,
55
+ speed,
56
+ };
57
+ });
58
+ }, [speed]);
59
+
60
+ const pause = useCallback(() => {
61
+ setLocalState((current) => {
62
+ if (!isPlaying(current)) {
63
+ return current;
64
+ }
65
+ const positionMillis =
66
+ (Date.now() - current.effectiveStartTimeMillis) * speed;
67
+ return {
68
+ state: 'stopped',
69
+ positionMillis,
70
+ };
71
+ });
72
+ }, [speed]);
73
+
74
+ const seekRelative = useCallback(
75
+ (deltaMillis: number) => {
76
+ setLocalState((current) => {
77
+ if (current.state === 'none') {
78
+ return current;
79
+ }
80
+ const now = Date.now();
81
+ const positionMillis = isPlaying(current)
82
+ ? (now - current.effectiveStartTimeMillis) * speed
83
+ : current.positionMillis;
84
+ const newPositionMillis = Math.max(positionMillis + deltaMillis, 0);
85
+ if (isPlaying(current)) {
86
+ const effectiveStartTimeMillis = now - newPositionMillis / speed;
87
+ return {
88
+ ...current,
89
+ effectiveStartTimeMillis,
90
+ };
91
+ } else {
92
+ return {
93
+ ...current,
94
+ positionMillis: newPositionMillis,
95
+ };
96
+ }
97
+ });
98
+ },
99
+ [speed],
100
+ );
101
+
102
+ const beginning = useCallback(() => {
103
+ setLocalState((current) => {
104
+ if (current.state === 'none') {
105
+ return current;
106
+ }
107
+ if (isPlaying(current)) {
108
+ const effectiveStartTimeMillis = Date.now();
109
+ return {
110
+ ...current,
111
+ effectiveStartTimeMillis,
112
+ };
113
+ } else {
114
+ return {
115
+ ...current,
116
+ positionMillis: 0,
117
+ };
118
+ }
119
+ });
120
+ }, []);
121
+
122
+ useEffect(() => {
123
+ setLocalState((current) => {
124
+ if (current.state === 'none' || current.state === 'stopped') {
125
+ return current;
126
+ }
127
+ const now = Date.now();
128
+ const positionMillis =
129
+ (now - current.effectiveStartTimeMillis) * current.speed;
130
+ const effectiveStartTimeMillis = now - positionMillis / speed;
131
+ return {
132
+ ...current,
133
+ effectiveStartTimeMillis,
134
+ speed,
135
+ };
136
+ });
137
+ }, [speed]);
138
+
139
+ useEffect(() => {
140
+ setHandlers((current) =>
141
+ updateTreeState(current, id, { play, pause, seekRelative, beginning }),
142
+ );
143
+ }, [setHandlers, id, play, pause, seekRelative, beginning]);
144
+
145
+ useEffect(
146
+ () =>
147
+ setState((current) => ({
148
+ ...current,
149
+ generators: {
150
+ ...current.generators,
151
+ [uuid]: {
152
+ timecode: {
153
+ metadata: null,
154
+ name: null,
155
+ state: {
156
+ accuracyMillis: null,
157
+ smpteMode: null,
158
+ onAir: null,
159
+ ...state,
160
+ },
161
+ },
162
+ },
163
+ },
164
+ })),
165
+ [setState, uuid, state, config.name],
166
+ );
167
+
168
+ useEffect(
169
+ () => () => {
170
+ setState((current) => {
171
+ const { [uuid]: _, ...rest } = current.generators;
172
+ return {
173
+ ...current,
174
+ generators: rest,
175
+ };
176
+ });
177
+ setHandlers((current) => deleteTreePath(current, id));
178
+ },
179
+ [setState, setHandlers, id, uuid],
180
+ );
181
+
182
+ return null;
183
+ };
184
+
185
+ type ClockGeneratorsProps = StateSensitiveComponentProps & {
186
+ setHandlers: HandlersUpdater;
187
+ };
188
+
189
+ export const ClockGenerators: FC<ClockGeneratorsProps> = (props) => {
190
+ const { generators } = useDataFileData(ToolboxConfigData);
191
+ return Object.entries(generators).map<ReactNode>(([uuid, input]) => {
192
+ const generator = input.definition;
193
+ if (generator.type !== 'clock') {
194
+ return null;
195
+ }
196
+ return (
197
+ <ClockGenerator
198
+ key={uuid}
199
+ uuid={uuid}
200
+ config={input}
201
+ generator={generator}
202
+ {...props}
203
+ />
204
+ );
205
+ });
206
+ };
@@ -0,0 +1,15 @@
1
+ import { FC } from 'react';
2
+ import { ClockGenerators } from './clock';
3
+ import { HandlersUpdater, StateSensitiveComponentProps } from '../types';
4
+
5
+ type GeneratorsProps = StateSensitiveComponentProps & {
6
+ setHandlers: HandlersUpdater;
7
+ };
8
+
9
+ export const Generators: FC<GeneratorsProps> = (props) => {
10
+ return (
11
+ <>
12
+ <ClockGenerators {...props} />
13
+ </>
14
+ );
15
+ };
package/src/index.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { CoreComponents } from '@arcanejs/react-toolkit';
2
+ import {
3
+ runSigilApp,
4
+ SigilAppInstance,
5
+ SIGIL_COMPONENTS,
6
+ } from '@arcanewizards/sigil';
7
+ import { ToolkitOptions } from '@arcanejs/toolkit';
8
+ import pino from 'pino';
9
+ import { AppApi, createApp, TimecodeToolboxAppProps } from './app';
10
+ import { C } from './components/backend';
11
+ import { version } from '../package.json';
12
+
13
+ export type { AppApi };
14
+
15
+ export * as urls from './urls';
16
+
17
+ export type TimecodeToolboxOptions = {
18
+ logger: pino.Logger;
19
+ appProps: TimecodeToolboxAppProps;
20
+ toolkitOptions?: Omit<Partial<ToolkitOptions>, 'logger'>;
21
+ title: string;
22
+ };
23
+
24
+ export const runTimecodeToolboxServer = ({
25
+ logger,
26
+ appProps,
27
+ toolkitOptions,
28
+ title,
29
+ }: TimecodeToolboxOptions): SigilAppInstance<AppApi> =>
30
+ runSigilApp<AppApi, TimecodeToolboxAppProps>({
31
+ logger,
32
+ title,
33
+ version,
34
+ appProps,
35
+ toolkitOptions,
36
+ createApp,
37
+ componentNamespaces: [CoreComponents, SIGIL_COMPONENTS, C],
38
+ });