From 135d46bd0ad90613efd7c4547082f866803c7029 Mon Sep 17 00:00:00 2001 From: simon Date: Sat, 8 Jul 2023 01:41:41 +0800 Subject: [PATCH] refactor the ui components --- index.html | 4 +- package-lock.json | 7 ++ package.json | 1 + src/App.ts | 39 +++---- src/components/PictureCanvas.ts | 104 ++++++++++++++---- src/components/PixelEditor.ts | 31 ++++-- src/components/index.ts | 10 +- .../{ => uiControls/bottom}/LoadButton.ts | 6 +- .../{ => uiControls/bottom}/SaveButton.ts | 8 +- src/components/uiControls/bottom/index.ts | 20 ++++ .../{ => uiControls/left}/ColorSelect.ts | 5 +- src/components/uiControls/left/ToolButton.ts | 22 ++++ .../{ => uiControls/left}/ToolSelect.ts | 4 +- src/components/uiControls/left/index.ts | 26 +++++ .../uiControls/right/ClearButton.ts | 20 ++++ .../{ => uiControls/right}/UndoButton.ts | 4 +- src/components/uiControls/right/index.ts | 23 ++++ .../uiControls/top/SetSizeButton.ts | 23 ++++ src/components/uiControls/top/index.ts | 25 +++++ src/main.ts | 2 +- src/models/Picture.ts | 1 + src/models/reducers.ts | 2 +- src/style.css | 30 +++-- src/types.ts | 24 +++- src/utils/createElement.ts | 4 +- src/utils/drawHelpers.ts | 11 +- tsconfig.json | 5 + vite.config.ts | 10 ++ 28 files changed, 371 insertions(+), 100 deletions(-) rename src/components/{ => uiControls/bottom}/LoadButton.ts (64%) rename src/components/{ => uiControls/bottom}/SaveButton.ts (78%) create mode 100644 src/components/uiControls/bottom/index.ts rename src/components/{ => uiControls/left}/ColorSelect.ts (83%) create mode 100644 src/components/uiControls/left/ToolButton.ts rename src/components/{ => uiControls/left}/ToolSelect.ts (86%) create mode 100644 src/components/uiControls/left/index.ts create mode 100644 src/components/uiControls/right/ClearButton.ts rename src/components/{ => uiControls/right}/UndoButton.ts (80%) create mode 100644 src/components/uiControls/right/index.ts create mode 100644 src/components/uiControls/top/SetSizeButton.ts create mode 100644 src/components/uiControls/top/index.ts create mode 100644 vite.config.ts diff --git a/index.html b/index.html index 232eed3..3d5a2b3 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,9 @@ Pixel Editor -
+
+

Pixel Editor

