From ea4f64cacb565323b660c67e4bf3a2e211881723 Mon Sep 17 00:00:00 2001 From: Dylan Vorster Date: Sat, 1 Jul 2023 23:54:22 -0600 Subject: [PATCH 1/2] rename boards --- README.md | 3 +- tornado-frontend/src/System.ts | 4 ++ tornado-frontend/src/hooks/useButton.tsx | 2 +- .../src/routes/content/ConceptBoardPage.tsx | 3 +- .../src/routes/manage/ConceptBoardsPage.tsx | 25 ++++++++++- tornado-frontend/src/stores/ConceptsStore.ts | 11 ++--- tornado-frontend/src/stores/DialogStore.tsx | 40 ++++++++++++++++- tornado-frontend/src/theme/theme-light.ts | 12 ++--- .../src/widgets/dialog/InputDialogWidget.tsx | 45 +++++++++++++++++++ .../src/widgets/layout/RootWidget.tsx | 1 + 10 files changed, 129 insertions(+), 17 deletions(-) create mode 100644 tornado-frontend/src/widgets/dialog/InputDialogWidget.tsx diff --git a/README.md b/README.md index 3ada632..b599c42 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Concept image-board software for ambitious creatives 🎨 ## What Tornado is self-hosted software for the web (currently in development) that provides digital media artists with the ability to create concept and reference boards. -You can simply drag in images, or paste images you have copied in your clipboard, and then arrange them as you see fit. +You can simply paste images you have copied in your clipboard, and then arrange them as you see fit. ## Features: @@ -22,6 +22,7 @@ You can simply drag in images, or paste images you have copied in your clipboard * Image resizing on the server * Canvas zoom and translate * Image paste from clipboard and translate +* Name and rename boards ![](./images/screenshot.png) ![](./images/screenshot2.png) diff --git a/tornado-frontend/src/System.ts b/tornado-frontend/src/System.ts index 5cae2c9..72cc8bf 100644 --- a/tornado-frontend/src/System.ts +++ b/tornado-frontend/src/System.ts @@ -60,6 +60,10 @@ export class System { makeObservable(this); } + updateTitle(title: string) { + document.title = `Tornado${title ? ` | ${title}` : ''}`; + } + toggleTheme() { if (this.theme.light) { this.theme = ThemeDark; diff --git a/tornado-frontend/src/hooks/useButton.tsx b/tornado-frontend/src/hooks/useButton.tsx index beabf34..acc187d 100644 --- a/tornado-frontend/src/hooks/useButton.tsx +++ b/tornado-frontend/src/hooks/useButton.tsx @@ -30,6 +30,6 @@ export const useButton = (props: UseButtonOptions, ref: React.RefObject { ref.current?.removeEventListener('click', l); }; - }, []); + }, [props.action, props.disabled]); return { loading }; }; diff --git a/tornado-frontend/src/routes/content/ConceptBoardPage.tsx b/tornado-frontend/src/routes/content/ConceptBoardPage.tsx index c6ea184..6f731ac 100644 --- a/tornado-frontend/src/routes/content/ConceptBoardPage.tsx +++ b/tornado-frontend/src/routes/content/ConceptBoardPage.tsx @@ -3,7 +3,6 @@ import { useEffect } from 'react'; import styled from '@emotion/styled'; import { useAuthenticated } from '../../hooks/useAuthenticated'; import { ContentViewWidget } from './widgets/ContentViewWidget'; -import { usePasteMedia } from '../../hooks/usePasteMedia'; import { useSystem } from '../../hooks/useSystem'; import { ConceptCanvasWidget } from './widgets/react-canvas/ConceptCanvasWidget'; import { useParams } from 'react-router-dom'; @@ -21,7 +20,7 @@ export const ConceptBoardPage: React.FC = observer((props return autorun(() => { const concept = system.conceptStore.getConcept(parseInt(id)); if (concept) { - concept.loadData(); + system.updateTitle(`Concept ${concept.board.name}`); } }); }, [id]); diff --git a/tornado-frontend/src/routes/manage/ConceptBoardsPage.tsx b/tornado-frontend/src/routes/manage/ConceptBoardsPage.tsx index 5a9cb34..d86cbc2 100644 --- a/tornado-frontend/src/routes/manage/ConceptBoardsPage.tsx +++ b/tornado-frontend/src/routes/manage/ConceptBoardsPage.tsx @@ -21,6 +21,7 @@ export const ConceptBoardsPage: React.FC = observer((props) => { const system = useSystem(); useEffect(() => { system.conceptStore.loadConcepts(); + system.updateTitle('Concepts'); }, []); return ( @@ -51,7 +52,21 @@ export const ConceptBoardsPage: React.FC = observer((props) => { render: ({ rowHover, row }) => { return ( - { + const name = await system.dialogStore.showInputDialog({ + title: 'Enter a new name', + desc: `You are about to rename concept board ${row.board.board.name}` + }); + if (!name) { + return; + } + return row.board.setName(name); + }} + /> + { @@ -96,4 +111,12 @@ namespace S { export const Buttons = styled.div` margin-bottom: 5px; `; + + export const RowButton = styled(ButtonWidget)` + margin-right: 5px; + + &:last-of-type { + margin-right: 0; + } + `; } diff --git a/tornado-frontend/src/stores/ConceptsStore.ts b/tornado-frontend/src/stores/ConceptsStore.ts index 00952c2..11c6eda 100644 --- a/tornado-frontend/src/stores/ConceptsStore.ts +++ b/tornado-frontend/src/stores/ConceptsStore.ts @@ -34,8 +34,11 @@ export class ConceptBoardModel extends BaseObserver { }); } - updateBoard(board: ConceptBoard) { - this.board = board; + async setName(name: string) { + this.board.name = name; + await this.options.client.updateConcept({ + board: this.board + }); } get id() { @@ -45,8 +48,6 @@ export class ConceptBoardModel extends BaseObserver { async delete() { return this.iterateListenersAsync((cb) => cb.deleted?.()); } - - async loadData() {} } export class ConceptsStore { @@ -69,7 +70,7 @@ export class ConceptsStore { }); return board; }, - update: (c, board) => board.updateBoard(c) + update: (c, board) => (board.board = c) }); } diff --git a/tornado-frontend/src/stores/DialogStore.tsx b/tornado-frontend/src/stores/DialogStore.tsx index 8f98f84..f06927e 100644 --- a/tornado-frontend/src/stores/DialogStore.tsx +++ b/tornado-frontend/src/stores/DialogStore.tsx @@ -2,15 +2,23 @@ import * as React from 'react'; import { Layer, LayerStore } from './LayerStore'; import { DialogWidget } from '../widgets/dialog/DialogWidget'; import { ButtonType } from '../widgets/forms/ButtonWidget'; +import { InputDialogWidget } from '../widgets/dialog/InputDialogWidget'; +import { Simulate } from 'react-dom/test-utils'; +import submit = Simulate.submit; export interface DialogStoreOptions { layerStore: LayerStore; } +export interface CommonDialogOptions { + title: string; + desc: string; +} + export class DialogStore { constructor(protected options: DialogStoreOptions) {} - async showConfirmDialog(options: { title: string; desc: string }): Promise { + async showConfirmDialog(options: CommonDialogOptions): Promise { return await new Promise((resolve) => { let confirm = false; const layer = new Layer(() => { @@ -48,4 +56,34 @@ export class DialogStore { this.options.layerStore.addLayer(layer); }); } + + async showInputDialog(options: CommonDialogOptions & { initial?: string }): Promise { + return await new Promise((resolve) => { + let value: string = null; + const layer = new Layer(() => { + return ( + { + value = val; + layer.dispose(); + }} + cancel={() => { + layer.dispose(); + }} + initial={options.initial || ''} + /> + ); + }); + const l1 = layer.registerListener({ + disposed: () => { + l1(); + resolve(value); + } + }); + + this.options.layerStore.addLayer(layer); + }); + } } diff --git a/tornado-frontend/src/theme/theme-light.ts b/tornado-frontend/src/theme/theme-light.ts index 911b245..c9e0abd 100644 --- a/tornado-frontend/src/theme/theme-light.ts +++ b/tornado-frontend/src/theme/theme-light.ts @@ -9,11 +9,11 @@ export const ThemeLight: TornadoTheme = { centerPanel: '#d2d2d2' }, dialog: { - background: '#131315', - border: '#000', - header: '#fff', - desc: '#5f5f60', - shadow: 'rgba(0,0,0,0.3)' + background: '#eaeaea', + border: '#b6b6b6', + header: '#000000', + desc: '#424242', + shadow: 'rgba(122,122,122,0.3)' }, text: { heading: '#000000', @@ -34,7 +34,7 @@ export const ThemeLight: TornadoTheme = { controls: { error: '#ab3838', field: { - background: '#c4c4c4', + background: '#e1e1e1', color: '#000000', placeholder: '#616162' }, diff --git a/tornado-frontend/src/widgets/dialog/InputDialogWidget.tsx b/tornado-frontend/src/widgets/dialog/InputDialogWidget.tsx new file mode 100644 index 0000000..5f59ed1 --- /dev/null +++ b/tornado-frontend/src/widgets/dialog/InputDialogWidget.tsx @@ -0,0 +1,45 @@ +import * as React from 'react'; +import { useState } from 'react'; +import { ButtonType } from '../forms/ButtonWidget'; +import { DialogWidget, DialogWidgetProps } from './DialogWidget'; +import { FieldWidget } from '../forms/FieldWidget'; + +export interface InputDialogWidgetProps extends Omit { + initial?: string; + submit: (value: string) => any; + cancel: () => any; +} + +export const InputDialogWidget: React.FC = (props) => { + const [value, setValue] = useState(props.initial); + + return ( + { + props.submit(value); + }, + type: ButtonType.PRIMARY + }, + { + label: 'Cancel', + action: async () => { + props.cancel(); + }, + type: ButtonType.NORMAL + } + ]} + > + { + setValue(v); + }} + placeholder="" + /> + + ); +}; diff --git a/tornado-frontend/src/widgets/layout/RootWidget.tsx b/tornado-frontend/src/widgets/layout/RootWidget.tsx index 44e8b77..598cc50 100644 --- a/tornado-frontend/src/widgets/layout/RootWidget.tsx +++ b/tornado-frontend/src/widgets/layout/RootWidget.tsx @@ -44,6 +44,7 @@ namespace S { export const Body = styled(BodyWidget)` flex-grow: 1; + overflow-y: auto; `; export const Layers = styled(LayersWidget)` From ed7967ef5791da7e3311011f512f8e991350aaa2 Mon Sep 17 00:00:00 2001 From: Dylan Vorster Date: Sat, 1 Jul 2023 23:57:07 -0600 Subject: [PATCH 2/2] small fix --- tornado-frontend/src/widgets/forms/FieldWidget.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tornado-frontend/src/widgets/forms/FieldWidget.tsx b/tornado-frontend/src/widgets/forms/FieldWidget.tsx index b192a66..3b9b106 100644 --- a/tornado-frontend/src/widgets/forms/FieldWidget.tsx +++ b/tornado-frontend/src/widgets/forms/FieldWidget.tsx @@ -16,7 +16,7 @@ export const FieldWidget: React.FC = (props) => { ref={props.forwardRef} placeholder={props.placeholder} type={props.type} - value={props.value?.trim() || ''} + value={props.value || ''} onChange={(event) => { let value = event.target.value; if (value.trim() === '') {