@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,170 +0,0 @@
1
- import { useDataFileData } from '@arcanejs/react-toolkit/data';
2
- import {
3
- FC,
4
- ReactNode,
5
- useCallback,
6
- useEffect,
7
- useMemo,
8
- useState,
9
- } from 'react';
10
- import { ToolboxConfigData } from '../config';
11
- import {
12
- ApplicationState,
13
- OutputArtnetDefinition,
14
- OutputConfig,
15
- OutputState,
16
- isOutputArtnetDefinition,
17
- } from '../components/proto';
18
- import { adjustTimecodeForDelay, getTimecodeInstance } from '../util';
19
- import { useLogger } from '@arcanewizards/sigil';
20
- import { ArtNet, createArtnet } from '@arcanewizards/artnet';
21
- import { TIMECODE_FPS } from '@arcanewizards/artnet/constants';
22
- import { StateSensitiveComponentProps } from '../types';
23
-
24
- type ArtnetOutputConnectionProps = StateSensitiveComponentProps & {
25
- uuid: string;
26
- config: OutputConfig;
27
- connection: OutputArtnetDefinition;
28
- state: ApplicationState;
29
- };
30
-
31
- const ArtnetOutputConnection: FC<ArtnetOutputConnectionProps> = ({
32
- uuid,
33
- config,
34
- connection: { target, mode },
35
- setState,
36
- state,
37
- }) => {
38
- const log = useLogger();
39
-
40
- const [artnetInstance, setArtnetInstance] = useState<ArtNet | null>(null);
41
-
42
- const setConnection = useCallback(
43
- (state: OutputState) =>
44
- setState((current) => ({
45
- ...current,
46
- outputs: {
47
- ...current.outputs,
48
- [uuid]: state,
49
- },
50
- })),
51
- [setState, uuid],
52
- );
53
-
54
- useEffect(() => {
55
- let artnet: ArtNet | null = null;
56
- setConnection({ status: 'connecting' });
57
- const created = createArtnet({
58
- mode: 'send',
59
- ...target,
60
- });
61
- created.on('error', (err) => {
62
- const error = new Error('ArtNet output connection error');
63
- error.cause = err instanceof Error ? err : new Error(String(err));
64
- log.error(error);
65
- setConnection({
66
- status: 'error',
67
- errors: [`${err}`],
68
- });
69
- });
70
- created
71
- .connect()
72
- .then(() => {
73
- artnet = created;
74
- setArtnetInstance(created);
75
- log.info(`ArtNet Timecode output initialized`);
76
- setConnection({ status: 'active' });
77
- })
78
- .catch((err) => {
79
- const error = new Error('Failed to start ArtNet Timecode output');
80
- error.cause = err instanceof Error ? err : new Error(String(err));
81
- log.error(error);
82
- setConnection({
83
- status: 'error',
84
- errors: [`${err}`],
85
- });
86
- });
87
-
88
- return () => {
89
- if (artnet) {
90
- artnet.destroy();
91
- setArtnetInstance((current) => (artnet === current ? null : current));
92
- }
93
- };
94
- }, [setConnection, uuid, target, log]);
95
-
96
- useEffect(() => {
97
- return () => {
98
- // Remove the connection when it's no longer mounted / configured
99
- setState((current) => {
100
- const { [uuid]: _, ...rest } = current.outputs;
101
- return {
102
- ...current,
103
- outputs: rest,
104
- };
105
- });
106
- };
107
- }, [setState, uuid]);
108
-
109
- const tcInstance = useMemo(
110
- () => config.link && getTimecodeInstance(state, config.link),
111
- [state, config.link],
112
- );
113
-
114
- const timecodeState = useMemo(
115
- () =>
116
- tcInstance?.state
117
- ? adjustTimecodeForDelay(tcInstance.state, config.delayMs ?? 0)
118
- : null,
119
- [tcInstance?.state, config.delayMs],
120
- );
121
-
122
- useEffect(() => {
123
- if (!artnetInstance) {
124
- return;
125
- }
126
-
127
- if (
128
- timecodeState?.state === 'playing' ||
129
- timecodeState?.state === 'lagging'
130
- ) {
131
- const tcState = timecodeState;
132
- const interval = setInterval(() => {
133
- const time =
134
- (Date.now() - tcState.effectiveStartTimeMillis) * tcState.speed;
135
- artnetInstance.sendTimecode(mode, time);
136
- }, 1000 / TIMECODE_FPS[mode]);
137
- return () => {
138
- clearInterval(interval);
139
- };
140
- } else if (timecodeState?.state === 'stopped') {
141
- artnetInstance.sendTimecode(mode, timecodeState?.positionMillis ?? 0);
142
- }
143
- }, [artnetInstance, mode, timecodeState]);
144
-
145
- return null;
146
- };
147
-
148
- export const ArtnetOutputConnections: FC<StateSensitiveComponentProps> = (
149
- props,
150
- ) => {
151
- const { outputs } = useDataFileData(ToolboxConfigData);
152
-
153
- return Object.entries(outputs)
154
- .filter(([_, { enabled }]) => enabled)
155
- .map<ReactNode>(([uuid, output]) => {
156
- const definition = output.definition;
157
- if (!isOutputArtnetDefinition(definition)) {
158
- return null;
159
- }
160
- return (
161
- <ArtnetOutputConnection
162
- key={uuid}
163
- uuid={uuid}
164
- config={output}
165
- connection={definition}
166
- {...props}
167
- />
168
- );
169
- });
170
- };
@@ -1,11 +0,0 @@
1
- import { FC } from 'react';
2
- import { ArtnetOutputConnections } from './artnet';
3
- import { StateSensitiveComponentProps } from '../types';
4
-
5
- export const OutputConnections: FC<StateSensitiveComponentProps> = (props) => {
6
- return (
7
- <>
8
- <ArtnetOutputConnections {...props} />
9
- </>
10
- );
11
- };
package/src/start.ts DELETED
@@ -1,47 +0,0 @@
1
- import path from 'path';
2
- import pino from 'pino';
3
- import { runTimecodeToolboxServer } from '.';
4
- import { homedir } from 'os';
5
-
6
- const DATA_DIR = path.join(homedir(), 'timecode-toolbox');
7
-
8
- const logger = pino({
9
- level: 'info',
10
- transport: {
11
- target: 'pino-pretty',
12
- },
13
- });
14
-
15
- const server = runTimecodeToolboxServer({
16
- logger,
17
- appProps: {
18
- dataDirectory: DATA_DIR,
19
- },
20
- toolkitOptions: {
21
- entrypointJsFile: path.join(path.dirname(__dirname), 'dist/entrypoint.js'),
22
- },
23
- title: 'Timecode Toolbox Server',
24
- });
25
-
26
- const shutdown = () =>
27
- server
28
- .shutdown()
29
- .catch((err) => {
30
- logger.error({ err }, 'Error during shutdown');
31
- })
32
- .finally(() => {
33
- process.exit(0);
34
- });
35
-
36
- // Catch SIGINT and SIGTERM to allow for graceful shutdown
37
- process.on('SIGINT', async () => {
38
- logger.info('Received SIGINT, shutting down...');
39
- shutdown();
40
- });
41
-
42
- process.on('SIGTERM', () => {
43
- logger.info('Received SIGTERM, shutting down...');
44
- shutdown();
45
- });
46
-
47
- export {};
package/src/tree.ts DELETED
@@ -1,133 +0,0 @@
1
- export type TreeLeaf<T> = {
2
- value: T;
3
- children?: undefined;
4
- };
5
-
6
- export type TreeNode<T> = {
7
- value?: T;
8
- children: Record<string, Tree<T>>;
9
- };
10
-
11
- export type Tree<T> = TreeLeaf<T> | TreeNode<T>;
12
-
13
- export const isTreeNode = <T>(node: Tree<T>): node is TreeNode<T> =>
14
- 'children' in node &&
15
- typeof node.children === 'object' &&
16
- node.children !== null;
17
-
18
- export const updateTreeState = <T>(
19
- current: Tree<T>,
20
- path: string[],
21
- value: T,
22
- ): Tree<T> => {
23
- if (path.length === 0) {
24
- return {
25
- ...current,
26
- value,
27
- };
28
- }
29
- const [nextKey, ...remainingPath] = path;
30
- if (!nextKey) {
31
- throw new Error('Invalid path');
32
- }
33
- if (remainingPath.length === 0) {
34
- // Update the child directly
35
- return {
36
- ...current,
37
- children: {
38
- ...current.children,
39
- [nextKey]: {
40
- value,
41
- children: current.children?.[nextKey]?.children,
42
- },
43
- },
44
- };
45
- }
46
- // Path is not empty, need to recurse
47
- const children = isTreeNode(current) ? current.children : {};
48
- return {
49
- ...current,
50
- children: {
51
- ...children,
52
- [nextKey]: updateTreeState(
53
- children[nextKey] ?? { children: {} },
54
- remainingPath,
55
- value,
56
- ),
57
- },
58
- };
59
- };
60
-
61
- export const getTreeValue = <T>(current: Tree<T>, path: string[]): T | null => {
62
- if (path.length === 0) {
63
- return current.value ?? null;
64
- }
65
- const [nextKey, ...remainingPath] = path;
66
- if (!nextKey) {
67
- throw new Error('Invalid path');
68
- }
69
- if (isTreeNode(current)) {
70
- return getTreeValue(
71
- current.children[nextKey] ?? { children: {} },
72
- remainingPath,
73
- );
74
- }
75
- return null;
76
- };
77
-
78
- export const deleteTreePath = <T>(
79
- current: Tree<T>,
80
- path: string[],
81
- ): Tree<T> => {
82
- if (path.length === 0) {
83
- throw new Error('Cannot delete root of the tree');
84
- }
85
- const [nextKey, ...remainingPath] = path;
86
- if (!nextKey) {
87
- throw new Error('Invalid path');
88
- }
89
- if (isTreeNode(current)) {
90
- if (remainingPath.length === 0) {
91
- // Delete the child directly
92
- const { [nextKey]: _, ...remainingChildren } = current.children;
93
- return {
94
- ...current,
95
- children: remainingChildren,
96
- };
97
- } else {
98
- // Recurse into the child
99
- return {
100
- ...current,
101
- children: {
102
- ...current.children,
103
- [nextKey]: deleteTreePath(
104
- current.children[nextKey] ?? { children: {} },
105
- remainingPath,
106
- ),
107
- },
108
- };
109
- }
110
- }
111
- return current;
112
- };
113
-
114
- export const mapTree = <T, U>(
115
- current: Tree<T>,
116
- fn: (value: T) => U,
117
- ): Tree<U> => {
118
- if (isTreeNode(current)) {
119
- return {
120
- value: current.value ? fn(current.value) : undefined,
121
- children: Object.fromEntries(
122
- Object.entries(current.children).map(([key, child]) => [
123
- key,
124
- mapTree(child, fn),
125
- ]),
126
- ),
127
- };
128
- } else {
129
- return {
130
- value: fn(current.value),
131
- };
132
- }
133
- };
package/src/types.ts DELETED
@@ -1,12 +0,0 @@
1
- import { Dispatch, SetStateAction } from 'react';
2
- import { ApplicationState, TimecodeHandlerMethods } from './components/proto';
3
- import { Tree } from './tree';
4
-
5
- export type TimecodeHandlers = Tree<TimecodeHandlerMethods>;
6
-
7
- export type StateSensitiveComponentProps = {
8
- state: ApplicationState;
9
- setState: Dispatch<SetStateAction<ApplicationState>>;
10
- };
11
-
12
- export type HandlersUpdater = Dispatch<SetStateAction<TimecodeHandlers>>;
package/src/urls.ts DELETED
@@ -1,49 +0,0 @@
1
- import z from 'zod';
2
- import { TimecodeInstanceId } from './components/proto';
3
-
4
- export const TIMECODE_PATH_FRAGMENT = 'tc';
5
-
6
- export const WINDOW_MODE_TIMECODE = 'timecode';
7
-
8
- type FragmentValues = {
9
- tc?: TimecodeInstanceId;
10
- };
11
-
12
- type WithUrlFragmentArgs = {
13
- location?: Location | URL;
14
- values: FragmentValues;
15
- };
16
-
17
- export const withUrlFragment = ({ location, values }: WithUrlFragmentArgs) => {
18
- const url = new URL(location ? location.href : window.location.href);
19
- const fragmentParams = new URLSearchParams();
20
-
21
- for (const [key, value] of Object.entries(values)) {
22
- if (value === null) {
23
- fragmentParams.delete(key);
24
- } else {
25
- fragmentParams.set(key, JSON.stringify(value));
26
- }
27
- }
28
- url.hash = `#${fragmentParams.toString()}`;
29
- return url;
30
- };
31
-
32
- export const getFragmentValue = <K extends keyof FragmentValues>(
33
- key: K,
34
- zodParser: z.ZodType<NonNullable<FragmentValues[K]>>,
35
- ): NonNullable<FragmentValues[K]> | null => {
36
- const url = new URL(window.location.href);
37
- const fragmentParams = new URLSearchParams(url.hash.slice(1));
38
- const value = fragmentParams.get(key);
39
- if (!value) {
40
- return null;
41
- }
42
- try {
43
- return zodParser.parse(JSON.parse(value));
44
- } catch (e) {
45
- // eslint-disable-next-line no-console
46
- console.error(`Error parsing URL fragment value for key ${key}:`, e);
47
- return null;
48
- }
49
- };
package/src/util.ts DELETED
@@ -1,82 +0,0 @@
1
- import {
2
- ApplicationState,
3
- InputOrGenInstance,
4
- isTimecodeGroup,
5
- isTimecodeInstance,
6
- OutputConfig,
7
- TimecodeInstance,
8
- TimecodeState,
9
- } from './components/proto';
10
-
11
- export const isKeyedEntry =
12
- <K, I, O extends I>(typeGuard: (value: I) => value is O) =>
13
- (entry: [K, I]): entry is [K, O] =>
14
- entry.length === 2 && typeGuard(entry[1]);
15
-
16
- export const getTimecodeInstance = (
17
- state: ApplicationState,
18
- id: InputOrGenInstance,
19
- ): TimecodeInstance | null => {
20
- const [type, firstId, ...remainingPath] = id;
21
- if (!firstId) {
22
- return null;
23
- }
24
- let current =
25
- state[type === 'input' ? 'inputs' : 'generators'][firstId]?.timecode ??
26
- null;
27
- for (const idPart of remainingPath) {
28
- if (!current || !isTimecodeGroup(current)) {
29
- return null;
30
- }
31
- current = current.timecodes[idPart] ?? null;
32
- }
33
- if (isTimecodeInstance(current)) {
34
- return current;
35
- }
36
- return null;
37
- };
38
-
39
- export const adjustTimecodeForDelay = (
40
- state: TimecodeState,
41
- delayMillis: number,
42
- ): TimecodeState => {
43
- if (state.state === 'playing' || state.state === 'lagging') {
44
- return {
45
- ...state,
46
- effectiveStartTimeMillis: state.effectiveStartTimeMillis + delayMillis,
47
- };
48
- } else if (state.state === 'stopped') {
49
- return {
50
- ...state,
51
- positionMillis: state.positionMillis - delayMillis,
52
- };
53
- }
54
- return state;
55
- };
56
-
57
- export const augmentUpstreamTimecodeWithOutputMetadata = (
58
- tc: TimecodeInstance | null,
59
- config: OutputConfig,
60
- ): TimecodeInstance => {
61
- if (!tc) {
62
- return {
63
- name: null,
64
- metadata: null,
65
- state: {
66
- state: 'none',
67
- accuracyMillis: null,
68
- smpteMode: config.definition.mode,
69
- onAir: null,
70
- },
71
- };
72
- }
73
- // Adjust the timecode instance with output-specific metadata
74
- return {
75
- name: null,
76
- metadata: tc.metadata,
77
- state: {
78
- ...adjustTimecodeForDelay(tc.state, config.delayMs ?? 0),
79
- smpteMode: config.definition.mode,
80
- },
81
- };
82
- };
@@ -1,7 +0,0 @@
1
- /** @type {import('tailwindcss').Config} */
2
- module.exports = {
3
- content: ['./src/components/frontend/**/*.{ts,tsx,css}'],
4
- corePlugins: {
5
- preflight: false,
6
- },
7
- };
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "@arcanewizards/typescript-config/react.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "module": "esnext",
6
- "moduleResolution": "bundler"
7
- },
8
- "include": ["src", "tsup.config.ts"],
9
- "exclude": ["node_modules", "dist"],
10
- }
package/tsup.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import { defineConfig } from 'tsup';
2
-
3
- export default defineConfig({
4
- entry: ['src/index.ts', 'src/start.ts', 'src/components/frontend/index.tsx'],
5
- format: ['cjs', 'esm'],
6
- splitting: false,
7
- dts: true,
8
- external: [],
9
- sourcemap: false,
10
- });