+
diff --git a/package-lock.json b/package-lock.json index 700a43a..c0c7794 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "pixel-editor", "version": "0.0.0", "devDependencies": { + "@types/node": "^20.4.0", "typescript": "^5.0.2", "vite": "^4.4.0" } @@ -364,6 +365,12 @@ "node": ">=12" } }, + "node_modules/@types/node": { + "version": "20.4.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.0.tgz", + "integrity": "sha512-jfT7iTf/4kOQ9S7CHV9BIyRaQqHu67mOjsIQBC3BKZvzvUB6zLxEwJ6sBE3ozcvP8kF6Uk5PXN0Q+c0dfhGX0g==", + "dev": true + }, "node_modules/esbuild": { "version": "0.18.11", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.11.tgz", diff --git a/package.json b/package.json index ec12b0f..262d268 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "devDependencies": { + "@types/node": "^20.4.0", "typescript": "^5.0.2", "vite": "^4.4.0" } diff --git a/src/App.ts b/src/App.ts index 44046f7..ed40ba3 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,22 +1,23 @@ -import { - ToolSelect, - ColorSelect, - SaveButton, - LoadButton, - UndoButton, - PixelEditor, -} from "./components"; +import { SaveButton, LoadButton, UndoButton, PixelEditor } from "./components"; + +import SetSizeButtons from "./components/uiControls/top"; +import ToolBar from "./components/uiControls/left"; +import RightControls from "./components/uiControls/right"; +import BottomControls from "./components/uiControls/bottom"; import { draw, fill, rectangle, pick } from "./utils/drawHelpers"; -import { EditorState } from "./types"; -import Picture from "./models/Picture"; +import { EditorState, ActionType } from "./types"; +import Picture from "./models/picture"; import { historyUpdateState } from "./models/reducers"; const initialState: EditorState = { + currentTool: "draw", + currentSize: 64, + size: 128, tool: "draw", color: "#000000", - picture: Picture.empty(60, 30, "#f0f0f0"), + picture: Picture.empty(128, 128, "#f0f0f000"), done: [], doneAt: 0, }; @@ -29,13 +30,12 @@ const baseTools = { }; // UI components -const baseControls = [ - ToolSelect, - ColorSelect, - SaveButton, - LoadButton, - UndoButton, -]; +const baseControls = { + LeftControls: ToolBar, + TopControls: SetSizeButtons, + RightControls, + BottomControls, +}; function startPixelEditor({ state = initialState, @@ -45,7 +45,8 @@ function startPixelEditor({ const app = new PixelEditor(state, { tools, controls, - dispatch(action: { undo: boolean; picture: Picture }) { + dispatch(action: ActionType) { + // TODO: add 'clear' action reducer state = historyUpdateState(state, action); app.syncState(state); }, diff --git a/src/components/PictureCanvas.ts b/src/components/PictureCanvas.ts index 3200d42..6f7a46d 100644 --- a/src/components/PictureCanvas.ts +++ b/src/components/PictureCanvas.ts @@ -1,70 +1,106 @@ import elt from "../utils/createElement"; import { Position } from "../types"; -import Picture from "../models/Picture"; +import Picture from "../models/picture"; import { drawPicture } from "../utils/drawHelpers"; -// A component holds canvas that only knows current picture -// It adds mouse and touch events handlers when constructs +// A canvas component that only knows current picture class PictureCanvas { - public dom: HTMLCanvasElement; + public dom: HTMLElement; + public canvas: HTMLCanvasElement; + private background: HTMLCanvasElement; public picture: Picture; - private scale: number = 10; + private WIDTH = 384; + private HEIGHT = 384; + private _size = 64; constructor( picture: Picture, pointerDown: (...args: any[]) => void, - scale?: number + size?: number ) { - this.dom = elt("canvas", { + if (size) this._size = size; + this.picture = picture; + + this.background = elt("canvas", { + width: this.WIDTH, + height: this.HEIGHT, + className: "canvas", + }) as HTMLCanvasElement; + + this.drawGrid(0, 0, this.WIDTH, this.HEIGHT, this.WIDTH / this._size); + this.canvas = elt("canvas", { onmousedown: (event: MouseEvent) => this.mouse(event, pointerDown), ontouchstart: (event: TouchEvent) => this.touch(event, pointerDown), + width: this.WIDTH, + height: this.HEIGHT, + className: "canvas", }) as HTMLCanvasElement; - if (scale) this.scale = scale; - this.picture = picture; + + this.dom = elt( + "div", + { + id: "canvas-container", + }, + this.background, + this.canvas + ); + this.syncState(picture); } public syncState(picture: Picture) { if (this.picture == picture) return; + this.picture = picture; - drawPicture(this.picture, this.dom, this.scale); + drawPicture(this.picture, this.canvas, this.scale); } public mouse(downEvent: MouseEvent, onDown: (...args: any[]) => any) { if (downEvent.button != 0) return; - let pos = this.pointerPosition(downEvent, this.dom); + let pos = this.pointerPosition(downEvent, this.canvas); let onMove = onDown(pos); if (!onMove) return; let move = (moveEvent: MouseEvent) => { if (moveEvent.buttons == 0) { - this.dom.removeEventListener("mousemove", move); + this.canvas.removeEventListener("mousemove", move); } else { - let newPos = this.pointerPosition(moveEvent, this.dom); + let newPos = this.pointerPosition(moveEvent, this.canvas); if (newPos.x == pos.x && newPos.y == pos.y) return; pos = newPos; onMove(newPos); } }; - this.dom.addEventListener("mousemove", move); + this.canvas.addEventListener("mousemove", move); } public touch(startEvent: TouchEvent, onDown: (...args: any[]) => any) { - let pos = this.pointerPosition(startEvent.changedTouches[0], this.dom); + let pos = this.pointerPosition(startEvent.changedTouches[0], this.canvas); let onMove = onDown(pos); startEvent.preventDefault(); if (!onMove) return; let move = (moveEvent: TouchEvent) => { - let newPos = this.pointerPosition(moveEvent.changedTouches[0], this.dom); + let newPos = this.pointerPosition( + moveEvent.changedTouches[0], + this.canvas + ); if (newPos.x == pos.x && newPos.y == pos.y) return; pos = newPos; onMove(newPos); }; let end = () => { - this.dom.removeEventListener("touchmove", move); - this.dom.removeEventListener("touchend", end); + this.canvas.removeEventListener("touchmove", move); + this.canvas.removeEventListener("touchend", end); }; - this.dom.addEventListener("touchmove", move); - this.dom.addEventListener("touchend", end); + this.canvas.addEventListener("touchmove", move); + this.canvas.addEventListener("touchend", end); + } + + public get size() { + return this._size; + } + + public get scale() { + return this.WIDTH / this._size; } private pointerPosition( @@ -77,6 +113,34 @@ class PictureCanvas { y: Math.floor((downEvent.clientY - rect.top) / this.scale), }; } + + private drawGrid( + startX: number, + startY: number, + endX: number, + endY: number, + gridCellSize: number + ) { + const ctx = this.background.getContext("2d"); + ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT); + ctx.fillStyle = "#ffffff"; + ctx.fillRect(0, 0, this.WIDTH, this.HEIGHT); + ctx.beginPath(); + ctx.lineWidth = 0.5; + for (let x = startX; x <= endX; x += gridCellSize) { + ctx.moveTo(x, startY); + ctx.lineTo(x, endY); + } + + for (let y = startY; y <= endY; y += gridCellSize) { + ctx.moveTo(startX, y); + ctx.lineTo(endX, y); + } + + ctx.strokeStyle = "#00000035"; + ctx.stroke(); + ctx.closePath(); + } } export default PictureCanvas; diff --git a/src/components/PixelEditor.ts b/src/components/PixelEditor.ts index 4dc538c..b27416a 100644 --- a/src/components/PixelEditor.ts +++ b/src/components/PixelEditor.ts @@ -2,29 +2,38 @@ import elt from "../utils/createElement"; import PictureCanvas from "./PictureCanvas"; import { UIComponent, EditorState, EditorConfig, Position } from "../types"; +// whole app holds the state and all UI components class PixelEditor implements UIComponent { public state: EditorState; public canvas: PictureCanvas; - public controls: any[]; + public controls: UIComponent[]; public dom: HTMLElement; constructor(state: EditorState, config: EditorConfig) { const { tools, controls, dispatch } = config; this.state = state; - this.canvas = new PictureCanvas(state.picture, (pos: Position) => { - const tool = tools[this.state.tool]; - const onMove = tool(pos, this.state, dispatch); - if (onMove) return (pos: Position) => onMove(pos, this.state); - }); + this.canvas = new PictureCanvas( + state.picture, + (pos: Position) => { + const tool = tools[this.state.tool]; + const onMove = tool(pos, this.state, dispatch); + if (onMove) return (pos: Position) => onMove(pos, this.state); + }, + state.size + ); + + this.controls = Object.keys(controls).map( + (key) => new controls[key](state, config) + ); - this.controls = controls.map((Control) => new Control(state, config)); this.dom = elt( "div", - {}, - this.canvas.dom, - elt("br"), - ...this.controls.reduce((a, c) => a.concat(" ", c.dom), []) + { + className: "app-container", + }, + ...this.controls.map((controlUI) => controlUI.dom), + this.canvas.dom ); } diff --git a/src/components/index.ts b/src/components/index.ts index 3453ba5..64ce035 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,8 +1,8 @@ -import ColorSelect from "./ColorSelect"; -import SaveButton from "./SaveButton"; -import ToolSelect from "./ToolSelect"; -import LoadButton from "./LoadButton"; -import UndoButton from "./UndoButton"; +import ColorSelect from "./uiControls/left/ColorSelect"; +import SaveButton from "./uiControls/bottom/SaveButton"; +import ToolSelect from "./uiControls/left/ToolSelect"; +import LoadButton from "./uiControls/bottom/LoadButton"; +import UndoButton from "./uiControls/right/UndoButton"; import PictureCanvas from "./PictureCanvas"; import PixelEditor from "./PixelEditor"; diff --git a/src/components/LoadButton.ts b/src/components/uiControls/bottom/LoadButton.ts similarity index 64% rename from src/components/LoadButton.ts rename to src/components/uiControls/bottom/LoadButton.ts index 299c9ca..5739916 100644 --- a/src/components/LoadButton.ts +++ b/src/components/uiControls/bottom/LoadButton.ts @@ -1,6 +1,6 @@ -import elt from "../utils/createElement"; -import { EditorConfig, UIComponent } from "../types"; -import { startLoad } from "../utils/fileLoader"; +import elt from "../../../utils/createElement"; +import { EditorConfig, UIComponent } from "../../../types"; +import { startLoad } from "../../../utils/fileLoader"; class LoadButton implements UIComponent { public dom: HTMLElement; diff --git a/src/components/SaveButton.ts b/src/components/uiControls/bottom/SaveButton.ts similarity index 78% rename from src/components/SaveButton.ts rename to src/components/uiControls/bottom/SaveButton.ts index bf60e31..8671b02 100644 --- a/src/components/SaveButton.ts +++ b/src/components/uiControls/bottom/SaveButton.ts @@ -1,7 +1,7 @@ -import elt from "../utils/createElement"; -import { EditorState, UIComponent } from "../types"; -import Picture from "../models/Picture"; -import { drawPicture } from "../utils/drawHelpers"; +import elt from "@/utils/createElement"; +import { EditorState, UIComponent } from "@/types"; +import Picture from "@/models/picture"; +import { drawPicture } from "@/utils/drawHelpers"; class SaveButton implements UIComponent { public picture: Picture; diff --git a/src/components/uiControls/bottom/index.ts b/src/components/uiControls/bottom/index.ts new file mode 100644 index 0000000..4a2d81d --- /dev/null +++ b/src/components/uiControls/bottom/index.ts @@ -0,0 +1,20 @@ +import { EditorState, UIComponent, EditorConfig } from "@/types"; +import elt from "@/utils/createElement"; +import SaveButton from "./SaveButton"; + +class BottomControls implements UIComponent { + public dom: HTMLElement; + + constructor(state: EditorState, config: EditorConfig) { + this.dom = elt( + "div", + { + className: "bottom-controls", + }, + new SaveButton(state).dom + ); + } + public syncState(): void {} +} + +export default BottomControls; diff --git a/src/components/ColorSelect.ts b/src/components/uiControls/left/ColorSelect.ts similarity index 83% rename from src/components/ColorSelect.ts rename to src/components/uiControls/left/ColorSelect.ts index 1ab4abf..c0b836c 100644 --- a/src/components/ColorSelect.ts +++ b/src/components/uiControls/left/ColorSelect.ts @@ -1,5 +1,5 @@ -import elt from "../utils/createElement"; -import { UIComponent, EditorState } from "../types"; +import elt from "../../../utils/createElement"; +import { UIComponent, EditorState } from "../../../types"; class ColorSelect implements UIComponent { public input: HTMLInputElement; @@ -13,6 +13,7 @@ class ColorSelect implements UIComponent { value: state.color, onchange: () => dispatch({ color: this.input.value }), }) as HTMLInputElement; + this.dom = elt("label", null, "🎨 Color: ", this.input); } public syncState(state: EditorState) { diff --git a/src/components/uiControls/left/ToolButton.ts b/src/components/uiControls/left/ToolButton.ts new file mode 100644 index 0000000..3880ecb --- /dev/null +++ b/src/components/uiControls/left/ToolButton.ts @@ -0,0 +1,22 @@ +import { EditorState, UIComponent, EditorConfig } from "@/types"; +import elt from "@/utils/createElement"; + +class ToolButton implements UIComponent { + public dom: HTMLButtonElement; + private name: string; + + constructor(state: EditorState, { dispatch }: EditorConfig, name: string) { + this.name = name; + this.dom = elt( + "button", + { + onclick: () => dispatch({ tool: this.dom.textContent }), + className: this.name === state.currentTool ? "selected" : "", + }, + name + ) as HTMLButtonElement; + } + public syncState() {} +} + +export default ToolButton; diff --git a/src/components/ToolSelect.ts b/src/components/uiControls/left/ToolSelect.ts similarity index 86% rename from src/components/ToolSelect.ts rename to src/components/uiControls/left/ToolSelect.ts index 5775645..55177d2 100644 --- a/src/components/ToolSelect.ts +++ b/src/components/uiControls/left/ToolSelect.ts @@ -1,5 +1,5 @@ -import elt from "../utils/createElement"; -import { EditorConfig, EditorState, UIComponent } from "../types"; +import elt from "@/utils/createElement"; +import { EditorConfig, EditorState, UIComponent } from "@/types"; class ToolSelect implements UIComponent { public select: HTMLSelectElement; diff --git a/src/components/uiControls/left/index.ts b/src/components/uiControls/left/index.ts new file mode 100644 index 0000000..30d6b17 --- /dev/null +++ b/src/components/uiControls/left/index.ts @@ -0,0 +1,26 @@ +import { EditorState, UIComponent, EditorConfig } from "@/types"; +import ToolButton from "./ToolButton"; +import ColorSelect from "./ColorSelect"; +import elt from "@/utils/createElement"; + +class ToolBar implements UIComponent { + public dom: HTMLElement; + + constructor(state: EditorState, config: EditorConfig) { + const tools = config.tools; + this.dom = elt( + "div", + { + className: "toolbar", + }, + ...Object.keys(tools).map( + (name) => new ToolButton(state, config, name).dom + ), + new ColorSelect(state, config).dom + ); + } + + public syncState(state: EditorState): void {} +} + +export default ToolBar; diff --git a/src/components/uiControls/right/ClearButton.ts b/src/components/uiControls/right/ClearButton.ts new file mode 100644 index 0000000..e95d5dc --- /dev/null +++ b/src/components/uiControls/right/ClearButton.ts @@ -0,0 +1,20 @@ +import { EditorState, UIComponent, EditorConfig } from "@/types"; +import elt from "@/utils/createElement"; + +class ClearButton implements UIComponent { + public dom: HTMLButtonElement; + + constructor(state: EditorState, { dispatch }: EditorConfig) { + this.dom = elt( + "button", + { + onclick: () => dispatch({ clear: true }), + disabled: state.done.length == 0, + }, + "⎚ Clear" + ) as HTMLButtonElement; + } + public syncState(): void {} +} + +export default ClearButton; diff --git a/src/components/UndoButton.ts b/src/components/uiControls/right/UndoButton.ts similarity index 80% rename from src/components/UndoButton.ts rename to src/components/uiControls/right/UndoButton.ts index f739034..923dea9 100644 --- a/src/components/UndoButton.ts +++ b/src/components/uiControls/right/UndoButton.ts @@ -1,5 +1,5 @@ -import elt from "../utils/createElement"; -import { EditorConfig, EditorState, UIComponent } from "../types"; +import elt from "@/utils/createElement"; +import { EditorConfig, EditorState, UIComponent } from "@/types"; class UndoButton implements UIComponent { public dom: HTMLButtonElement; diff --git a/src/components/uiControls/right/index.ts b/src/components/uiControls/right/index.ts new file mode 100644 index 0000000..aa4a716 --- /dev/null +++ b/src/components/uiControls/right/index.ts @@ -0,0 +1,23 @@ +import { EditorConfig, EditorState, UIComponent } from "@/types"; +import elt from "@/utils/createElement"; +import ClearButton from "./ClearButton"; +import UndoButton from "./UndoButton"; + +class RightControls implements UIComponent { + public dom: HTMLElement; + + constructor(state: EditorState, config: EditorConfig) { + this.dom = elt( + "div", + { + className: "right-controls", + }, + new ClearButton(state, config).dom, + new UndoButton(state, config).dom + ); + } + + public syncState(): void {} +} + +export default RightControls; diff --git a/src/components/uiControls/top/SetSizeButton.ts b/src/components/uiControls/top/SetSizeButton.ts new file mode 100644 index 0000000..ca0d93f --- /dev/null +++ b/src/components/uiControls/top/SetSizeButton.ts @@ -0,0 +1,23 @@ +import { EditorConfig, EditorState, UIComponent } from "@/types"; +import elt from "@/utils/createElement"; + +class SetSizeButton implements UIComponent { + public dom: HTMLButtonElement; + private size: number; + + constructor(state: EditorState, { dispatch }: EditorConfig, size: number) { + this.size = size; + this.dom = elt( + "button", + { + onclick: () => dispatch({ size: Number(this.dom.textContent) }), + className: this.size === state.currentSize ? "selected" : "", + }, + size.toString() + ) as HTMLButtonElement; + } + + public syncState(): void {} +} + +export default SetSizeButton; diff --git a/src/components/uiControls/top/index.ts b/src/components/uiControls/top/index.ts new file mode 100644 index 0000000..b58f3c2 --- /dev/null +++ b/src/components/uiControls/top/index.ts @@ -0,0 +1,25 @@ +import { EditorConfig, EditorState, UIComponent } from "@/types"; +import SetSizeButton from "./SetSizeButton"; +import elt from "@/utils/createElement"; + +// initial different sizes of buttons and export +export default class SetSizeButtons implements UIComponent { + private sizes = [16, 32, 64, 128]; + public dom: HTMLElement; + + constructor(state: EditorState, config: EditorConfig) { + const sizeButtons: UIComponent[] = this.sizes.map( + (size) => new SetSizeButton(state, config, size) + ); + + this.dom = elt( + "div", + { + className: "top-btns-container", + }, + ...sizeButtons.map((btn) => btn.dom) + ); + } + + public syncState() {} +} diff --git a/src/main.ts b/src/main.ts index 2f526ea..8c8384b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ import "./style.css"; -import startPixelEditor from "App"; +import startPixelEditor from "./App"; document.querySelector("#app")!.appendChild(startPixelEditor({})); diff --git a/src/models/Picture.ts b/src/models/Picture.ts index 8b009d0..f37d10c 100644 --- a/src/models/Picture.ts +++ b/src/models/Picture.ts @@ -9,6 +9,7 @@ class Picture { this.height = height; this.pixels = pixels; } + public static empty(width: number, height: number, color: string) { const pixels = new Array(width * height).fill(color); return new Picture(width, height, pixels); diff --git a/src/models/reducers.ts b/src/models/reducers.ts index d861059..873810f 100644 --- a/src/models/reducers.ts +++ b/src/models/reducers.ts @@ -1,5 +1,5 @@ import { EditorState } from "../types"; -import Picture from "./Picture"; +import Picture from "./picture"; // undo state reducer export function historyUpdateState( diff --git a/src/style.css b/src/style.css index b528b6c..22d7657 100644 --- a/src/style.css +++ b/src/style.css @@ -26,36 +26,21 @@ a:hover { body { margin: 0; display: flex; - place-items: center; min-width: 320px; min-height: 100vh; } h1 { - font-size: 3.2em; + font-size: 3em; line-height: 1.1; } #app { max-width: 1280px; margin: 0 auto; - padding: 2rem; text-align: center; } -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.vanilla:hover { - filter: drop-shadow(0 0 2em #3178c6aa); -} - .card { padding: 2em; } @@ -95,3 +80,16 @@ button:focus-visible { background-color: #f9f9f9; } } + +#canvas-container { + position: relative; + width: 384px; + height: 384px; + margin: 0 auto; +} + +.canvas { + position: absolute; + top: 0; + left: 0; +} diff --git a/src/types.ts b/src/types.ts index df8f395..67857b8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,9 +1,10 @@ -import Picture from "./models/Picture"; +import Picture from "./models/picture"; // UIs are modeled as components. // Each component creates corresponding HTML elements // and expose sysnState() to outside world export interface UIComponent { + dom: HTMLElement; syncState(state: EditorState): void; } @@ -13,6 +14,9 @@ declare var UIComponent: { }; export interface EditorState { + size: 16 | 32 | 64 | 128; + currentSize: 16 | 32 | 64 | 128; // 64 means 64 * 64 + currentTool: string; // TODO: maybe change to enum tool: string; color: string; // like "#000000", picture: Picture; @@ -20,13 +24,27 @@ export interface EditorState { doneAt: number; } +interface BaseControls { + [key: string]: typeof UIComponent; + // LeftControls: typeof UIComponent; + // TopControls: typeof UIComponent; + // RightControls: typeof UIComponent; + // BottomControls: typeof UIComponent; +} + export interface EditorConfig { tools: any; - controls: (typeof UIComponent)[]; - dispatch: (stat: any, action?: any) => void; + controls: BaseControls; + dispatch: (state: any, action?: any) => void; } export interface Position { x: number; y: number; } + +export interface ActionType { + undo: boolean; + picture: Picture; + clear: boolean; +} diff --git a/src/utils/createElement.ts b/src/utils/createElement.ts index f2964a3..84fec04 100644 --- a/src/utils/createElement.ts +++ b/src/utils/createElement.ts @@ -1,5 +1,5 @@ // dom building -function elt( +export default function elt( type: string, props?: any, ...children: (HTMLElement | string)[] @@ -12,5 +12,3 @@ function elt( } return dom; } - -export default elt; diff --git a/src/utils/drawHelpers.ts b/src/utils/drawHelpers.ts index 84ec07c..029376c 100644 --- a/src/utils/drawHelpers.ts +++ b/src/utils/drawHelpers.ts @@ -1,21 +1,18 @@ -import Picture, { Pixel } from "../models/Picture"; +import Picture, { Pixel } from "../models/picture"; import { EditorState, Position } from "../types"; import elt from "./createElement"; -// draw Picture to canvas export function drawPicture( picture: Picture, canvas: HTMLCanvasElement, scale: number ) { - canvas.width = picture.width * scale; - canvas.height = picture.height * scale; - let cx = canvas.getContext("2d"); + let cx = canvas.getContext("2d"); for (let y = 0; y < picture.height; y++) { for (let x = 0; x < picture.width; x++) { - cx!.fillStyle = picture.getPixel(x, y); - cx!.fillRect(x * scale, y * scale, scale, scale); + cx.fillStyle = picture.getPixel(x, y); + cx.fillRect(x * scale, y * scale, scale, scale); } } } diff --git a/tsconfig.json b/tsconfig.json index 75abdef..a37e1d5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,12 @@ "useDefineForClassFields": true, "module": "ESNext", "lib": ["ES2020", "DOM", "DOM.Iterable"], + "types": ["node"], "skipLibCheck": true, + "baseUrl": "./", + "paths": { + "@/*": ["src/*"] + }, /* Bundler mode */ "moduleResolution": "bundler", diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..8731877 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,10 @@ +import path from "path"; +import { defineConfig } from "vite"; + +export default defineConfig({ + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, +});