From 404133ed55af7ea4808ae7e925fd44a24f6eb308 Mon Sep 17 00:00:00 2001 From: Mikhail Chekan Date: Tue, 26 Sep 2023 10:59:16 +0000 Subject: [PATCH 1/7] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=B8=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=20=D0=BF=D1=80=D0=B8=20=D1=83=D0=B4=D0=B0?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B8=20=D1=81=D0=B8=D0=B3=D0=BD=D0=BB?= =?UTF-8?q?=D1=82=D0=BE=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ComponentDeleteModal.tsx | 16 ++++++++++++---- src/renderer/src/hooks/useDeleteComponent.ts | 18 ++++++++++-------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/renderer/src/components/ComponentDeleteModal.tsx b/src/renderer/src/components/ComponentDeleteModal.tsx index 0e99dfe37..0c60579d9 100644 --- a/src/renderer/src/components/ComponentDeleteModal.tsx +++ b/src/renderer/src/components/ComponentDeleteModal.tsx @@ -2,17 +2,23 @@ import React from 'react'; import { Modal } from './Modal/Modal'; +import { ComponentProto } from '@renderer/types/platform'; +import { Component as ComponentData } from '@renderer/types/diagram'; + interface ComponentDeleteModalProps { isOpen: boolean; - idx: string; - type: string; onClose: () => void; + + idx: string; + data: ComponentData; + proto: ComponentProto; onSubmit: (idx: string) => void; } export const ComponentDeleteModal: React.FC = ({ idx, - type, + data, + proto, onClose, onSubmit, ...props @@ -24,7 +30,9 @@ export const ComponentDeleteModal: React.FC = ({ onClose(); }; - const compoLabel = type ? `${type} ${idx}` : idx; + const type = data.type; + + const compoLabel = type && !proto.singletone ? `${type} ${idx}` : idx; return ( { const components = manager.useData('elements.components'); const [idx, setIdx] = useState(''); - const [type, setType] = useState(''); + const [data, setData] = useState({ type: '', parameters: {} }); + const [proto, setProto] = useState(systemComponent); const [isOpen, setIsOpen] = useState(false); @@ -17,14 +20,12 @@ export const useDeleteComponent = (editor: CanvasEditor | null, manager: EditorM const machine = editor!.container.machine; const component = components[idx]; if (typeof component === 'undefined') return; - const proto = machine.platform.data.components[component.type]; - if (typeof proto === 'undefined') { - console.error('non-existing %s %s', idx, component.type); - return; - } + // NOTE: systemComponent имеет флаг singletone, что и используется в форме + const proto = machine.platform.data.components[component.type] ?? systemComponent; setIdx(idx); - setType(component.type); + setData(component); + setProto(proto); setIsOpen(true); }; @@ -36,7 +37,8 @@ export const useDeleteComponent = (editor: CanvasEditor | null, manager: EditorM isOpen, onClose, idx, - type, + data, + proto, onSubmit, onRequestDeleteComponent, }; From e19ad5c20a8ceba8b5f97840e5d8115bd907f007 Mon Sep 17 00:00:00 2001 From: Mikhail Chekan Date: Tue, 26 Sep 2023 10:59:30 +0000 Subject: [PATCH 2/7] =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D0=BD=D0=BE=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B8=D0=BC=D0=B5=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=81=D0=B8=D0=BD=D0=B3=D0=BB=D1=82?= =?UTF-8?q?=D0=BE=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ComponentEditModal.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/renderer/src/components/ComponentEditModal.tsx b/src/renderer/src/components/ComponentEditModal.tsx index 0631f393d..1bebf6306 100644 --- a/src/renderer/src/components/ComponentEditModal.tsx +++ b/src/renderer/src/components/ComponentEditModal.tsx @@ -74,16 +74,19 @@ export const ComponentEditModal: React.FC = ({ sideLabel="Удалить" onSide={handleDelete} > - - + {proto.singletone ? ( + '' + ) : ( + + )} {Object.entries(proto.parameters ?? {}).map(([idx, param]) => { const name = param.name ?? idx; const value = parameters[name] ?? ''; From 14d26dad838eab10f50d1b5c68ab7b6e85668cac Mon Sep 17 00:00:00 2001 From: Mikhail Chekan Date: Sat, 30 Sep 2023 15:15:59 +0000 Subject: [PATCH 3/7] =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20UX=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit вернули подтверждение удаления удаление прямо из списка по средней кнопке явное выделение компонентов по любому клику --- src/renderer/src/components/Explorer.tsx | 39 ++++++++----- src/renderer/src/hooks/index.ts | 3 +- src/renderer/src/hooks/useDeleteComponent.ts | 45 --------------- ...Component.ts => useEditDeleteComponent.ts} | 55 ++++++++++++++----- 4 files changed, 66 insertions(+), 76 deletions(-) delete mode 100644 src/renderer/src/hooks/useDeleteComponent.ts rename src/renderer/src/hooks/{useEditComponent.ts => useEditDeleteComponent.ts} (56%) diff --git a/src/renderer/src/components/Explorer.tsx b/src/renderer/src/components/Explorer.tsx index f2b60cdc9..6c33d156b 100644 --- a/src/renderer/src/components/Explorer.tsx +++ b/src/renderer/src/components/Explorer.tsx @@ -1,14 +1,15 @@ import React, { useState } from 'react'; -import { twMerge } from 'tailwind-merge'; -import { ScrollableList } from './ScrollableList'; +import { twMerge } from 'tailwind-merge'; -import UnknownIcon from '@renderer/assets/icons/unknown.svg'; import { ReactComponent as AddIcon } from '@renderer/assets/icons/new transition.svg'; -import { EditorManager } from '@renderer/lib/data/EditorManager'; -import { CanvasEditor } from '@renderer/lib/CanvasEditor'; -import { useAddComponent, useDeleteComponent, useEditComponent } from '@renderer/hooks'; +import UnknownIcon from '@renderer/assets/icons/unknown.svg'; import { ComponentEditModal, ComponentAddModal, ComponentDeleteModal } from '@renderer/components'; +import { useAddComponent, useEditDeleteComponent } from '@renderer/hooks'; +import { CanvasEditor } from '@renderer/lib/CanvasEditor'; +import { EditorManager } from '@renderer/lib/data/EditorManager'; + +import { ScrollableList } from './ScrollableList'; interface ExplorerProps { editor: CanvasEditor | null; @@ -20,26 +21,33 @@ export const Explorer: React.FC = ({ editor, manager }) => { const components = manager.useData('elements.components'); const { onRequestAddComponent, ...addComponent } = useAddComponent(editor, manager); - const { onRequestEditComponent, ...editComponent } = useEditComponent(editor, manager); - const { onRequestDeleteComponent, ...deleteComponent } = useDeleteComponent(editor, manager); + const { onRequestEditComponent, onRequestDeleteComponent, editProps, deleteProps } = + useEditDeleteComponent(editor, manager); const [cursor, setCursor] = useState(null); - const onUnClick = (_e: React.MouseEvent) => { + const onUnClick = () => { setCursor(null); }; - const onCompClick = (key: string) => { + const onClick = (key: string) => { setCursor(key); }; + const onAuxClick = (key: string) => { + setCursor(key); + onRequestDeleteComponent(key); + }; + const onCompDblClick = (key: string) => { + setCursor(key); onRequestEditComponent(key); }; // TODO: контекстное меню? клонировать, переименовать, удалить const onCompRightClick = (key: string) => { - onRequestDeleteComponent(key); + setCursor(key); + onRequestEditComponent(key); }; const onAddClick = (e: React.MouseEvent) => { @@ -48,7 +56,7 @@ export const Explorer: React.FC = ({ editor, manager }) => { }; return ( -
+
onUnClick()}>

Компоненты

@@ -73,7 +81,8 @@ export const Explorer: React.FC = ({ editor, manager }) => {
onCompClick(key)} + onClick={() => onClick(key)} + onAuxClick={() => onAuxClick(key)} onDoubleClick={() => onCompDblClick(key)} onContextMenu={() => onCompRightClick(key)} > @@ -90,8 +99,8 @@ export const Explorer: React.FC = ({ editor, manager }) => {
- - + + {/* TODO:
diff --git a/src/renderer/src/hooks/index.ts b/src/renderer/src/hooks/index.ts index caa20dc03..476c39138 100644 --- a/src/renderer/src/hooks/index.ts +++ b/src/renderer/src/hooks/index.ts @@ -1,9 +1,8 @@ export * from './useAddComponent'; export * from './useClickOutside'; -export * from './useDeleteComponent'; export * from './useDiagramContextMenu'; export * from './useDiagramStateName'; -export * from './useEditComponent'; +export * from './useEditDeleteComponent'; export * from './useEditorManager'; export * from './useErrorModal'; export * from './useFileOperations'; diff --git a/src/renderer/src/hooks/useDeleteComponent.ts b/src/renderer/src/hooks/useDeleteComponent.ts deleted file mode 100644 index ff98bfc9d..000000000 --- a/src/renderer/src/hooks/useDeleteComponent.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { useState } from 'react'; - -import { CanvasEditor } from '@renderer/lib/CanvasEditor'; -import { EditorManager } from '@renderer/lib/data/EditorManager'; -import { Component as ComponentData } from '@renderer/types/diagram'; -import { systemComponent } from '@renderer/lib/data/PlatformManager'; - -export const useDeleteComponent = (editor: CanvasEditor | null, manager: EditorManager) => { - const components = manager.useData('elements.components'); - - const [idx, setIdx] = useState(''); - const [data, setData] = useState({ type: '', parameters: {} }); - const [proto, setProto] = useState(systemComponent); - - const [isOpen, setIsOpen] = useState(false); - - const onClose = () => setIsOpen(false); - - const onRequestDeleteComponent = (idx: string) => { - const machine = editor!.container.machine; - const component = components[idx]; - if (typeof component === 'undefined') return; - // NOTE: systemComponent имеет флаг singletone, что и используется в форме - const proto = machine.platform.data.components[component.type] ?? systemComponent; - - setIdx(idx); - setData(component); - setProto(proto); - setIsOpen(true); - }; - - const onSubmit = (idx: string) => { - editor!.container.machine.removeComponent(idx, false); - }; - - return { - isOpen, - onClose, - idx, - data, - proto, - onSubmit, - onRequestDeleteComponent, - }; -}; diff --git a/src/renderer/src/hooks/useEditComponent.ts b/src/renderer/src/hooks/useEditDeleteComponent.ts similarity index 56% rename from src/renderer/src/hooks/useEditComponent.ts rename to src/renderer/src/hooks/useEditDeleteComponent.ts index 01f308772..41ec27152 100644 --- a/src/renderer/src/hooks/useEditComponent.ts +++ b/src/renderer/src/hooks/useEditDeleteComponent.ts @@ -1,20 +1,22 @@ import { useState } from 'react'; import { CanvasEditor } from '@renderer/lib/CanvasEditor'; -import { Component as ComponentData } from '@renderer/types/diagram'; -import { systemComponent } from '@renderer/lib/data/PlatformManager'; import { EditorManager } from '@renderer/lib/data/EditorManager'; +import { systemComponent } from '@renderer/lib/data/PlatformManager'; +import { Component as ComponentData } from '@renderer/types/diagram'; -export const useEditComponent = (editor: CanvasEditor | null, manager: EditorManager) => { +export const useEditDeleteComponent = (editor: CanvasEditor | null, manager: EditorManager) => { const components = manager.useData('elements.components'); const [idx, setIdx] = useState(''); const [data, setData] = useState({ type: '', parameters: {} }); const [proto, setProto] = useState(systemComponent); - const [isOpen, setIsOpen] = useState(false); + const [isEditOpen, setIsEditOpen] = useState(false); + const onEditClose = () => setIsEditOpen(false); - const onClose = () => setIsOpen(false); + const [isDeleteOpen, setIsDeleteOpen] = useState(false); + const onDeleteClose = () => setIsDeleteOpen(false); const onRequestEditComponent = (idx: string) => { const machine = editor!.container.machine; @@ -36,7 +38,20 @@ export const useEditComponent = (editor: CanvasEditor | null, manager: EditorMan setData(component); setProto(proto); // setExistingComponents(existingComponents); - setIsOpen(true); + setIsEditOpen(true); + }; + + const onRequestDeleteComponent = (idx: string) => { + const machine = editor!.container.machine; + const component = components[idx]; + if (typeof component === 'undefined') return; + // NOTE: systemComponent имеет флаг singletone, что и используется в форме + const proto = machine.platform.data.components[component.type] ?? systemComponent; + + setIdx(idx); + setData(component); + setProto(proto); + setIsDeleteOpen(true); }; const onEdit = (idx: string, data: ComponentData, newName?: string) => { @@ -46,17 +61,29 @@ export const useEditComponent = (editor: CanvasEditor | null, manager: EditorMan const onDelete = (idx: string) => { editor!.container.machine.removeComponent(idx, false); - onClose(); + onEditClose(); }; return { - isOpen, - onClose, - idx, - data, - proto, - onEdit, - onDelete, + editProps: { + isOpen: isEditOpen, + onClose: onEditClose, + idx, + data, + proto, + onEdit, + onDelete: onRequestDeleteComponent, + }, + deleteProps: { + isOpen: isDeleteOpen, + onClose: onDeleteClose, + idx, + data, + proto, + onEdit, + onSubmit: onDelete, + }, + onRequestDeleteComponent, onRequestEditComponent, }; }; From 5d0ec78d7f985e252134aa0ea78e865935906020 Mon Sep 17 00:00:00 2001 From: Mikhail Chekan Date: Sat, 30 Sep 2023 15:24:37 +0000 Subject: [PATCH 4/7] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=BF=D0=BE=D1=80=D1=86=D0=B8=D0=B9=20=D0=B7=D0=BD=D0=B0?= =?UTF-8?q?=D1=87=D0=BA=D0=BE=D0=B2=20=D0=B2=20=D1=80=D0=B5=D0=B4=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B5=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82?= =?UTF-8?q?=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/components/CreateModal.tsx | 36 ++++++++++++--------- src/renderer/src/components/EventsModal.tsx | 21 +++++++----- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/renderer/src/components/CreateModal.tsx b/src/renderer/src/components/CreateModal.tsx index 23edfb218..761fdcb6d 100644 --- a/src/renderer/src/components/CreateModal.tsx +++ b/src/renderer/src/components/CreateModal.tsx @@ -1,27 +1,28 @@ import React, { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; - -import { ColorInput } from './Modal/ColorInput'; -import { Modal } from './Modal/Modal'; import { twMerge } from 'tailwind-merge'; + +import { ReactComponent as AddIcon } from '@renderer/assets/icons/add.svg'; +import { ReactComponent as SubtractIcon } from '@renderer/assets/icons/subtract.svg'; +import { Select, SelectOption } from '@renderer/components/UI'; import { CanvasEditor } from '@renderer/lib/CanvasEditor'; -import { TextInput } from './Modal/TextInput'; +import { EditorManager } from '@renderer/lib/data/EditorManager'; +import { operatorSet } from '@renderer/lib/data/PlatformManager'; +import { Condition } from '@renderer/lib/drawable/Condition'; +import { State } from '@renderer/lib/drawable/State'; import { Action, Condition as ConditionData, Event as StateEvent, Variable as VariableData, } from '@renderer/types/diagram'; -import { ReactComponent as AddIcon } from '@renderer/assets/icons/add.svg'; -import { ReactComponent as SubtractIcon } from '@renderer/assets/icons/subtract.svg'; -import { Select, SelectOption } from '@renderer/components/UI'; -import { Condition } from '@renderer/lib/drawable/Condition'; -import { State } from '@renderer/lib/drawable/State'; import { ArgumentProto } from '@renderer/types/platform'; -import { operatorSet } from '@renderer/lib/data/PlatformManager'; -import { EditorManager } from '@renderer/lib/data/EditorManager'; + import { defaultTransColor } from './DiagramEditor'; +import { ColorInput } from './Modal/ColorInput'; +import { Modal } from './Modal/Modal'; +import { TextInput } from './Modal/TextInput'; type ArgSet = { [k: string]: string }; type ArgFormEntry = { name: string; description?: string }; @@ -104,7 +105,10 @@ export const CreateModal: React.FC = ({ value: idx, label: (
- + {idx}
), @@ -118,7 +122,7 @@ export const CreateModal: React.FC = ({
{name}
@@ -133,7 +137,7 @@ export const CreateModal: React.FC = ({
{name}
@@ -152,7 +156,7 @@ export const CreateModal: React.FC = ({ name, true )} - className="mr-1 h-7 w-7" + className="mr-1 h-7 w-7 object-contain" /> {name}
@@ -477,7 +481,7 @@ export const CreateModal: React.FC = ({ } }, [dataDo]); - var method: Action[] = props.isCondition!; + const method: Action[] = props.isCondition!; //-----------------------------Функция на нажатие кнопки "Сохранить"----------------------------------- const [condOperator, setCondOperator] = useState(); const handleSubmit = hookHandleSubmit((formData) => { diff --git a/src/renderer/src/components/EventsModal.tsx b/src/renderer/src/components/EventsModal.tsx index 1e2cfeec4..8f1d1bcab 100644 --- a/src/renderer/src/components/EventsModal.tsx +++ b/src/renderer/src/components/EventsModal.tsx @@ -1,14 +1,16 @@ import React, { useEffect, useState } from 'react'; + import ReactModal, { Props } from 'react-modal'; import './Modal/style.css'; -import { EventSelection } from '../lib/drawable/Events'; -import { Action, Event } from '@renderer/types/diagram'; -import { State } from '@renderer/lib/drawable/State'; -import { CanvasEditor } from '@renderer/lib/CanvasEditor'; import { Select, SelectOption } from '@renderer/components/UI'; -import { ArgumentProto } from '@renderer/types/platform'; +import { CanvasEditor } from '@renderer/lib/CanvasEditor'; import { EditorManager } from '@renderer/lib/data/EditorManager'; +import { State } from '@renderer/lib/drawable/State'; +import { Action, Event } from '@renderer/types/diagram'; +import { ArgumentProto } from '@renderer/types/platform'; + +import { EventSelection } from '../lib/drawable/Events'; type ArgSet = { [k: string]: string }; type ArgFormEntry = { name: string; description?: string }; @@ -56,7 +58,10 @@ export const CreateEventsModal: React.FC = ({ value: idx, label: (
- + {idx}
), @@ -70,7 +75,7 @@ export const CreateEventsModal: React.FC = ({
{name}
@@ -85,7 +90,7 @@ export const CreateEventsModal: React.FC = ({
{name}
From 0dda6084e327c2b2ecdf4503c7353ade48f84f5c Mon Sep 17 00:00:00 2001 From: Mikhail Chekan Date: Sat, 30 Sep 2023 20:49:13 +0000 Subject: [PATCH 5/7] =?UTF-8?q?WIP=20=D0=BC=D0=B5=D1=82=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20(=D0=BA=D1=80=D0=BE=D0=BC=D0=B5=20canvas)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit теперь параметры label и labelColor отвечают за отрисовку метки компонента (текст и его цвет). прикручено везде, кроме канвы, там нужно аккуратно подойти --- demos/demo4 (schema v2).json | 8 +- .../src/components/ComponentEditModal.tsx | 46 +++++++++--- src/renderer/src/components/CreateModal.tsx | 10 +-- src/renderer/src/components/EventsModal.tsx | 5 +- src/renderer/src/components/Explorer.tsx | 8 +- ...PlatformManager.ts => PlatformManager.tsx} | 75 +++++++++++++++---- src/renderer/src/lib/data/StateMachine.ts | 25 +++++-- src/renderer/src/lib/drawable/Picto.ts | 28 +++---- 8 files changed, 136 insertions(+), 69 deletions(-) rename src/renderer/src/lib/data/{PlatformManager.ts => PlatformManager.tsx} (87%) diff --git a/demos/demo4 (schema v2).json b/demos/demo4 (schema v2).json index 2b2ca4fb3..10381a922 100644 --- a/demos/demo4 (schema v2).json +++ b/demos/demo4 (schema v2).json @@ -206,13 +206,17 @@ "components": { "diod1": { "parameters": { - "pin": "12" + "pin": "12", + "labelColor": "#0008f5", + "label": "1" }, "type": "LED" }, "button1": { "parameters": { - "pin": "4" + "pin": "4", + "labelColor": "#c70000", + "label": "1" }, "type": "Button" } diff --git a/src/renderer/src/components/ComponentEditModal.tsx b/src/renderer/src/components/ComponentEditModal.tsx index 1bebf6306..8f2d9adf3 100644 --- a/src/renderer/src/components/ComponentEditModal.tsx +++ b/src/renderer/src/components/ComponentEditModal.tsx @@ -1,9 +1,9 @@ import React, { useEffect, useState } from 'react'; -import { Modal } from './Modal/Modal'; - -import { ComponentProto } from '@renderer/types/platform'; import { Component as ComponentData } from '@renderer/types/diagram'; +import { ComponentProto } from '@renderer/types/platform'; + +import { Modal } from './Modal/Modal'; interface ComponentEditModalProps { isOpen: boolean; @@ -77,15 +77,37 @@ export const ComponentEditModal: React.FC = ({ {proto.singletone ? ( '' ) : ( - + <> + + + + )} {Object.entries(proto.parameters ?? {}).map(([idx, param]) => { const name = param.name ?? idx; diff --git a/src/renderer/src/components/CreateModal.tsx b/src/renderer/src/components/CreateModal.tsx index 761fdcb6d..c26c6e34b 100644 --- a/src/renderer/src/components/CreateModal.tsx +++ b/src/renderer/src/components/CreateModal.tsx @@ -105,10 +105,7 @@ export const CreateModal: React.FC = ({ value: idx, label: (
- + {machine.platform.getFullComponentIcon(idx, 'mr-1 h-7 w-7')} {idx}
), @@ -792,10 +789,7 @@ export const CreateModal: React.FC = ({ 'm-2 flex min-h-[3rem] w-36 items-center justify-around rounded-lg border-2 bg-neutral-700 px-1' )} > - + {machine.platform.getFullComponentIcon(data.component)}
= ({ value: idx, label: (
- + {machine.platform.getFullComponentIcon(idx, 'mr-1 h-7 w-7')} {idx}
), diff --git a/src/renderer/src/components/Explorer.tsx b/src/renderer/src/components/Explorer.tsx index 6c33d156b..a980cdd8f 100644 --- a/src/renderer/src/components/Explorer.tsx +++ b/src/renderer/src/components/Explorer.tsx @@ -3,7 +3,6 @@ import React, { useState } from 'react'; import { twMerge } from 'tailwind-merge'; import { ReactComponent as AddIcon } from '@renderer/assets/icons/new transition.svg'; -import UnknownIcon from '@renderer/assets/icons/unknown.svg'; import { ComponentEditModal, ComponentAddModal, ComponentDeleteModal } from '@renderer/components'; import { useAddComponent, useEditDeleteComponent } from '@renderer/hooks'; import { CanvasEditor } from '@renderer/lib/CanvasEditor'; @@ -86,12 +85,7 @@ export const Explorer: React.FC = ({ editor, manager }) => { onDoubleClick={() => onCompDblClick(key)} onContextMenu={() => onCompRightClick(key)} > - + {editor?.container.machine.platform?.getFullComponentIcon(key)}

{key}

)} diff --git a/src/renderer/src/lib/data/PlatformManager.ts b/src/renderer/src/lib/data/PlatformManager.tsx similarity index 87% rename from src/renderer/src/lib/data/PlatformManager.ts rename to src/renderer/src/lib/data/PlatformManager.tsx index dd90f335f..16435451d 100644 --- a/src/renderer/src/lib/data/PlatformManager.ts +++ b/src/renderer/src/lib/data/PlatformManager.tsx @@ -1,9 +1,16 @@ -import { Platform } from '@renderer/types/platform'; -import { icons, picto } from '../drawable/Picto'; +import UnknownIcon from '@renderer/assets/icons/unknown.svg'; import { Action, Condition, Event, Variable } from '@renderer/types/diagram'; -import { ComponentProto } from '@renderer/types/platform'; +import { Platform, ComponentProto } from '@renderer/types/platform'; + +import { icons, picto } from '../drawable/Picto'; import { stateStyle } from '../styles'; +export type VisualCompoData = { + component: string; + label?: string; + color?: string; +}; + export type ListEntry = { name: string; description?: string; @@ -46,11 +53,12 @@ export class PlatformManager { data!: Platform; /** - * Проекция названия компонента к его типу. + * Проекция названия компонента к его типу и метке. * Если платформа не видит проекцию, она будет считать - * переданное название типом компонента. + * переданное название типом компонента, + * а данные метки – пустыми. */ - nameToComponent: Map = new Map(); + nameToVisual: Map = new Map(); componentToIcon: Map = new Map(); eventToIcon: Map = new Map(); @@ -98,7 +106,7 @@ export class PlatformManager { } resolveComponent(name: string): string { - return this.nameToComponent.get(name) ?? name; + return this.nameToVisual.get(name)?.component ?? name; } getComponent(name: string, isType?: boolean): ComponentProto | undefined { @@ -169,6 +177,41 @@ export class PlatformManager { return icons.get(query)!.src; } + getFullComponentIcon(name: string, className?: string): React.ReactNode { + const query = this.nameToVisual.get(name) ?? { component: name }; + const iconQuery = this.getComponentIcon(query.component, false); + const icon = icons.get(iconQuery)!; + // console.log(['getComponentIcon', name, isName, query, icons.get(query)!.src]); + // return ; + return ( + + ; + {!query.label ? ( + '' + ) : ( + + {query.label} + + )} + + + ); + } + getEventIcon(component: string, method: string) { const icon = this.eventToIcon.get(`${component}/${method}`); if (icon && icons.has(icon)) { @@ -220,8 +263,8 @@ export class PlatformManager { drawEvent(ctx: CanvasRenderingContext2D, ev: Event, x: number, y: number) { let leftIcon: string | undefined = undefined; let rightIcon = 'unknown'; - let bgColor = '#3a426b'; - let fgColor = '#fff'; + const bgColor = '#3a426b'; + const fgColor = '#fff'; let argQuery: string = ''; if (ev.component === 'System') { @@ -264,9 +307,9 @@ export class PlatformManager { drawAction(ctx: CanvasRenderingContext2D, ac: Action, x: number, y: number, alpha?: number) { let leftIcon: string | undefined = undefined; let rightIcon = 'unknown'; - let bgColor = '#5b5f73'; - let fgColor = '#fff'; - let opacity = alpha ?? 1.0; + const bgColor = '#5b5f73'; + const fgColor = '#fff'; + const opacity = alpha ?? 1.0; let argQuery: string = ''; if (ac.component === 'System') { @@ -335,9 +378,9 @@ export class PlatformManager { y: number, alpha?: number ) { - let bgColor = '#5b7173'; - let fgColor = '#fff'; - let opacity = alpha ?? 1.0; + const bgColor = '#5b7173'; + const fgColor = '#fff'; + const opacity = alpha ?? 1.0; if (ac.type == 'component') { let leftIcon: string | undefined = undefined; @@ -379,7 +422,7 @@ export class PlatformManager { const mr = picto.eventMargin; const icoW = (picto.eventHeight + picto.eventMargin) / picto.scale; - let leftW = (this.measureCondition(ac.value[0]) + mr) / picto.scale; + const leftW = (this.measureCondition(ac.value[0]) + mr) / picto.scale; this.drawCondition(ctx, ac.value[0], x, y, alpha); picto.drawMono(ctx, x + leftW, y, { diff --git a/src/renderer/src/lib/data/StateMachine.ts b/src/renderer/src/lib/data/StateMachine.ts index 72ac58d0d..8b07d56f7 100644 --- a/src/renderer/src/lib/data/StateMachine.ts +++ b/src/renderer/src/lib/data/StateMachine.ts @@ -113,7 +113,11 @@ export class StateMachine extends EventEmitter { for (const name in items) { const component = items[name]; // this.components.set(name, new Component(component)); - this.platform.nameToComponent.set(name, component.type); + this.platform.nameToVisual.set(name, { + component: component.type, + label: component.parameters['label'], + color: component.parameters['labelColor'], + }); } } @@ -462,7 +466,9 @@ export class StateMachine extends EventEmitter { addComponent(name: string, type: string) { this.container.app.manager.addComponent(name, type); - this.platform.nameToComponent.set(name, type); + this.platform.nameToVisual.set(name, { + component: type, + }); this.container.isDirty = true; } @@ -470,6 +476,13 @@ export class StateMachine extends EventEmitter { editComponent(name: string, parameters: ComponentType['parameters'], newName?: string) { this.container.app.manager.editComponent(name, parameters); + const component = this.container.app.manager.data.elements.components[name]; + this.platform.nameToVisual.set(name, { + component: component.type, + label: component.parameters['label'], + color: component.parameters['labelColor'], + }); + if (newName) { this.renameComponent(name, newName); } @@ -479,10 +492,10 @@ export class StateMachine extends EventEmitter { private renameComponent(name: string, newName: string) { this.container.app.manager.renameComponent(name, newName); - const component = this.container.app.manager.data.elements.components[newName]; - this.platform.nameToComponent.set(newName, component.type); - this.platform.nameToComponent.delete(name); + const visualCompo = this.platform.nameToVisual.get(name)!; + this.platform.nameToVisual.set(newName, visualCompo); + this.platform.nameToVisual.delete(name); // А сейчас будет занимательное путешествие по схеме с заменой всего this.states.forEach((state) => { @@ -550,7 +563,7 @@ export class StateMachine extends EventEmitter { console.error('removeComponent purge not implemented yet'); } - this.platform.nameToComponent.delete(name); + this.platform.nameToVisual.delete(name); this.container.isDirty = true; } diff --git a/src/renderer/src/lib/drawable/Picto.ts b/src/renderer/src/lib/drawable/Picto.ts index e19c17ea5..37a439e9b 100644 --- a/src/renderer/src/lib/drawable/Picto.ts +++ b/src/renderer/src/lib/drawable/Picto.ts @@ -1,6 +1,6 @@ import InitialIcon from '@renderer/assets/icons/initial state.svg'; -import UnknownIcon from '@renderer/assets/icons/unknown-alt.svg'; import EdgeHandle from '@renderer/assets/icons/new transition.svg'; +import UnknownIcon from '@renderer/assets/icons/unknown-alt.svg'; import { Rectangle } from '@renderer/types/graphics'; import { drawImageFit, preloadImagesMap } from '../utils'; @@ -150,10 +150,10 @@ export class Picto { } drawMono(ctx: CanvasRenderingContext2D, x: number, y: number, ps: PictoProps) { - let rightIcon = ps.rightIcon; - let bgColor = ps.bgColor ?? '#3a426b'; - let fgColor = ps.fgColor ?? '#fff'; - let opacity = ps.opacity ?? 1.0; + const rightIcon = ps.rightIcon; + const bgColor = ps.bgColor ?? '#3a426b'; + const fgColor = ps.fgColor ?? '#fff'; + const opacity = ps.opacity ?? 1.0; // Рамка this.drawRect(ctx, x, y, this.eventHeight, this.eventHeight, bgColor, fgColor, opacity); @@ -169,10 +169,10 @@ export class Picto { } drawText(ctx: CanvasRenderingContext2D, x: number, y: number, ps: PictoProps) { - let text = ps.rightIcon; - let bgColor = ps.bgColor ?? '#3a426b'; - let fgColor = ps.fgColor ?? '#fff'; - let opacity = ps.opacity ?? 1.0; + const text = ps.rightIcon; + const bgColor = ps.bgColor ?? '#3a426b'; + const fgColor = ps.fgColor ?? '#fff'; + const opacity = ps.opacity ?? 1.0; const baseFontSize = 24; const w = this.textPadding * 2 + text.length * this.pxPerChar; @@ -195,11 +195,11 @@ export class Picto { } drawPicto(ctx: CanvasRenderingContext2D, x: number, y: number, ps: PictoProps) { - let leftIcon = ps.leftIcon; - let rightIcon = ps.rightIcon; - let bgColor = ps.bgColor ?? '#3a426b'; - let fgColor = ps.fgColor ?? '#fff'; - let opacity = ps.opacity ?? 1.0; + const leftIcon = ps.leftIcon; + const rightIcon = ps.rightIcon; + const bgColor = ps.bgColor ?? '#3a426b'; + const fgColor = ps.fgColor ?? '#fff'; + const opacity = ps.opacity ?? 1.0; // Рамка this.drawBorder(ctx, x, y, bgColor, fgColor, opacity); From b97882feaf39c8ff5bbf7140ddaedc45b72a99d4 Mon Sep 17 00:00:00 2001 From: Mikhail Chekan Date: Tue, 3 Oct 2023 10:03:51 +0000 Subject: [PATCH 6/7] =?UTF-8?q?=D0=BC=D0=B5=D1=82=D0=BA=D0=B8=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B8=20=D0=BD=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=BE=D0=B9?= =?UTF-8?q?=20=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=B3=20=D0=BF=D0=BB=D0=B0=D1=82=D1=84=D0=BE=D1=80=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/components/CreateModal.tsx | 3 +- src/renderer/src/components/EventsModal.tsx | 3 +- src/renderer/src/lib/data/PlatformManager.tsx | 83 +++++++++---------- .../src/lib/drawable/{Picto.ts => Picto.tsx} | 60 +++++++++++++- 4 files changed, 97 insertions(+), 52 deletions(-) rename src/renderer/src/lib/drawable/{Picto.ts => Picto.tsx} (82%) diff --git a/src/renderer/src/components/CreateModal.tsx b/src/renderer/src/components/CreateModal.tsx index 9630ac1fb..300309daf 100644 --- a/src/renderer/src/components/CreateModal.tsx +++ b/src/renderer/src/components/CreateModal.tsx @@ -237,8 +237,7 @@ export const CreateModal: React.FC = ({ const [argForm, setArgForm] = useState([]); const retrieveArgForm = (compo: string, method: string) => { - const compoType = machine.platform.resolveComponent(compo); - const component = machine.platform.data.components[compoType]; + const component = machine.platform.getComponent(compo); if (!component) return []; const argList: ArgumentProto[] | undefined = isEditingEvent diff --git a/src/renderer/src/components/EventsModal.tsx b/src/renderer/src/components/EventsModal.tsx index dd79e9c07..83b91f5ec 100644 --- a/src/renderer/src/components/EventsModal.tsx +++ b/src/renderer/src/components/EventsModal.tsx @@ -125,8 +125,7 @@ export const CreateEventsModal: React.FC = ({ const [argForm, setArgForm] = useState([]); const retrieveArgForm = (compo: string, method: string) => { - const compoType = machine.platform.resolveComponent(compo); - const component = machine.platform.data.components[compoType]; + const component = machine.platform.getComponent(compo); if (!component) return []; const argList: ArgumentProto[] | undefined = isEditingEvent diff --git a/src/renderer/src/lib/data/PlatformManager.tsx b/src/renderer/src/lib/data/PlatformManager.tsx index 16435451d..390b71304 100644 --- a/src/renderer/src/lib/data/PlatformManager.tsx +++ b/src/renderer/src/lib/data/PlatformManager.tsx @@ -1,8 +1,7 @@ -import UnknownIcon from '@renderer/assets/icons/unknown.svg'; import { Action, Condition, Event, Variable } from '@renderer/types/diagram'; import { Platform, ComponentProto } from '@renderer/types/platform'; -import { icons, picto } from '../drawable/Picto'; +import { MarkedIconData, icons, picto } from '../drawable/Picto'; import { stateStyle } from '../styles'; export type VisualCompoData = { @@ -105,13 +104,17 @@ export class PlatformManager { } } - resolveComponent(name: string): string { + resolveComponent(name: string) { + return this.nameToVisual.get(name) ?? { component: name }; + } + + resolveComponentType(name: string): string { return this.nameToVisual.get(name)?.component ?? name; } getComponent(name: string, isType?: boolean): ComponentProto | undefined { if (name == 'System') return systemComponent; - const query = isType ? name : this.resolveComponent(name); + const query = isType ? name : this.resolveComponentType(name); return this.data.components[query]; } @@ -161,7 +164,7 @@ export class PlatformManager { } getComponentIcon(name: string, isName?: boolean) { - const query = isName ? this.resolveComponent(name) : name; + const query = isName ? this.resolveComponentType(name) : name; const icon = this.componentToIcon.get(query); // console.log(['getComponentIcon', name, isName, icon]); if (icon && icons.has(icon)) { @@ -179,37 +182,13 @@ export class PlatformManager { getFullComponentIcon(name: string, className?: string): React.ReactNode { const query = this.nameToVisual.get(name) ?? { component: name }; - const iconQuery = this.getComponentIcon(query.component, false); - const icon = icons.get(iconQuery)!; + const iconQuery = { + ...query, + icon: this.getComponentIcon(query.component, false), + }; // console.log(['getComponentIcon', name, isName, query, icons.get(query)!.src]); // return ; - return ( - - ; - {!query.label ? ( - '' - ) : ( - - {query.label} - - )} - - - ); + return picto.getMarkedSvg(iconQuery, className); } getEventIcon(component: string, method: string) { @@ -222,7 +201,7 @@ export class PlatformManager { } getEventIconUrl(component: string, method: string, isName?: boolean) { - const compoQuery = isName ? this.resolveComponent(component) : component; + const compoQuery = isName ? this.resolveComponentType(component) : component; const query = this.getEventIcon(compoQuery, method); // console.log(['getEventIconUrl', component, isName, compoQuery, method, query, icons.get(query)!.src,]); return icons.get(query)!.src; @@ -238,7 +217,7 @@ export class PlatformManager { } getActionIconUrl(component: string, method: string, isName?: boolean) { - const compoQuery = isName ? this.resolveComponent(component) : component; + const compoQuery = isName ? this.resolveComponentType(component) : component; const query = this.getActionIcon(compoQuery, method); // console.log(['getActionIconUrl', component, isName, compoQuery, method, query, icons.get(query)!.src,]); return icons.get(query)!.src; @@ -254,14 +233,14 @@ export class PlatformManager { } getVariableIconUrl(component: string, method: string, isName?: boolean) { - const compoQuery = isName ? this.resolveComponent(component) : component; + const compoQuery = isName ? this.resolveComponentType(component) : component; const query = this.getVariableIcon(compoQuery, method); // console.log(['getEventIconUrl', component, isName, compoQuery, method, query, icons.get(query)!.src,]); return icons.get(query)!.src; } drawEvent(ctx: CanvasRenderingContext2D, ev: Event, x: number, y: number) { - let leftIcon: string | undefined = undefined; + let leftIcon: string | MarkedIconData | undefined = undefined; let rightIcon = 'unknown'; const bgColor = '#3a426b'; const fgColor = '#fff'; @@ -271,8 +250,12 @@ export class PlatformManager { // ev.method === 'onEnter' || ev.method === 'onExit' rightIcon = ev.method; } else { - const component = this.resolveComponent(ev.component); - leftIcon = this.getComponentIcon(component); + const compoData = this.resolveComponent(ev.component); + const component = compoData.component; + leftIcon = { + ...compoData, + icon: this.getComponentIcon(component), + }; rightIcon = this.getEventIcon(component, ev.method); const parameterList = this.data.components[component]?.signals[ev.method]?.parameters; @@ -305,7 +288,7 @@ export class PlatformManager { } drawAction(ctx: CanvasRenderingContext2D, ac: Action, x: number, y: number, alpha?: number) { - let leftIcon: string | undefined = undefined; + let leftIcon: string | MarkedIconData | undefined = undefined; let rightIcon = 'unknown'; const bgColor = '#5b5f73'; const fgColor = '#fff'; @@ -315,8 +298,12 @@ export class PlatformManager { if (ac.component === 'System') { rightIcon = ac.method; } else { - const component = this.resolveComponent(ac.component); - leftIcon = this.getComponentIcon(component); + const compoData = this.resolveComponent(ac.component); + const component = compoData.component; + leftIcon = { + ...compoData, + icon: this.getComponentIcon(component), + }; rightIcon = this.getActionIcon(component, ac.method); const parameterList = this.data.components[component]?.methods[ac.method]?.parameters; @@ -383,7 +370,7 @@ export class PlatformManager { const opacity = alpha ?? 1.0; if (ac.type == 'component') { - let leftIcon: string | undefined = undefined; + let leftIcon: string | MarkedIconData | undefined = undefined; let rightIcon = 'unknown'; // FIXME: столько проверок ради простой валидации... @@ -396,8 +383,12 @@ export class PlatformManager { if (vr.component === 'System') { rightIcon = vr.method; } else { - const component = this.resolveComponent(vr.component); - leftIcon = this.getComponentIcon(component); + const compoData = this.resolveComponent(vr.component); + const component = compoData.component; + leftIcon = { + ...compoData, + icon: this.getComponentIcon(component), + }; rightIcon = this.getVariableIcon(component, vr.method); } } diff --git a/src/renderer/src/lib/drawable/Picto.ts b/src/renderer/src/lib/drawable/Picto.tsx similarity index 82% rename from src/renderer/src/lib/drawable/Picto.ts rename to src/renderer/src/lib/drawable/Picto.tsx index 37a439e9b..5447fdaed 100644 --- a/src/renderer/src/lib/drawable/Picto.ts +++ b/src/renderer/src/lib/drawable/Picto.tsx @@ -5,6 +5,12 @@ import { Rectangle } from '@renderer/types/graphics'; import { drawImageFit, preloadImagesMap } from '../utils'; +export type MarkedIconData = { + icon: string; + label?: string; + color?: string; +}; + let imagesLoaded = false; export const icons: Map = new Map(); @@ -62,7 +68,7 @@ export function preloadPicto(callback: () => void) { } export type PictoProps = { - leftIcon?: string; + leftIcon?: string | MarkedIconData; rightIcon: string; bgColor?: string; fgColor?: string; @@ -82,8 +88,10 @@ export class Picto { return imagesLoaded; } - drawImage(ctx: CanvasRenderingContext2D, iconName: string, bounds: Rectangle) { + drawImage(ctx: CanvasRenderingContext2D, iconData: string | MarkedIconData, bounds: Rectangle) { // console.log([iconName, icons.has(iconName)]); + const isMarked = !(typeof iconData === 'string'); + const iconName = isMarked ? iconData.icon : iconData; const image = icons.get(iconName); if (!image) return; @@ -93,9 +101,57 @@ export class Picto { width: bounds.width / this.scale, height: bounds.height / this.scale, }); + if (isMarked && iconData.label) { + const { x, y, width, height } = bounds; + const tX = x + width / this.scale; + const tY = y + (height - 1) / this.scale; + ctx.save(); + ctx.font = `600 ${16 / this.scale}px/0 Fira Mono`; + ctx.fillStyle = iconData.color ?? 'white'; + ctx.strokeStyle = 'white'; + ctx.lineWidth = 0.5 / this.scale; + ctx.textAlign = 'end'; + ctx.textBaseline = 'alphabetic'; + + ctx.fillText(iconData.label, tX, tY); + ctx.strokeText(iconData.label, tX, tY); + + ctx.restore(); + } ctx.closePath(); } + getMarkedSvg(data: MarkedIconData, className?: string) { + const icon = icons.get(data.icon); + return ( + + ; + {!data.label ? ( + '' + ) : ( + + {data.label} + + )} + + + ); + } + // TODO: все перечисленные ниже функции нужно вернуть в законные места eventWidth = 100; From 14e876a664048b41428b3cc30ad8a0134676a808 Mon Sep 17 00:00:00 2001 From: Mikhail Chekan Date: Tue, 3 Oct 2023 14:21:05 +0000 Subject: [PATCH 7/7] =?UTF-8?q?=D0=BD=D0=B5=D0=BC=D0=BD=D0=BE=D0=B3=D0=BE?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B2=20Picto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/lib/drawable/Picto.tsx | 28 ++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/lib/drawable/Picto.tsx b/src/renderer/src/lib/drawable/Picto.tsx index 5447fdaed..9134ab700 100644 --- a/src/renderer/src/lib/drawable/Picto.tsx +++ b/src/renderer/src/lib/drawable/Picto.tsx @@ -88,9 +88,16 @@ export class Picto { return imagesLoaded; } + /** + * Рисует масштабированный значок на canvas. + * + * @param ctx Контекст canvas, в котором рисуем + * @param iconData Название значка или контейнер с данными для метки + * @param bounds Координаты и размер рамки + */ drawImage(ctx: CanvasRenderingContext2D, iconData: string | MarkedIconData, bounds: Rectangle) { // console.log([iconName, icons.has(iconName)]); - const isMarked = !(typeof iconData === 'string'); + const isMarked = typeof iconData !== 'string'; const iconName = isMarked ? iconData.icon : iconData; const image = icons.get(iconName); if (!image) return; @@ -121,6 +128,14 @@ export class Picto { ctx.closePath(); } + /** + * Генерирует SVG-ноду для значка с меткой. + * По сути, дублирует {@link drawImage} вне canvas. + * + * @param data Контейнер с данными значка + * @param className Атрибут class для генерируемой ноды (дополнительно) + * @returns JSX-нода со значком + */ getMarkedSvg(data: MarkedIconData, className?: string) { const icon = icons.get(data.icon); return ( @@ -152,8 +167,6 @@ export class Picto { ); } - // TODO: все перечисленные ниже функции нужно вернуть в законные места - eventWidth = 100; eventHeight = 40; eventMargin = 5; @@ -250,6 +263,15 @@ export class Picto { ctx.restore(); } + /** + * Рисует масштабированную пиктограмму на canvas. + * Главная функция в этом классе. + * + * @param ctx Контекст canvas, в котором рисуем + * @param x X-координата + * @param y Y-координата + * @param ps Контейнер с параметрами пиктограммы + */ drawPicto(ctx: CanvasRenderingContext2D, x: number, y: number, ps: PictoProps) { const leftIcon = ps.leftIcon; const rightIcon = ps.rightIcon;