From a4b81fb97de1435855c707cd32d773bff46513e5 Mon Sep 17 00:00:00 2001 From: stagas Date: Tue, 8 Oct 2024 16:01:17 +0300 Subject: [PATCH 01/25] docs: mark complete --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5250d34..39adac8 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ - [x] OAuthRegister - [x] QrCode - [x] WebSockets - - [ ] UI Showcase + - [x] UI Showcase - [x] Components - [x] Header - [x] Toast @@ -104,6 +104,7 @@ - [x] ResetPassword - [x] VerifyEmail - [x] UI + - [x] Button - [x] Fieldset - [x] Input - [x] Label From 94fed17555c59bec0d1f8e4218865039d3c9b745 Mon Sep 17 00:00:00 2001 From: stagas Date: Wed, 9 Oct 2024 17:52:45 +0300 Subject: [PATCH 02/25] feat: wip editor --- lib/caching-router.ts | 3 +- src/pages/App.tsx | 6 +- src/pages/{Canvas.tsx => CanvasDemo.tsx} | 48 +-- src/pages/EditorDemo.tsx | 35 ++ src/pages/Home.tsx | 1 + src/ui/Canvas.tsx | 28 ++ src/ui/editor/Editor.tsx | 348 ++++++++++++++++++ src/ui/editor/text-buffer.test.ts | 227 ++++++++++++ src/ui/editor/text-buffer.ts | 198 ++++++++++ src/ui/editor/util/begin-of-line.ts | 3 + src/ui/editor/util/find-matching-brackets.ts | 65 ++++ src/ui/editor/util/index-to-point.ts | 23 ++ src/ui/editor/util/index-to-visual-point.ts | 25 ++ src/ui/editor/util/index.ts | 11 + src/ui/editor/util/parse-words.ts | 7 + src/ui/editor/util/point-to-index.test.ts | 57 +++ src/ui/editor/util/point-to-index.ts | 26 ++ src/ui/editor/util/regexp.ts | 5 + src/ui/editor/util/types.ts | 8 + src/ui/editor/util/visual-point-to-index.ts | 22 ++ .../util/visual-point-to-logical-point.ts | 11 + src/ui/editor/util/word-wrap-text.test.ts | 67 ++++ src/ui/editor/util/word-wrap-text.ts | 60 +++ src/ui/index.ts | 1 + 24 files changed, 1258 insertions(+), 27 deletions(-) rename src/pages/{Canvas.tsx => CanvasDemo.tsx} (59%) create mode 100644 src/pages/EditorDemo.tsx create mode 100644 src/ui/Canvas.tsx create mode 100644 src/ui/editor/Editor.tsx create mode 100644 src/ui/editor/text-buffer.test.ts create mode 100644 src/ui/editor/text-buffer.ts create mode 100644 src/ui/editor/util/begin-of-line.ts create mode 100644 src/ui/editor/util/find-matching-brackets.ts create mode 100644 src/ui/editor/util/index-to-point.ts create mode 100644 src/ui/editor/util/index-to-visual-point.ts create mode 100644 src/ui/editor/util/index.ts create mode 100644 src/ui/editor/util/parse-words.ts create mode 100644 src/ui/editor/util/point-to-index.test.ts create mode 100644 src/ui/editor/util/point-to-index.ts create mode 100644 src/ui/editor/util/regexp.ts create mode 100644 src/ui/editor/util/types.ts create mode 100644 src/ui/editor/util/visual-point-to-index.ts create mode 100644 src/ui/editor/util/visual-point-to-logical-point.ts create mode 100644 src/ui/editor/util/word-wrap-text.test.ts create mode 100644 src/ui/editor/util/word-wrap-text.ts diff --git a/lib/caching-router.ts b/lib/caching-router.ts index f3e759e..33067e1 100644 --- a/lib/caching-router.ts +++ b/lib/caching-router.ts @@ -1,7 +1,7 @@ import { dispose } from 'sigui' export function CachingRouter(routes: Record JSX.Element>) { - const cache = new Map() + const cache = new Map() let shouldDispose = false return function (pathname: string) { if (shouldDispose) { @@ -15,6 +15,7 @@ export function CachingRouter(routes: Record JSX.Element>) { if (!(pathname in routes)) return let el = cache.get(pathname) if (!el) cache.set(pathname, el = routes[pathname]()) + else requestAnimationFrame(() => el!.focus?.()) return el } } diff --git a/src/pages/App.tsx b/src/pages/App.tsx index cd456e7..5832a71 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -8,8 +8,9 @@ import { Toast } from '~/src/comp/Toast.tsx' import { VerifyEmail } from '~/src/comp/VerifyEmail.tsx' import { About } from '~/src/pages/About.tsx' import { AssemblyScript } from '~/src/pages/AssemblyScript.tsx' -import { Canvas } from '~/src/pages/Canvas' +import { CanvasDemo } from '~/src/pages/CanvasDemo' import { Chat } from '~/src/pages/Chat/Chat.tsx' +import { EditorDemo } from '~/src/pages/EditorDemo.tsx' import { Home } from '~/src/pages/Home.tsx' import { OAuthRegister } from '~/src/pages/OAuthRegister.tsx' import { QrCode } from '~/src/pages/QrCode.tsx' @@ -35,7 +36,8 @@ export function App() { '/ui': () => , '/chat': () => , '!/ws': () => , - '!/canvas': () => , + '!/canvas': () => , + '/editor': () => , '/asc': () => , '/qrcode': () => , '/about': () => , diff --git a/src/pages/Canvas.tsx b/src/pages/CanvasDemo.tsx similarity index 59% rename from src/pages/Canvas.tsx rename to src/pages/CanvasDemo.tsx index e5b89b2..224ea7c 100644 --- a/src/pages/Canvas.tsx +++ b/src/pages/CanvasDemo.tsx @@ -1,39 +1,27 @@ import { Sigui, type Signal } from 'sigui' import { drawText } from 'utils' import { screen } from '~/src/screen.ts' +import { Canvas } from '~/src/ui/Canvas.tsx' +import { H2 } from '~/src/ui/Heading.tsx' -export function Canvas({ width, height }: { +export function CanvasDemo({ width, height }: { width: Signal height: Signal }) { using $ = Sigui() - const canvas = as HTMLCanvasElement - const c = canvas.getContext('2d')! + const canvas = as HTMLCanvasElement const info = $({ + c: null as null | CanvasRenderingContext2D, pr: screen.$.pr, width, height, - c: $.unwrap(async () => { - await document.fonts.ready - return c - }) }) - $.fx(() => { - const { c, width, height, pr } = $.of(info) - $() - if (c instanceof Error) return - canvas.width = width * pr - canvas.height = height * pr - canvas.style.width = `${width}px` - canvas.style.height = `${height}px` - c.scale(pr, pr) - c.textBaseline = 'top' - c.textRendering = 'optimizeSpeed' - c.miterLimit = 1.5 - c.font = '32px "Fustat"' + const c = canvas.getContext('2d')! + document.fonts.ready.then(() => { + info.c = c }) let i = 0 @@ -47,13 +35,25 @@ export function Canvas({ width, height }: { } $.fx(() => { - const { c } = $.of(info) + const { pr, c } = $.of(info) + $() + c.scale(pr, pr) + c.textBaseline = 'top' + c.textRendering = 'optimizeSpeed' + c.miterLimit = 1.5 + c.font = '32px "Fustat"' + }) + + $.fx(() => { + const { c, width, height } = $.of(info) $() - if (c instanceof Error) return - c.translate(info.width / 2, info.height / 2) + c.translate(width / 2, height / 2) tick() return () => cancelAnimationFrame(animFrame) }) - return canvas + return
+

Canvas demo

+ {canvas} +
} diff --git a/src/pages/EditorDemo.tsx b/src/pages/EditorDemo.tsx new file mode 100644 index 0000000..a91f18c --- /dev/null +++ b/src/pages/EditorDemo.tsx @@ -0,0 +1,35 @@ +import { Sigui, type Signal } from 'sigui' +import { screen } from '~/src/screen.ts' +import { Editor } from '~/src/ui/editor/Editor' +import { H2 } from '~/src/ui/Heading.tsx' + +export function EditorDemo({ width, height }: { + width: Signal + height: Signal +}) { + using $ = Sigui() + + const info = $({ + c: null as null | CanvasRenderingContext2D, + pr: screen.$.pr, + width, + height, + code: `[sin 300] [tri 444] [sqr 555] +[lp 300 .8] +`, + }) + + const editor = as Element & { focus(): void } + + const el =
+

Editor demo

+ + {editor} +
+ + return { el, focus: editor.focus } +} diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index d1571fb..55da2d6 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -26,6 +26,7 @@ export function Home() { Chat WebSockets Canvas + Editor AssemblyScript QrCode About diff --git a/src/ui/Canvas.tsx b/src/ui/Canvas.tsx new file mode 100644 index 0000000..6206300 --- /dev/null +++ b/src/ui/Canvas.tsx @@ -0,0 +1,28 @@ +import { Sigui, type Signal } from 'sigui' +import { screen } from '~/src/screen.ts' + +export function Canvas({ width, height }: { + width: Signal + height: Signal +}) { + using $ = Sigui() + + const canvas = as HTMLCanvasElement + + const info = $({ + pr: screen.$.pr, + width, + height, + }) + + $.fx(() => { + const { width, height, pr } = $.of(info) + $() + canvas.width = width * pr + canvas.height = height * pr + canvas.style.width = `${width}px` + canvas.style.height = `${height}px` + }) + + return canvas +} diff --git a/src/ui/editor/Editor.tsx b/src/ui/editor/Editor.tsx new file mode 100644 index 0000000..8b3d5e0 --- /dev/null +++ b/src/ui/editor/Editor.tsx @@ -0,0 +1,348 @@ +import { Sigui, type Signal } from 'sigui' +import { dom, drawText } from 'utils' +import { screen } from '~/src/screen.ts' +import { Canvas } from '~/src/ui/Canvas.tsx' +import { TextBuffer } from '~/src/ui/editor/text-buffer' +import { + beginOfLine, + parseWords, + Point, + WORD +} from '~/src/ui/editor/util/index.ts' + +export function Editor({ code, width, height }: { + code: Signal + width: Signal + height: Signal +}) { + using $ = Sigui() + + const canvas = as HTMLCanvasElement + const textarea =