rename History -> Editor
This commit is contained in:
parent
532fc0c006
commit
5ec5574488
@ -11,6 +11,9 @@
|
||||
"pages": {
|
||||
"screening": {
|
||||
"nodes": {
|
||||
"base": {
|
||||
"remove": "削除"
|
||||
},
|
||||
"fetch_ToshoListed": {
|
||||
"title": "東証上場銘柄一覧"
|
||||
},
|
||||
|
@ -1,38 +1,59 @@
|
||||
import { useCallback } from "react";
|
||||
import { Background, Controls, ReactFlow, Node, useNodesState, useEdgesState, addEdge, Connection } from "@xyflow/react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Background, Controls, Edge, ReactFlow, Node, useNodesState, useEdgesState, Connection } from "@xyflow/react";
|
||||
import * as node from "./node/";
|
||||
|
||||
import "@xyflow/react/dist/style.css";
|
||||
|
||||
import { History, Impl as HistoryImpl } from "./context/History";
|
||||
import { Editor, Impl as EditorImpl } from "./context/Editor";
|
||||
|
||||
const nodeTypes = {
|
||||
fetch_ToshoListed: node.fetch.ToshoListed,
|
||||
present_TableDisplay: node.present.TableDisplay,
|
||||
};
|
||||
const initialNodes: Node[] = [
|
||||
{id: "n1", type: "fetch_ToshoListed", position: {x:0, y:0}, data: {prime: true, standard: true, growth: true}},
|
||||
{id: "n2", type: "present_TableDisplay", position: {x:0, y:100}, data: {}},
|
||||
{id: "n3", position: {x:0, y:200}, data: {label: "hello"}},
|
||||
{
|
||||
id: "n1",
|
||||
type: "fetch_ToshoListed",
|
||||
position: {x:0, y:0},
|
||||
data: {prime: true, standard: true, growth: true},
|
||||
deletable: false,
|
||||
},
|
||||
{
|
||||
id: "n2",
|
||||
type: "present_TableDisplay",
|
||||
position: {x:0, y:100},
|
||||
data: {},
|
||||
},
|
||||
{
|
||||
id: "n3",
|
||||
position: {x:0, y:200},
|
||||
data: {label: "hello"},
|
||||
},
|
||||
];
|
||||
|
||||
export default function Screening() {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([] as Edge[]);
|
||||
|
||||
const editor = useMemo(()=>new EditorImpl(setNodes, setEdges, 100), []);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(connection: Connection) => setEdges((eds) => addEdge(connection, eds)),
|
||||
[]
|
||||
);
|
||||
(conn: Connection)=>editor.connect(conn), []);
|
||||
const onDisconnect = useCallback(
|
||||
(conns: Edge[])=>editor.disconnect(conns.map((x)=>x as Connection)), []);
|
||||
const onNodesDelete = useCallback(
|
||||
(nodes: Node[])=>editor.remove(nodes.map((x)=>x.id)), []);
|
||||
|
||||
return (
|
||||
<History.Provider value={new HistoryImpl(setNodes)}>
|
||||
<Editor.Provider value={editor}>
|
||||
<div style={{ position: "absolute", left: 0, right: 0, top: 0, bottom: 0 }}>
|
||||
<ReactFlow
|
||||
nodeTypes={nodeTypes}
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesDelete={onNodesDelete}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesDelete={onDisconnect}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
fitView
|
||||
@ -41,6 +62,6 @@ export default function Screening() {
|
||||
<Background />
|
||||
</ReactFlow>
|
||||
</div>
|
||||
</History.Provider>
|
||||
</Editor.Provider>
|
||||
);
|
||||
}
|
||||
|
72
src/pages/context/Editor.tsx
Normal file
72
src/pages/context/Editor.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import { Dispatch, SetStateAction, createContext } from "react";
|
||||
import { Connection, EdgeType, NodeType, addEdge } from "@xyflow/react";
|
||||
|
||||
type Pos = {x: int, y: int,};
|
||||
|
||||
export interface Iface {
|
||||
add(type: string, pos: Pos, data: any): string;
|
||||
remove(id: string[]): void;
|
||||
modify(id: string, data: any): void;
|
||||
|
||||
connect(conn: Connection): void;
|
||||
disconnect(conn: Connection[]): void;
|
||||
};
|
||||
|
||||
export const Editor = createContext<Iface|undefined>(undefined);
|
||||
export default Editor;
|
||||
|
||||
type SetNodes = Dispatch<SetStateAction<NodeType[]>>;
|
||||
type SetEdges = Dispatch<SetStateAction<EdgeType[]>>;
|
||||
export class Impl implements Iface {
|
||||
_setNodes: SetNodes;
|
||||
_setEdges: SetEdges;
|
||||
_nextId: number = 0;
|
||||
|
||||
constructor(setNodes: SetNodes, setEdges: SetEdges, nextId: number = 0) {
|
||||
this._setNodes = setNodes;
|
||||
this._setEdges = setEdges;
|
||||
this._nextId = nextId;
|
||||
}
|
||||
|
||||
add(type: string, pos: Pos, data: any): string {
|
||||
const ret = this._genNextId();
|
||||
this._setNodes((nds)=>[...nds, {
|
||||
id: ret,
|
||||
type: type,
|
||||
pos: pos,
|
||||
data: data,
|
||||
}]);
|
||||
return ret;
|
||||
}
|
||||
remove(ids: string[]): void {
|
||||
this._setNodes((nds)=>nds.filter((x)=>!ids.includes(x.id)));
|
||||
}
|
||||
modify(id: string, data: any): void {
|
||||
this._setNodes(
|
||||
(nds)=>nds.map((n)=>n.id === id? {...n, data: data}: n)
|
||||
);
|
||||
}
|
||||
|
||||
connect(c: Connection): void {
|
||||
this._setEdges((eds)=>addEdge({
|
||||
...c,
|
||||
deletable: true,
|
||||
}, eds));
|
||||
}
|
||||
disconnect(cs: Connection[]): void {
|
||||
this._setEdges((eds)=>eds.filter((e)=>!cs.some(c=>matchEdgeAndConn(e, c))));
|
||||
}
|
||||
|
||||
_genNextId(): number {
|
||||
return "n"+(_nextId++);
|
||||
}
|
||||
};
|
||||
|
||||
function matchEdgeAndConn(e: Edge, c: Connection) {
|
||||
return (
|
||||
(e.source === c.source) &&
|
||||
((e.sourceHandle??null) == (c.sourceHandle??null)) &&
|
||||
(e.target == c.target) &&
|
||||
((e.targetHandle??null) == (c.targetHandle??null))
|
||||
);
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import { Dispatch, SetStateAction, createContext } from "react";
|
||||
import { Node, } from "@xyflow/react";
|
||||
|
||||
export interface Iface {
|
||||
push(id: string, data: any): void,
|
||||
};
|
||||
|
||||
export const History = createContext<Iface|undefined>(undefined);
|
||||
export default History;
|
||||
|
||||
export class Impl implements Iface {
|
||||
setNodes: Dispatch<SetStateAction<Node[]>>;
|
||||
|
||||
constructor(setNodes: Dispatch<SetStateAction<Node[]>>) {
|
||||
this.setNodes = setNodes;
|
||||
}
|
||||
push(id: string, data: any): void {
|
||||
this.setNodes(
|
||||
(nds)=>nds.map((n)=>n.id == id? {...n, data: data}: n)
|
||||
);
|
||||
}
|
||||
};
|
@ -1,14 +1,39 @@
|
||||
import { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ReactNode, useContext, useCallback } from "react";
|
||||
import { NodeProps } from "@xyflow/react";
|
||||
|
||||
import style from "./style.module.scss";
|
||||
|
||||
import Editor from "../context/Editor";
|
||||
|
||||
type Params = {
|
||||
title: ReactNode,
|
||||
children: ReactNode,
|
||||
node: NodeProps,
|
||||
};
|
||||
export default function Base(p: Params) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
const editor = useContext(Editor);
|
||||
if (editor === undefined) {
|
||||
throw new Error("no editor context");
|
||||
}
|
||||
|
||||
const onDelete = useCallback(()=>editor.remove([p.node.id]), [p.node]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{p.title}</div>
|
||||
<div className={style.node}>
|
||||
<div className={style.header}>
|
||||
<div className={style.title}>{p.title}</div>
|
||||
<div className={style.buttons}>
|
||||
<button onClick={onDelete} disabled={!p.node.deletable}>
|
||||
{t("pages.screening.nodes.base.remove")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.body}>
|
||||
{p.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -3,30 +3,35 @@ import { useTranslation } from "react-i18next";
|
||||
import { Handle, Node, NodeProps, Position } from "@xyflow/react";
|
||||
import Base from "./Base";
|
||||
|
||||
import History from "../context/History";
|
||||
import Editor from "../context/Editor";
|
||||
|
||||
export type ToshoListed = Node<{
|
||||
prime: boolean,
|
||||
standard: boolean,
|
||||
growth: boolean,
|
||||
}, "fetch_ToshoListed">;
|
||||
export function ToshoListed({id, data}: NodeProps<ToshoListed>) {
|
||||
export function ToshoListed(node: NodeProps<ToshoListed>) {
|
||||
const {id, data} = node;
|
||||
|
||||
const {t} = useTranslation();
|
||||
|
||||
const history = useContext(History);
|
||||
if (history === undefined) {
|
||||
throw new Error("no History context");
|
||||
const editor = useContext(Editor);
|
||||
if (editor === undefined) {
|
||||
throw new Error("no editor context");
|
||||
}
|
||||
|
||||
return (
|
||||
<Base title={t("pages.screening.nodes.fetch_ToshoListed.title")}>
|
||||
<Base
|
||||
node={node}
|
||||
title={t("pages.screening.nodes.fetch_ToshoListed.title")}
|
||||
>
|
||||
<div>
|
||||
<div><label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="prime"
|
||||
checked={data.prime}
|
||||
onChange={(e)=>history.push(id, {...data, prime: e.target.checked})}
|
||||
onChange={(e)=>editor.modify(id, {...data, prime: e.target.checked})}
|
||||
/>
|
||||
{t("terms.toshoPrime")}
|
||||
</label></div>
|
||||
@ -35,7 +40,7 @@ export function ToshoListed({id, data}: NodeProps<ToshoListed>) {
|
||||
type="checkbox"
|
||||
name="standard"
|
||||
checked={data.standard}
|
||||
onChange={(e)=>history.push(id, {...data, standard: e.target.checked})}
|
||||
onChange={(e)=>editor.modify(id, {...data, standard: e.target.checked})}
|
||||
/>
|
||||
{t("terms.toshoStandard")}
|
||||
</label></div>
|
||||
@ -44,7 +49,7 @@ export function ToshoListed({id, data}: NodeProps<ToshoListed>) {
|
||||
type="checkbox"
|
||||
name="growth"
|
||||
checked={data.growth}
|
||||
onChange={(e)=>history.push(id, {...data, growth: e.target.checked})}
|
||||
onChange={(e)=>editor.modify(id, {...data, growth: e.target.checked})}
|
||||
/>
|
||||
{t("terms.toshoGrowth")}
|
||||
</label></div>
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Handle, Position } from "@xyflow/react";
|
||||
import { Handle, NodeProps, Position } from "@xyflow/react";
|
||||
import Base from "./Base";
|
||||
|
||||
export function TableDisplay() {
|
||||
export function TableDisplay(node: NodeProps) {
|
||||
const {t} = useTranslation();
|
||||
return (
|
||||
<Base title={t("pages.screening.nodes.present_TableDisplay.title")}>
|
||||
<Base
|
||||
node={node}
|
||||
title={t("pages.screening.nodes.present_TableDisplay.title")}
|
||||
removable={true}
|
||||
>
|
||||
<div>
|
||||
<div><a href="#">{t("pages.screening.nodes.present_TableDisplay.open")}</a></div>
|
||||
</div>
|
||||
|
21
src/pages/node/style.module.scss
Normal file
21
src/pages/node/style.module.scss
Normal file
@ -0,0 +1,21 @@
|
||||
.node {
|
||||
background-color: #FFF9E5;
|
||||
border: 1px solid #4A9782;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
|
||||
.header {
|
||||
background-color: #DCD0A8;
|
||||
display: flex;
|
||||
padding: 0 .5rem;
|
||||
gap: 1rem;
|
||||
|
||||
.title {
|
||||
}
|
||||
.buttons {
|
||||
}
|
||||
}
|
||||
.body {
|
||||
padding: .5rem;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user