From 5ec5574488e8c06c81c49795f0f5b0fec075d10d Mon Sep 17 00:00:00 2001 From: falsycat Date: Sat, 12 Jul 2025 20:27:51 +0900 Subject: [PATCH] rename History -> Editor --- src/locales/jp.json | 3 ++ src/pages/Screening.tsx | 45 ++++++++++++++------ src/pages/context/Editor.tsx | 72 ++++++++++++++++++++++++++++++++ src/pages/context/History.tsx | 22 ---------- src/pages/node/Base.tsx | 33 +++++++++++++-- src/pages/node/fetch.tsx | 23 ++++++---- src/pages/node/present.tsx | 10 +++-- src/pages/node/style.module.scss | 21 ++++++++++ 8 files changed, 179 insertions(+), 50 deletions(-) create mode 100644 src/pages/context/Editor.tsx delete mode 100644 src/pages/context/History.tsx create mode 100644 src/pages/node/style.module.scss diff --git a/src/locales/jp.json b/src/locales/jp.json index 30ec264..f486352 100644 --- a/src/locales/jp.json +++ b/src/locales/jp.json @@ -11,6 +11,9 @@ "pages": { "screening": { "nodes": { + "base": { + "remove": "削除" + }, "fetch_ToshoListed": { "title": "東証上場銘柄一覧" }, diff --git a/src/pages/Screening.tsx b/src/pages/Screening.tsx index 9155c2c..6a48d34 100644 --- a/src/pages/Screening.tsx +++ b/src/pages/Screening.tsx @@ -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 ( - +
-
+ ); } diff --git a/src/pages/context/Editor.tsx b/src/pages/context/Editor.tsx new file mode 100644 index 0000000..52e7c5e --- /dev/null +++ b/src/pages/context/Editor.tsx @@ -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(undefined); +export default Editor; + +type SetNodes = Dispatch>; +type SetEdges = Dispatch>; +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)) + ); +} diff --git a/src/pages/context/History.tsx b/src/pages/context/History.tsx deleted file mode 100644 index 6674ccb..0000000 --- a/src/pages/context/History.tsx +++ /dev/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(undefined); -export default History; - -export class Impl implements Iface { - setNodes: Dispatch>; - - constructor(setNodes: Dispatch>) { - this.setNodes = setNodes; - } - push(id: string, data: any): void { - this.setNodes( - (nds)=>nds.map((n)=>n.id == id? {...n, data: data}: n) - ); - } -}; diff --git a/src/pages/node/Base.tsx b/src/pages/node/Base.tsx index a2a89ba..0489488 100644 --- a/src/pages/node/Base.tsx +++ b/src/pages/node/Base.tsx @@ -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 ( -
-
{p.title}
- {p.children} +
+
+
{p.title}
+
+ +
+
+
+ {p.children} +
); } diff --git a/src/pages/node/fetch.tsx b/src/pages/node/fetch.tsx index a501768..50ba131 100644 --- a/src/pages/node/fetch.tsx +++ b/src/pages/node/fetch.tsx @@ -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) { +export function ToshoListed(node: NodeProps) { + 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 ( - +
@@ -35,7 +40,7 @@ export function ToshoListed({id, data}: NodeProps) { 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")}
@@ -44,7 +49,7 @@ export function ToshoListed({id, data}: NodeProps) { 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")}
diff --git a/src/pages/node/present.tsx b/src/pages/node/present.tsx index a16ed03..09a49ec 100644 --- a/src/pages/node/present.tsx +++ b/src/pages/node/present.tsx @@ -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 ( - + diff --git a/src/pages/node/style.module.scss b/src/pages/node/style.module.scss new file mode 100644 index 0000000..856f577 --- /dev/null +++ b/src/pages/node/style.module.scss @@ -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; + } +}