diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index a7f20afa..23f43b45 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -21,6 +21,7 @@ specifiers: '@rush-temp/openrazer': file:./projects/openrazer.tgz '@rush-temp/openrazer-examples': file:./projects/openrazer-examples.tgz '@rush-temp/precise-audio': file:./projects/precise-audio.tgz + '@rush-temp/react-light-desk': file:./projects/react-light-desk.tgz '@rush-temp/server': file:./projects/server.tgz '@rush-temp/synesthesia-razer': file:./projects/synesthesia-razer.tgz '@types/chai': ^4.1.7 @@ -33,6 +34,7 @@ specifiers: '@types/node': ~18.11.18 '@types/react': ^17.0.0 '@types/react-dom': ^17.0.0 + '@types/react-reconciler': ~0.26.0 '@types/spotify-web-playback-sdk': ^0.1.1 '@types/styled-components': ~5.1.26 '@types/uuid': ~9.0.0 @@ -68,6 +70,7 @@ specifiers: react-dom: ^17.0.2 react-icons: ^3.3.0 react-is: ~18.2.0 + react-reconciler: 0.26.0 run-sequence: ^1.2.2 soundbank-pitch-shift: ~1.0.3 source-map-loader: ~0.2.4 @@ -101,6 +104,7 @@ dependencies: '@rush-temp/openrazer': file:projects/openrazer.tgz '@rush-temp/openrazer-examples': file:projects/openrazer-examples.tgz_eslint@8.31.0 '@rush-temp/precise-audio': file:projects/precise-audio.tgz_typescript@4.9.4 + '@rush-temp/react-light-desk': file:projects/react-light-desk.tgz '@rush-temp/server': file:projects/server.tgz '@rush-temp/synesthesia-razer': file:projects/synesthesia-razer.tgz '@types/chai': 4.2.5 @@ -113,6 +117,7 @@ dependencies: '@types/node': 18.11.18 '@types/react': 17.0.58 '@types/react-dom': 17.0.19 + '@types/react-reconciler': 0.26.7 '@types/spotify-web-playback-sdk': 0.1.7 '@types/styled-components': 5.1.26 '@types/uuid': 9.0.0 @@ -148,6 +153,7 @@ dependencies: react-dom: 17.0.2_react@17.0.2 react-icons: 3.8.0_react@17.0.2 react-is: 18.2.0 + react-reconciler: 0.26.0_react@17.0.2 run-sequence: 1.2.2 soundbank-pitch-shift: 1.0.3 source-map-loader: 0.2.4 @@ -548,6 +554,12 @@ packages: '@types/react': 17.0.58 dev: false + /@types/react-reconciler/0.26.7: + resolution: {integrity: sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==} + dependencies: + '@types/react': 17.0.58 + dev: false + /@types/react/17.0.58: resolution: {integrity: sha512-c1GzVY97P0fGxwGxhYq989j4XwlcHQoto6wQISOC2v6wm3h0PORRWJFHlkRjfGsiG3y1609WdQ+J+tKxvrEd6A==} dependencies: @@ -5629,6 +5641,18 @@ packages: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: false + /react-reconciler/0.26.0_react@17.0.2: + resolution: {integrity: sha512-n2FJE9vPSiZ0Dn/jaV/iOAO6rXepnk74QGRcgwPSgmN/2syUJnbfEz7Bw5yodBfJhjA3L7cu1YdImYISTj4KZQ==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^17.0.0 + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react: 17.0.2 + scheduler: 0.20.2 + dev: false + /react/17.0.2: resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==} engines: {node: '>=0.10.0'} @@ -7775,6 +7799,18 @@ packages: - typescript dev: false + file:projects/react-light-desk.tgz: + resolution: {integrity: sha512-HfAvriemu8wOaN8umAnEGiloxs1ubOdbCJ9XaMato3uAdqHMUNCY0gVtShzpxrvX7irLbxI8YMkEuPjnIvlrBw==, tarball: file:projects/react-light-desk.tgz} + name: '@rush-temp/react-light-desk' + version: 0.0.0 + dependencies: + '@types/react': 17.0.58 + '@types/react-reconciler': 0.26.7 + gulp: 4.0.2 + react: 17.0.2 + react-reconciler: 0.26.0_react@17.0.2 + dev: false + file:projects/server.tgz: resolution: {integrity: sha512-1AaUpi/ZmfT6LQufRxBkBZRvOpazhB1Ovjs/mYMCguAYno/Klt6yO9ZwwBqIK3EJKUt3k54X6Stqk213bWHTAw==, tarball: file:projects/server.tgz} name: '@rush-temp/server' diff --git a/light-desk/CHANGELOG.md b/light-desk/CHANGELOG.md index 50f01830..d58c89c8 100644 --- a/light-desk/CHANGELOG.md +++ b/light-desk/CHANGELOG.md @@ -28,6 +28,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [breaking]: rework backend interfaces to unify defining and mutating props, adding / removing listeners, and child-parent relationships. +Deprecations, to remove before releases: +- `.addChild()` and `addChildren()` functions + ## [2.1.1] - 2022-01-11 - Update ws and @types/ws diff --git a/light-desk/src/backend/components/base.ts b/light-desk/src/backend/components/base.ts index 057dc908..eaff6900 100644 --- a/light-desk/src/backend/components/base.ts +++ b/light-desk/src/backend/components/base.ts @@ -94,24 +94,32 @@ export abstract class BaseParent extends Base implements Parent { public abstract validateChildren(children: Component[]): void; - public addChildren = (...children: CS): CS => { + public appendChildren = (...children: CS): CS => { for (const c of children) { - if (!this.children.includes(c)) { - const newChildren = [...this.children, c]; - this.validateChildren(newChildren); - this.children = Object.freeze(newChildren); - c.setParent(this); - } + const newChildren = [...this.children.filter((ch) => ch !== c), c]; + this.validateChildren(newChildren); + this.children = Object.freeze(newChildren); + c.setParent(this); } this.updateTree(); return children; }; - public addChild = (child: C): C => { - this.addChildren(child); + /** + * @deprecated use appendChildren instead + */ + public addChildren = this.appendChildren; + + public appendChild = (child: C): C => { + this.appendChildren(child); return child; }; + /** + * @deprecated use appendChild instead + */ + public addChild = this.appendChild; + public removeChild = (component: Component) => { const match = this.children.findIndex((c) => c === component); if (match >= 0) { @@ -158,8 +166,25 @@ export abstract class BaseParent extends Base implements Parent { } } - public insertBefore(_child: Component, _beforeChild: Component) { - throw new Error('TODO'); + public insertBefore(child: Component, beforeChild: Component) { + // Remove child from current parent (if it exists) + const filteredChildren = this.children.filter((c) => c !== child); + // Find position of beforeChild + let match = filteredChildren.findIndex((c) => c === beforeChild); + console.log('match', match); + if (match === -1) { + // If beforeChild is not found, insert at the end + match = filteredChildren.length; + } + const newChildren = [ + ...filteredChildren.slice(0, match), + child, + ...filteredChildren.slice(match), + ]; + this.validateChildren(newChildren); + this.children = Object.freeze(newChildren); + child.setParent(this); + this.updateTree(); } } diff --git a/light-desk/src/backend/components/tabs.ts b/light-desk/src/backend/components/tabs.ts index 1651ad10..cebfedee 100644 --- a/light-desk/src/backend/components/tabs.ts +++ b/light-desk/src/backend/components/tabs.ts @@ -12,7 +12,7 @@ type InternalTabProps = { name: string; }; -export type TabProps = InternalTabsProps; +export type TabProps = InternalTabProps; export class Tab extends BaseParent { public validateChildren = (children: Component[]) => { diff --git a/react-light-desk/.gitignore b/react-light-desk/.gitignore new file mode 100644 index 00000000..2d1d8f95 --- /dev/null +++ b/react-light-desk/.gitignore @@ -0,0 +1,5 @@ +/node_modules/ +/lib/ +/index.js +/index.d.ts +*.log \ No newline at end of file diff --git a/react-light-desk/gulpfile.js b/react-light-desk/gulpfile.js new file mode 100644 index 00000000..a1127d5f --- /dev/null +++ b/react-light-desk/gulpfile.js @@ -0,0 +1,9 @@ +var util = require('@synesthesia-project/gulp-util'); +var gulp = require('gulp'); + +util.setupBasicTypescriptProject({ + clean: ['lib'], + outputDir: './lib' +}); + +gulp.task('default', gulp.series('clean', 'ts')); diff --git a/react-light-desk/package.json b/react-light-desk/package.json new file mode 100644 index 00000000..c61c665a --- /dev/null +++ b/react-light-desk/package.json @@ -0,0 +1,36 @@ +{ + "name": "@synesthesia-project/react-light-desk", + "version": "0.0.1", + "description": "Core synesthesia libraries, type definitions & protocols", + "license": "MIT", + "main": "index.js", + "types": "index.d.js", + "repository": { + "type": "git", + "url": "https://github.com/synesthesia-project/synesthesia" + }, + "scripts": { + "clean": "gulp clean", + "build": "gulp", + "lint": "gulp lint", + "lint:fix": "gulp lint:fix" + }, + "files": [ + "lib/**/*", + "index.d.ts", + "index.js", + "README.md" + ], + "dependencies": { + "@synesthesia-project/compositor": "^0.0.1", + "@synesthesia-project/light-desk": "^2.1.1", + "react-reconciler": "0.26.0" + }, + "devDependencies": { + "@synesthesia-project/gulp-util": "~0.0.1", + "@types/react": "^17.0.0", + "@types/react-reconciler": "~0.26.0", + "react": "^17.0.2", + "gulp": "~4.0.2" + } +} diff --git a/react-light-desk/src/components.ts b/react-light-desk/src/components.ts new file mode 100644 index 00000000..6331c89b --- /dev/null +++ b/react-light-desk/src/components.ts @@ -0,0 +1,42 @@ +import * as React from 'react'; +import { LightDeskIntrinsicElements } from './types'; + +export const Button: React.FunctionComponent< + LightDeskIntrinsicElements['button'] +> = (props) => React.createElement('button', props, props.children); + +export const Group: React.FunctionComponent< + LightDeskIntrinsicElements['group'] +> = (props) => React.createElement('group', props, props.children); + +export const GroupHeader: React.FunctionComponent< + LightDeskIntrinsicElements['group-header'] +> = (props) => React.createElement('group-header', props, props.children); + +export const Label: React.FunctionComponent< + LightDeskIntrinsicElements['label'] +> = (props) => React.createElement('label', props, props.children); + +export const Rect: React.FunctionComponent< + LightDeskIntrinsicElements['rect'] +> = (props) => React.createElement('rect', props, props.children); + +export const SliderButton: React.FunctionComponent< + LightDeskIntrinsicElements['slider-button'] +> = (props) => React.createElement('slider-button', props, props.children); + +export const Switch: React.FunctionComponent< + LightDeskIntrinsicElements['switch'] +> = (props) => React.createElement('switch', props, props.children); + +export const Tab: React.FunctionComponent = ( + props +) => React.createElement('tab', props, props.children); + +export const Tabs: React.FunctionComponent< + LightDeskIntrinsicElements['tabs'] +> = (props) => React.createElement('tabs', props, props.children); + +export const TextInput: React.FunctionComponent< + LightDeskIntrinsicElements['text-input'] +> = (props) => React.createElement('text-input', props, props.children); diff --git a/react-light-desk/src/examples/example-001.tsx b/react-light-desk/src/examples/example-001.tsx new file mode 100644 index 00000000..0fc8b7f7 --- /dev/null +++ b/react-light-desk/src/examples/example-001.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import * as ld from '@synesthesia-project/light-desk'; + +import { LightDeskRenderer, Group } from '../index'; + +const desk = new ld.LightDesk(); + +desk.start({ + mode: 'automatic', + port: 1338, +}); + +const group = new ld.Group(); +desk.setRoot(group); + +const TestComponent = ({ foo }: { foo: string }) => { + const [count, setCount] = React.useState(0); + + React.useEffect(() => { + const timeout = setTimeout(() => { + setCount(count + 1); + }, 1000); + return () => clearTimeout(timeout); + }, [count]); + + return {`${foo}: ${count}`}; +}; + +const App = () => ( + + + + + + +); + +LightDeskRenderer.render(, group); diff --git a/react-light-desk/src/examples/example-002.tsx b/react-light-desk/src/examples/example-002.tsx new file mode 100644 index 00000000..eec8b33e --- /dev/null +++ b/react-light-desk/src/examples/example-002.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import * as ld from '@synesthesia-project/light-desk'; + +import { LightDeskRenderer, Group } from '../index'; + +const desk = new ld.LightDesk(); + +desk.start({ + mode: 'automatic', + port: 1338, +}); + +const group = new ld.Group(); +desk.setRoot(group); + +const App = () => { + const [title, setTitle] = React.useState('initial-group'); + + return ( + + {`Label: ${title}`} + + ); +}; + +LightDeskRenderer.render(, group); diff --git a/react-light-desk/src/examples/example-003.tsx b/react-light-desk/src/examples/example-003.tsx new file mode 100644 index 00000000..3f48b6c7 --- /dev/null +++ b/react-light-desk/src/examples/example-003.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import * as ld from '@synesthesia-project/light-desk'; + +import { + LightDeskRenderer, + Button, + Group, + GroupHeader, + SliderButton, +} from '../index'; + +const desk = new ld.LightDesk(); + +desk.start({ + mode: 'automatic', + port: 1338, +}); + +const group = new ld.Group(); +desk.setRoot(group); + +const App = () => { + const [count, setCount] = React.useState(2); + const [val, setVal] = React.useState(0); + + const buttons: JSX.Element[] = []; + + for (let i = 0; i < count; i++) { + buttons.push( + + ); + } + + return ( + + +