浏览代码

first somewhat working version of rountangle editor

Jakob Pietron 2 年之前
父节点
当前提交
5fed6055e3

文件差异内容过多而无法显示
+ 816 - 317
package-lock.json


+ 6 - 3
package.json

@@ -1,6 +1,5 @@
 {
 	"devDependencies": {
-		"@types/mocha": "^9.1.1",
 		"@emotion/react": "^11.10.0",
 		"@mantine/core": "^5.2.3",
 		"@mantine/hooks": "^5.2.3",
@@ -9,8 +8,9 @@
 		"@types/d3-drag": "^3.0.1",
 		"@types/d3-force": "^3.0.3",
 		"@types/d3-selection": "^3.0.3",
+		"@types/mocha": "^9.1.1",
 		"@types/node": "^18.6.1",
-		"@types/react": "^18.0.17",
+		"@types/react": "^18.0.21",
 		"@types/react-dom": "^18.0.6",
 		"mocha": "^10.0.0",
 		"nyc": "^15.1.0",
@@ -22,6 +22,7 @@
 	"dependencies": {
 		"buffer": "^6.0.3",
 		"crypto-browserify": "^3.12.0",
+		"css-loader": "^6.7.1",
 		"d3": "^7.6.1",
 		"d3-drag": "^3.0.0",
 		"d3-force": "^3.0.0",
@@ -31,8 +32,10 @@
 		"react": "^18.2.0",
 		"react-dom": "^18.2.0",
 		"stream-browserify": "^3.0.0",
+		"style-loader": "^3.3.1",
 		"ts-node": "^10.9.1",
-		"typescript": "^4.7.4"
+		"typescript": "^4.7.4",
+		"uuid": "^9.0.0"
 	},
 	"scripts": {
 		"test": "mocha --require ts-node/register './src/**/*.test.ts'",

+ 3 - 0
src/frontend/app.css

@@ -0,0 +1,3 @@
+html,body {
+    min-height: 100vh;
+}

+ 2 - 0
src/frontend/app.tsx

@@ -13,6 +13,7 @@ import {Version, initialVersion} from "../onion/version";
 import {Delta} from "../onion/delta";
 import {CompositeDelta} from "../onion/composite_delta";
 import {NodeCreation, NodeDeletion, EdgeCreation, EdgeUpdate} from "../onion/primitive_delta";
+import {RountangleEditor} from "./rountangleEditor/RountangleEditor";
 
 class D3StateManipulator implements GraphStateManipulator {
   readonly setGraph: (cb: (prevGraph: GraphType) => GraphType) => void;
@@ -239,6 +240,7 @@ export function App() {
     <Stack>
       <Title order={2}>Onion VCS Demo</Title>
       <Branch getUuid={getUuid} />
+      <RountangleEditor />
     </Stack>
   );
 }

+ 2 - 0
src/frontend/index.tsx

@@ -1,5 +1,7 @@
 import * as React from 'react';
 import {createRoot} from 'react-dom/client';
+import './rountangleEditor/RountangleEditor.css';
+import './app.css';
 
 import {App} from "./app";
 

+ 8 - 0
src/frontend/rountangleEditor/RountangleActions.ts

@@ -0,0 +1,8 @@
+
+interface CreateRountangle {tag: 'createRountangle', id: string, name: string, posX: number, posY: number}
+interface MoveRountangle   {tag: 'moveRountangle',   id: string, newPosX: number, newPosY: number}
+
+export type RountangleAction =
+    Readonly<CreateRountangle>
+    | Readonly<MoveRountangle>
+;

+ 86 - 0
src/frontend/rountangleEditor/RountangleComponent.tsx

@@ -0,0 +1,86 @@
+import * as React from "react";
+import {RountangleAction} from "./RountangleActions";
+
+export interface RountangleProps {
+    id:       string;
+    name:     string;
+    posX:     number;
+    posY:     number;
+    dispatch: (action: RountangleAction) => void;
+}
+
+interface RountangleState {
+    dragging:    boolean;
+    moved:       boolean;
+}
+
+export class RountangleComponent extends React.Component<RountangleProps, RountangleState> {
+    shouldComponentUpdate(nextProps: Readonly<RountangleProps>, nextState: Readonly<RountangleState>, nextContext: any): boolean {
+        const compare = this.props.posX !== nextProps.posX
+            || this.props.posY !== nextProps.posY;
+
+        console.log(`${this.props.name}: ${compare}`);
+
+        return this.props.posX !== nextProps.posX
+            || this.props.posY !== nextProps.posY;
+    }
+
+    constructor(props: RountangleProps) {
+        super(props);
+        this.state = {
+            dragging: false,
+            moved:    false
+        }
+    }
+
+    onPointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
+        // only left mouse button
+        if (event.button !== 0) return;
+
+        event.currentTarget.setPointerCapture(event.pointerId);
+
+        this.setState({dragging: true, moved: false});
+        event.stopPropagation();
+        event.preventDefault();
+    }
+
+    onPointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
+        if (!this.state.dragging) return;
+        this.setState({moved: true});
+        this.props.dispatch({
+            tag: 'moveRountangle',
+            id: this.props.id,
+            newPosX: this.props.posX + event.movementX,
+            newPosY: this.props.posY + event.movementY
+        });
+
+        event.stopPropagation();
+        event.preventDefault();
+    }
+
+    onPointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
+        event.currentTarget.releasePointerCapture(event.pointerId);
+        event.stopPropagation();
+        event.preventDefault();
+
+        // only left mouse button
+        if (event.button !== 0) return;
+        this.setState({dragging: false, moved: false});
+    }
+
+    render() {
+        return(
+          <div
+              className={'sce-rountangle'}
+              style={{ left: this.props.posX, top: this.props.posY }}
+              onPointerDown={this.onPointerDown}
+              onPointerMove={this.onPointerMove}
+              onPointerUp={this.onPointerUp}
+          >
+              <div className={'sce-ruontangle-name'}>
+                  {this.props.name}
+              </div>
+          </div>
+        );
+    }
+}

