diff --git a/astro.config.mjs b/astro.config.mjs index 97fb8b9..98ca6ba 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,3 +1,4 @@ +import fs from 'node:fs/promises' import path from 'node:path' import { fileURLToPath } from 'node:url' import mdx from '@astrojs/mdx' @@ -5,6 +6,67 @@ import tailwindcss from '@tailwindcss/vite' import { defineConfig } from 'astro/config' const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const thoughtsCanvasLayoutPath = path.resolve(__dirname, 'src/data/thoughts-canvas-layout.json') + +function thoughtsCanvasLayoutDevPlugin() { + return { + name: 'thoughts-canvas-layout-dev', + configureServer(server) { + server.middlewares.use('/__thoughts-canvas-layout', async (req, res, next) => { + if (req.method !== 'POST') + return next() + + try { + let body = '' + for await (const chunk of req) + body += chunk + + const payload = JSON.parse(body) + if (payload?.version !== 1 || typeof payload.cards !== 'object' || payload.cards === null) + throw new Error('Invalid thoughts layout payload') + + const cards = {} + for (const [slug, card] of Object.entries(payload.cards)) { + if ( + typeof slug !== 'string' + || typeof card !== 'object' + || card === null + || !Number.isFinite(card.x) + || !Number.isFinite(card.y) + || !Number.isFinite(card.rotateDeg) + ) { + throw new Error(`Invalid card layout for ${slug}`) + } + cards[slug] = { + x: Math.round(card.x * 100) / 100, + y: Math.round(card.y * 100) / 100, + rotateDeg: Math.round(card.rotateDeg * 100) / 100, + } + } + + const sortedCards = Object.fromEntries(Object.entries(cards).sort(([a], [b]) => a.localeCompare(b))) + await fs.writeFile( + thoughtsCanvasLayoutPath, + `${JSON.stringify({ version: 1, cards: sortedCards }, null, 2)}\n`, + 'utf8', + ) + + const layoutModules = server.moduleGraph.getModulesByFile(thoughtsCanvasLayoutPath) + layoutModules?.forEach(mod => server.moduleGraph.invalidateModule(mod)) + + res.statusCode = 200 + res.setHeader('Content-Type', 'application/json') + res.end(JSON.stringify({ ok: true, layout: { version: 1, cards: sortedCards } })) + } + catch (err) { + res.statusCode = 400 + res.setHeader('Content-Type', 'application/json') + res.end(JSON.stringify({ ok: false, error: err instanceof Error ? err.message : 'Unknown error' })) + } + }) + }, + } +} // https://astro.build/config export default defineConfig({ @@ -37,7 +99,12 @@ export default defineConfig({ ], vite: { - plugins: [tailwindcss()], + plugins: [tailwindcss(), thoughtsCanvasLayoutDevPlugin()], + server: { + watch: { + ignored: [thoughtsCanvasLayoutPath], + }, + }, resolve: { alias: { '@': path.resolve(__dirname, 'src'), diff --git a/src/components/icons/RotateCcw.astro b/src/components/icons/RotateCcw.astro new file mode 100644 index 0000000..2bd7136 --- /dev/null +++ b/src/components/icons/RotateCcw.astro @@ -0,0 +1,25 @@ +--- +interface Props { + class?: string + size?: number +} + +const { class: className = '', size = 14 } = Astro.props +--- + + diff --git a/src/components/icons/RotateCw.astro b/src/components/icons/RotateCw.astro new file mode 100644 index 0000000..6f9fa26 --- /dev/null +++ b/src/components/icons/RotateCw.astro @@ -0,0 +1,25 @@ +--- +interface Props { + class?: string + size?: number +} + +const { class: className = '', size = 14 } = Astro.props +--- + + diff --git a/src/components/thoughts/ThoughtsCanvasBottomToolbar.astro b/src/components/thoughts/ThoughtsCanvasBottomToolbar.astro index f631207..561d35c 100644 --- a/src/components/thoughts/ThoughtsCanvasBottomToolbar.astro +++ b/src/components/thoughts/ThoughtsCanvasBottomToolbar.astro @@ -1,9 +1,23 @@ --- +import RotateCcw from '@/components/icons/RotateCcw.astro' +import RotateCw from '@/components/icons/RotateCw.astro' + +interface Props { + isDev?: boolean +} + +const { isDev = false } = Astro.props ---