+ 19 - 0
src/frontend/rountangleEditor/RountangleEditor.css

@@ -0,0 +1,19 @@
+.sce-background {
+    background: lightgrey;
+    height: 500px;
+    position: relative;
+}
+.sce-rountangle {
+    position: absolute;
+    width: 100px;
+    height: 66px;
+    background: white;
+    border: 1px solid black;
+    border-radius: 5px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    text-align: center;
+}
+.sce-ruontangle-name {
+}

+ 44 - 0
src/frontend/rountangleEditor/RountangleEditor.tsx

@@ -0,0 +1,44 @@
+import * as React from "react";
+import {RountangleComponent} from "./RountangleComponent";
+import {RountangleStore} from "./RountangleStore";
+import {generateRandomGUID} from "../../util/uniqueID";
+
+interface RountangleEditorState {
+    store: RountangleStore;
+}
+
+
+export class RountangleEditor extends React.Component<{}, RountangleEditorState> {
+    constructor(props) {
+        super(props);
+        this.state = {store: new RountangleStore()};
+        this.state.store.dispatch({tag: 'createRountangle', id: generateRandomGUID(), posX: 10, posY: 50, name:'ABC'})
+        this.state.store.dispatch({tag: 'createRountangle', id: generateRandomGUID(), posX: 250, posY: 50, name:'DEF'})
+    }
+
+    render() {
+        return(
+           <div>
+               <h2>State Charts Editor</h2>
+               <div className={'sce-background'}>
+                   {
+                       Object.entries(this.state.store.getRountangles()).map(entry => {
+                            const [id, rountangle] = entry;
+                            return <RountangleComponent
+                                key={id}
+                                id={id}
+                                name={rountangle.name}
+                                posX={rountangle.posX}
+                                posY={rountangle.posY}
+                                dispatch={action => {
+                                    this.state.store.dispatch(action);
+                                    this.forceUpdate();
+                                }}
+                            />
+                       })
+                   }
+               </div>
+           </div>
+        )
+    }
+}

+ 36 - 0
src/frontend/rountangleEditor/RountangleStore.ts

@@ -0,0 +1,36 @@
+import {RountangleAction} from "./RountangleActions";
+
+interface Rountangle {
+    name: string;
+    posX: number;
+    posY: number;
+}
+
+export class RountangleStore {
+    private state: Record<string, Rountangle>;
+
+    constructor() {
+        this.state = {};
+    }
+
+    dispatch(action: RountangleAction) {
+        console.log(action);
+        switch (action.tag) {
+            case "createRountangle":
+                this.state = {
+                    ...this.state,
+                    [action.id]: {name: action.name, posX: action.posX, posY: action.posY}
+                };
+                break;
+            case 'moveRountangle':
+                this.state = {
+                    ...this.state,
+                    [action.id]: {...this.state[action.id], posX: action.newPosX, posY: action.newPosY}
+                }
+        }
+    }
+
+    getRountangles() {
+        return this.state;
+    }
+}

+ 6 - 0
src/util/uniqueID.ts

@@ -0,0 +1,6 @@
+import {v4 as uuidV4} from 'uuid';
+
+export function generateRandomGUID(): string {
+    return uuidV4().toUpperCase();
+}
+

+ 4 - 0
webpack.config.js

@@ -10,6 +10,10 @@ module.exports = {
         use: 'ts-loader',
         exclude: /node_modules/,
       },
+      {
+        test: /\.css$/i,
+        use: ["style-loader", "css-loader"],
+      },
     ],
   },
   resolve: {