From 5826357427547d5bf9157f5514ef018ffd382e5f Mon Sep 17 00:00:00 2001 From: Masaaki Hirotsu Date: Wed, 6 May 2026 21:30:35 +0900 Subject: [PATCH 1/2] chore(tooling): centralize oxlint/oxfmt config at workspace root Move oxlint and oxfmt configuration to repository root and reference it from both TS packages via -c flag. Enable type-aware lint with denyWarnings so warnings fail CI, and add oxlint-tsgolint to the workspace catalog. --- .github/dependabot.yml | 13 ++++ .oxfmtrc.json | 13 ++++ .oxlintrc.json | 44 ++++++++++++++ crates/relune-render-html/.oxfmtrc.jsonc | 7 --- crates/relune-render-html/package.json | 9 +-- playground/package.json | 9 +-- pnpm-lock.yaml | 77 +++++++++++++++++++++++- pnpm-workspace.yaml | 1 + 8 files changed, 155 insertions(+), 18 deletions(-) create mode 100644 .oxfmtrc.json create mode 100644 .oxlintrc.json delete mode 100644 crates/relune-render-html/.oxfmtrc.jsonc diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f2ead5a..dbc4b36 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -27,6 +27,12 @@ updates: patterns: - "resvg" - "fontdb" + cargo-minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" - package-ecosystem: npm directory: / @@ -42,4 +48,11 @@ updates: oxc: patterns: - "oxlint" + - "oxlint-tsgolint" - "oxfmt" + npm-minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..ebf9539 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,13 @@ +{ + "singleQuote": true, + "sortImports": true, + "ignorePatterns": [ + "**/node_modules/**", + "**/dist/**", + "**/target/**", + "**/pkg/**", + "**/src/js/**", + "**/*.min.js", + "**/pnpm-lock.yaml" + ] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..0021a87 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,44 @@ +{ + "plugins": ["eslint", "typescript", "oxc", "unicorn", "import", "promise"], + "categories": { + "correctness": "error", + "suspicious": "warn", + "perf": "warn" + }, + "env": { + "browser": true, + "es2024": true + }, + "options": { + "reportUnusedDisableDirectives": "warn", + "typeAware": true, + "denyWarnings": true + }, + "ignorePatterns": [ + "node_modules/**", + "dist/**", + "playground/dist/**", + "crates/relune-render-html/src/js/**" + ], + "rules": { + "no-console": "warn", + "eqeqeq": ["error", "smart"], + "no-underscore-dangle": "off", + "import/no-cycle": "warn", + "typescript/consistent-type-imports": "warn", + "typescript/no-unsafe-type-assertion": "off", + "unicorn/consistent-function-scoping": "off" + }, + "overrides": [ + { + "files": ["**/esbuild.config.mjs"], + "env": { + "node": true, + "browser": false + }, + "rules": { + "no-console": "off" + } + } + ] +} diff --git a/crates/relune-render-html/.oxfmtrc.jsonc b/crates/relune-render-html/.oxfmtrc.jsonc deleted file mode 100644 index e8719e2..0000000 --- a/crates/relune-render-html/.oxfmtrc.jsonc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "https://unpkg.com/oxfmt/configuration_schema.json", - "singleQuote": true, - "files": { - "ignore": ["**/node_modules/**", "**/pnpm-lock.yaml"] - } -} diff --git a/crates/relune-render-html/package.json b/crates/relune-render-html/package.json index 8c85a3c..9643094 100644 --- a/crates/relune-render-html/package.json +++ b/crates/relune-render-html/package.json @@ -4,9 +4,9 @@ "description": "TypeScript sources for embedded HTML viewer scripts (build outputs to src/js/)", "scripts": { "build": "node esbuild.config.mjs", - "fmt": "oxfmt ts", - "fmt:check": "oxfmt --check ts", - "lint": "oxlint ts", + "fmt": "oxfmt -c ../../.oxfmtrc.json", + "fmt:check": "oxfmt -c ../../.oxfmtrc.json --check", + "lint": "oxlint -c ../../.oxlintrc.json ts esbuild.config.mjs", "typecheck": "tsgo --noEmit -p tsconfig.json", "check": "pnpm fmt:check && pnpm lint && pnpm typecheck" }, @@ -15,6 +15,7 @@ "@typescript/native-preview": "catalog:", "esbuild": "catalog:", "oxfmt": "catalog:", - "oxlint": "catalog:" + "oxlint": "catalog:", + "oxlint-tsgolint": "catalog:" } } diff --git a/playground/package.json b/playground/package.json index e6c7b3e..7d61b22 100644 --- a/playground/package.json +++ b/playground/package.json @@ -4,9 +4,9 @@ "description": "Public WASM playground for relune", "scripts": { "build": "node esbuild.config.mjs", - "fmt": "oxfmt src", - "fmt:check": "oxfmt --check src", - "lint": "oxlint src", + "fmt": "oxfmt -c ../.oxfmtrc.json", + "fmt:check": "oxfmt -c ../.oxfmtrc.json --check", + "lint": "oxlint -c ../.oxlintrc.json src esbuild.config.mjs", "typecheck": "tsgo --noEmit -p tsconfig.json", "check": "pnpm fmt:check && pnpm lint && pnpm typecheck" }, @@ -24,6 +24,7 @@ "@typescript/native-preview": "catalog:", "esbuild": "catalog:", "oxfmt": "catalog:", - "oxlint": "catalog:" + "oxlint": "catalog:", + "oxlint-tsgolint": "catalog:" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22ecc65..b5e6aeb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,9 @@ catalogs: oxlint: specifier: 1.62.0 version: 1.62.0 + oxlint-tsgolint: + specifier: 0.22.1 + version: 0.22.1 importers: @@ -40,7 +43,10 @@ importers: version: 0.47.0 oxlint: specifier: 'catalog:' - version: 1.62.0 + version: 1.62.0(oxlint-tsgolint@0.22.1) + oxlint-tsgolint: + specifier: 'catalog:' + version: 0.22.1 playground: dependencies: @@ -80,7 +86,10 @@ importers: version: 0.47.0 oxlint: specifier: 'catalog:' - version: 1.62.0 + version: 1.62.0(oxlint-tsgolint@0.22.1) + oxlint-tsgolint: + specifier: 'catalog:' + version: 0.22.1 packages: @@ -398,6 +407,36 @@ packages: cpu: [x64] os: [win32] + '@oxlint-tsgolint/darwin-arm64@0.22.1': + resolution: {integrity: sha512-4150Lpgc1YM09GcjA6GSrra1JoPjC7aOpfywLjWEY4vW0Sd1qKzqHF1WRaiw0/qUZ40OATYdv3aRd7ipPkWQbw==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@0.22.1': + resolution: {integrity: sha512-vFWcPWYOgZs4HWcgS1EjUZg33NLcNfEYU49KGImmCfZWkflENrmBYV4HN/C0YeAPum6ZZ/goPSvQrB/cOD+NfA==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@0.22.1': + resolution: {integrity: sha512-6LiUpP0Zir3+29FvBm7Y28q/dBjSHqTZ5MhG1Ckw4fGhI4cAvbcwXaKvbjx1TP7rRmBNOoq/M5xdpHjTb+GAew==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@0.22.1': + resolution: {integrity: sha512-fuX1hEQfpHauUbXADsfqVhRzrUrGabzGXbj5wsp2vKhV5uk/Rze8Mba9GdjFGECzvXudMGqHqxB4r6jGRdhxVA==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@0.22.1': + resolution: {integrity: sha512-8SZidAj+jrbZf9ZjBEYW0tiNZ+KasqB2zgW26qdiPpQSF/DzURnPmXz651IeA9YsmbVdHGIooEHUmev6QJdquA==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@0.22.1': + resolution: {integrity: sha512-QweSk9H5lFh5Y+WUf2Kq/OAN88V6+62ZwGhP38gqdRotI90luXSMkruFTj7Q2rYrzH4ZVNaSqx7NY8JpSfIzqg==} + cpu: [x64] + os: [win32] + '@oxlint/binding-android-arm-eabi@1.62.0': resolution: {integrity: sha512-pKsthNECyvJh8lPTICz6VcwVy2jOqdhhsp1rlxCkhgZR47aKvXPmaRWQDv+zlXpRae4qm1MaaTnutkaOk5aofg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -673,6 +712,10 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + oxlint-tsgolint@0.22.1: + resolution: {integrity: sha512-YUSGSLUnoolsu8gxISEDio3q1rtsCozwfOzASUn3DT2mR2EeQ93uEEnen7s+6LpF+lyTQFln1pQfqwBh/fsVEg==} + hasBin: true + oxlint@1.62.0: resolution: {integrity: sha512-1uFkg6HakjsGIpW9wNdeW4/2LOHW9MEkoWjZUTUfQtIHyLIZPYt00w3Sg+H3lH+206FgBPHBbW5dVE5l2ExECQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -897,6 +940,24 @@ snapshots: '@oxfmt/binding-win32-x64-msvc@0.47.0': optional: true + '@oxlint-tsgolint/darwin-arm64@0.22.1': + optional: true + + '@oxlint-tsgolint/darwin-x64@0.22.1': + optional: true + + '@oxlint-tsgolint/linux-arm64@0.22.1': + optional: true + + '@oxlint-tsgolint/linux-x64@0.22.1': + optional: true + + '@oxlint-tsgolint/win32-arm64@0.22.1': + optional: true + + '@oxlint-tsgolint/win32-x64@0.22.1': + optional: true + '@oxlint/binding-android-arm-eabi@1.62.0': optional: true @@ -1052,7 +1113,16 @@ snapshots: '@oxfmt/binding-win32-ia32-msvc': 0.47.0 '@oxfmt/binding-win32-x64-msvc': 0.47.0 - oxlint@1.62.0: + oxlint-tsgolint@0.22.1: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 0.22.1 + '@oxlint-tsgolint/darwin-x64': 0.22.1 + '@oxlint-tsgolint/linux-arm64': 0.22.1 + '@oxlint-tsgolint/linux-x64': 0.22.1 + '@oxlint-tsgolint/win32-arm64': 0.22.1 + '@oxlint-tsgolint/win32-x64': 0.22.1 + + oxlint@1.62.0(oxlint-tsgolint@0.22.1): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.62.0 '@oxlint/binding-android-arm64': 1.62.0 @@ -1073,6 +1143,7 @@ snapshots: '@oxlint/binding-win32-arm64-msvc': 1.62.0 '@oxlint/binding-win32-ia32-msvc': 1.62.0 '@oxlint/binding-win32-x64-msvc': 1.62.0 + oxlint-tsgolint: 0.22.1 style-mod@4.1.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2251e9b..01ddb6c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,6 +6,7 @@ catalog: esbuild: 0.28.0 oxfmt: 0.47.0 oxlint: 1.62.0 + oxlint-tsgolint: 0.22.1 "@typescript/native-preview": 7.0.0-dev.20260428.1 blockExoticSubdeps: true From 1ef2eaaadcbb83562346929385e5fcbf1de8b121 Mon Sep 17 00:00:00 2001 From: Masaaki Hirotsu Date: Wed, 6 May 2026 21:30:47 +0900 Subject: [PATCH 2/2] style(frontend): apply oxfmt single-quote/sortImports and resolve type-aware lint findings Reformat TypeScript, esbuild configs, HTML, and CSS via oxfmt with the new root config, and address type-aware findings surfaced by oxlint-tsgolint: prefer Array.prototype.toSorted over [...arr].sort, replace generic emitViewerEvent with unknown detail, add exhaustive switch defaults, and rename a shadowed local in minimap. Regenerate the bundled viewer scripts under crates/relune-render-html/src/js to match the reformatted sources. --- ...udit__fixture_render_audit__cyclic_fk.snap | 2 +- ...udit__fixture_render_audit__ecommerce.snap | 2 +- ...dit__fixture_render_audit__join_heavy.snap | 2 +- ...t__fixture_render_audit__multi_schema.snap | 2 +- ...it__fixture_render_audit__simple_blog.snap | 2 +- crates/relune-render-html/esbuild.config.mjs | 37 +- .../src/js/filter_engine.js | 128 +- .../relune-render-html/src/js/group_toggle.js | 102 +- crates/relune-render-html/src/js/highlight.js | 168 +-- crates/relune-render-html/src/js/minimap.js | 8 +- crates/relune-render-html/src/js/pan_zoom.js | 172 +-- crates/relune-render-html/src/js/search.js | 42 +- crates/relune-render-html/ts/filter_engine.ts | 22 +- .../ts/filter_engine_state.ts | 4 +- crates/relune-render-html/ts/group_toggle.ts | 12 +- crates/relune-render-html/ts/highlight.ts | 6 +- .../ts/highlight_actions.ts | 2 +- crates/relune-render-html/ts/highlight_dom.ts | 4 +- crates/relune-render-html/ts/minimap.ts | 8 +- crates/relune-render-html/ts/pan_zoom.ts | 4 +- crates/relune-render-html/ts/pan_zoom_dom.ts | 2 + crates/relune-render-html/ts/search.ts | 2 +- crates/relune-render-html/ts/viewer_api.ts | 5 +- playground/esbuild.config.mjs | 47 +- playground/index.html | 92 +- playground/src/editor.ts | 106 +- playground/src/main.ts | 1211 +++++++++-------- playground/src/styles.css | 18 +- 28 files changed, 1124 insertions(+), 1088 deletions(-) diff --git a/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__cyclic_fk.snap b/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__cyclic_fk.snap index 466d8b9..c994e82 100644 --- a/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__cyclic_fk.snap +++ b/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__cyclic_fk.snap @@ -53,7 +53,7 @@ expression: "fixture_audit_snapshot(\"cyclic_fk.sql\")" }, "surfaces": { "html": { - "bytes": 452945, + "bytes": 453018, "contains_embedded_svg": true, "contains_metadata": true, "contains_viewport": true diff --git a/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__ecommerce.snap b/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__ecommerce.snap index e5e3f5d..2af19a0 100644 --- a/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__ecommerce.snap +++ b/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__ecommerce.snap @@ -49,7 +49,7 @@ expression: "fixture_audit_snapshot(\"ecommerce.sql\")" }, "surfaces": { "html": { - "bytes": 426839, + "bytes": 426912, "contains_embedded_svg": true, "contains_metadata": true, "contains_viewport": true diff --git a/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__join_heavy.snap b/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__join_heavy.snap index e1b4669..881951d 100644 --- a/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__join_heavy.snap +++ b/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__join_heavy.snap @@ -53,7 +53,7 @@ expression: "fixture_audit_snapshot(\"join_heavy.sql\")" }, "surfaces": { "html": { - "bytes": 608271, + "bytes": 608344, "contains_embedded_svg": true, "contains_metadata": true, "contains_viewport": true diff --git a/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__multi_schema.snap b/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__multi_schema.snap index 0f75f3b..a2ec6a8 100644 --- a/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__multi_schema.snap +++ b/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__multi_schema.snap @@ -47,7 +47,7 @@ expression: "fixture_audit_snapshot(\"multi_schema.sql\")" }, "surfaces": { "html": { - "bytes": 419866, + "bytes": 419939, "contains_embedded_svg": true, "contains_metadata": true, "contains_viewport": true diff --git a/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__simple_blog.snap b/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__simple_blog.snap index 2c2d3d8..18f5523 100644 --- a/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__simple_blog.snap +++ b/crates/relune-app/tests/snapshots/fixture_render_audit__fixture_render_audit__simple_blog.snap @@ -47,7 +47,7 @@ expression: "fixture_audit_snapshot(\"simple_blog.sql\")" }, "surfaces": { "html": { - "bytes": 380036, + "bytes": 380109, "contains_embedded_svg": true, "contains_metadata": true, "contains_viewport": true diff --git a/crates/relune-render-html/esbuild.config.mjs b/crates/relune-render-html/esbuild.config.mjs index d22d565..a795954 100644 --- a/crates/relune-render-html/esbuild.config.mjs +++ b/crates/relune-render-html/esbuild.config.mjs @@ -1,32 +1,33 @@ -import * as esbuild from "esbuild"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import * as esbuild from 'esbuild'; const __dirname = dirname(fileURLToPath(import.meta.url)); -const outDir = join(__dirname, "src", "js"); +const outDir = join(__dirname, 'src', 'js'); const entries = [ - "pan_zoom", - "search", - "filter_engine", - "group_toggle", - "collapse", - "highlight", - "minimap", - "shortcuts", - "load_motion", - "url_state", + 'pan_zoom', + 'search', + 'filter_engine', + 'group_toggle', + 'collapse', + 'highlight', + 'minimap', + 'shortcuts', + 'load_motion', + 'url_state', ]; await Promise.all( entries.map((name) => esbuild.build({ absWorkingDir: __dirname, - entryPoints: [join(__dirname, "ts", `${name}.ts`)], + entryPoints: [join(__dirname, 'ts', `${name}.ts`)], bundle: true, - format: "iife", - platform: "browser", - target: ["chrome120", "firefox120", "safari17"], + format: 'iife', + platform: 'browser', + target: ['chrome120', 'firefox120', 'safari17'], outfile: join(outDir, `${name}.js`), }), ), diff --git a/crates/relune-render-html/src/js/filter_engine.js b/crates/relune-render-html/src/js/filter_engine.js index f105c32..c500d4e 100644 --- a/crates/relune-render-html/src/js/filter_engine.js +++ b/crates/relune-render-html/src/js/filter_engine.js @@ -24,67 +24,6 @@ }); } - // ts/metadata.ts - var METADATA_ELEMENT_ID = "relune-metadata"; - function parseReluneMetadata() { - const el = document.getElementById(METADATA_ELEMENT_ID); - const raw = el?.textContent; - if (raw == null || raw === "") { - return null; - } - try { - return JSON.parse(raw); - } catch { - return null; - } - } - - // ts/viewer_api.ts - var VIEWER_RUNTIME_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.runtime"); - var VIEWER_READY_MODULES_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.ready_modules"); - var VIEWER_WAITERS_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.waiters"); - function getViewerRuntime() { - const viewerWindow = window; - if (viewerWindow[VIEWER_RUNTIME_KEY] === void 0) { - viewerWindow[VIEWER_RUNTIME_KEY] = {}; - } - return viewerWindow[VIEWER_RUNTIME_KEY]; - } - function readyModules() { - const viewerWindow = window; - if (viewerWindow[VIEWER_READY_MODULES_KEY] === void 0) { - viewerWindow[VIEWER_READY_MODULES_KEY] = /* @__PURE__ */ new Set(); - } - return viewerWindow[VIEWER_READY_MODULES_KEY]; - } - function runtimeWaiters() { - const viewerWindow = window; - if (viewerWindow[VIEWER_WAITERS_KEY] === void 0) { - viewerWindow[VIEWER_WAITERS_KEY] = []; - } - return viewerWindow[VIEWER_WAITERS_KEY]; - } - function markViewerModuleReady(module) { - readyModules().add(module); - flushViewerWaiters(); - } - function flushViewerWaiters() { - const ready = readyModules(); - const remaining = []; - for (const waiter of runtimeWaiters()) { - if (Array.from(waiter.modules).every((module) => ready.has(module))) { - waiter.callback(); - } else { - remaining.push(waiter); - } - } - const viewerWindow = window; - viewerWindow[VIEWER_WAITERS_KEY] = remaining; - } - function emitViewerEvent(name, detail) { - document.dispatchEvent(new CustomEvent(name, { detail })); - } - // ts/filter_engine_state.ts var DEFAULT_SCHEMA = "(default)"; function extractSchemaValues(table) { @@ -125,7 +64,7 @@ counts.set(v, (counts.get(v) ?? 0) + 1); } } - const allValues = [...valueSet].sort( + const allValues = [...valueSet].toSorted( (a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }) ); return { @@ -206,7 +145,7 @@ facetId: facet.id, label: facet.label, count: facet.selectedValues.size, - values: [...facet.selectedValues].sort() + values: [...facet.selectedValues].toSorted() }); } } @@ -391,6 +330,67 @@ rebuildFacetCheckboxes(details, visible, facet.selectedValues, facet.counts, onChange); } + // ts/metadata.ts + var METADATA_ELEMENT_ID = "relune-metadata"; + function parseReluneMetadata() { + const el = document.getElementById(METADATA_ELEMENT_ID); + const raw = el?.textContent; + if (raw == null || raw === "") { + return null; + } + try { + return JSON.parse(raw); + } catch { + return null; + } + } + + // ts/viewer_api.ts + var VIEWER_RUNTIME_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.runtime"); + var VIEWER_READY_MODULES_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.ready_modules"); + var VIEWER_WAITERS_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.waiters"); + function getViewerRuntime() { + const viewerWindow = window; + if (viewerWindow[VIEWER_RUNTIME_KEY] === void 0) { + viewerWindow[VIEWER_RUNTIME_KEY] = {}; + } + return viewerWindow[VIEWER_RUNTIME_KEY]; + } + function readyModules() { + const viewerWindow = window; + if (viewerWindow[VIEWER_READY_MODULES_KEY] === void 0) { + viewerWindow[VIEWER_READY_MODULES_KEY] = /* @__PURE__ */ new Set(); + } + return viewerWindow[VIEWER_READY_MODULES_KEY]; + } + function runtimeWaiters() { + const viewerWindow = window; + if (viewerWindow[VIEWER_WAITERS_KEY] === void 0) { + viewerWindow[VIEWER_WAITERS_KEY] = []; + } + return viewerWindow[VIEWER_WAITERS_KEY]; + } + function markViewerModuleReady(module) { + readyModules().add(module); + flushViewerWaiters(); + } + function flushViewerWaiters() { + const ready = readyModules(); + const remaining = []; + for (const waiter of runtimeWaiters()) { + if (Array.from(waiter.modules).every((module) => ready.has(module))) { + waiter.callback(); + } else { + remaining.push(waiter); + } + } + const viewerWindow = window; + viewerWindow[VIEWER_WAITERS_KEY] = remaining; + } + function emitViewerEvent(name, detail) { + document.dispatchEvent(new CustomEvent(name, { detail })); + } + // ts/filter_engine.ts { const sectionEl = document.getElementById("filter-section"); @@ -579,7 +579,7 @@ }, getFacetSelection(facetId) { const facet = state.facets.get(facetId); - return facet ? [...facet.selectedValues].sort() : []; + return facet ? [...facet.selectedValues].toSorted() : []; }, setFacetSelection(facetId, values) { const facet = state.facets.get(facetId); diff --git a/crates/relune-render-html/src/js/group_toggle.js b/crates/relune-render-html/src/js/group_toggle.js index c17ab40..afd9791 100644 --- a/crates/relune-render-html/src/js/group_toggle.js +++ b/crates/relune-render-html/src/js/group_toggle.js @@ -1,5 +1,56 @@ "use strict"; (() => { + // ts/group_toggle_dom.ts + function buildGroupListDOM(groups, container, onChange) { + container.innerHTML = ""; + for (const group of groups) { + const item = document.createElement("div"); + item.className = "group-item"; + item.setAttribute("data-group-id", group.id); + const checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.id = `group-${group.id}`; + checkbox.name = "relune-group-visibility"; + checkbox.checked = true; + const label = document.createElement("label"); + label.setAttribute("for", `group-${group.id}`); + label.textContent = group.label || group.id; + const count = document.createElement("span"); + count.className = "count"; + count.textContent = `(${group.table_ids?.length ?? 0})`; + item.appendChild(checkbox); + item.appendChild(label); + item.appendChild(count); + checkbox.addEventListener("change", () => { + onChange(group.id, checkbox.checked); + }); + container.appendChild(item); + } + } + function applyGroupVisibility(svg, tableIds, visible) { + for (const tableId of tableIds) { + const node = svg.querySelector(`.node[data-id="${CSS.escape(tableId)}"]`); + if (node) { + node.classList.toggle("hidden-by-group", !visible); + } + } + } + function updateEdgeVisibility(svg, isNodeHidden) { + svg.querySelectorAll(".edge").forEach((edge) => { + const fromId = edge.getAttribute("data-from"); + const toId = edge.getAttribute("data-to"); + const fromHidden = fromId ? isNodeHidden(fromId) : false; + const toHidden = toId ? isNodeHidden(toId) : false; + edge.classList.toggle("hidden-by-group", fromHidden || toHidden); + }); + } + function syncGroupItemClass(groupId, visible) { + const groupItem = document.querySelector(`.group-item[data-group-id="${CSS.escape(groupId)}"]`); + if (groupItem) { + groupItem.classList.toggle("hidden-group", !visible); + } + } + // ts/metadata.ts var METADATA_ELEMENT_ID = "relune-metadata"; function parseReluneMetadata() { @@ -105,57 +156,6 @@ } } - // ts/group_toggle_dom.ts - function buildGroupListDOM(groups, container, onChange) { - container.innerHTML = ""; - for (const group of groups) { - const item = document.createElement("div"); - item.className = "group-item"; - item.setAttribute("data-group-id", group.id); - const checkbox = document.createElement("input"); - checkbox.type = "checkbox"; - checkbox.id = `group-${group.id}`; - checkbox.name = "relune-group-visibility"; - checkbox.checked = true; - const label = document.createElement("label"); - label.setAttribute("for", `group-${group.id}`); - label.textContent = group.label || group.id; - const count = document.createElement("span"); - count.className = "count"; - count.textContent = `(${group.table_ids?.length ?? 0})`; - item.appendChild(checkbox); - item.appendChild(label); - item.appendChild(count); - checkbox.addEventListener("change", () => { - onChange(group.id, checkbox.checked); - }); - container.appendChild(item); - } - } - function applyGroupVisibility(svg, tableIds, visible) { - for (const tableId of tableIds) { - const node = svg.querySelector(`.node[data-id="${CSS.escape(tableId)}"]`); - if (node) { - node.classList.toggle("hidden-by-group", !visible); - } - } - } - function updateEdgeVisibility(svg, isNodeHidden) { - svg.querySelectorAll(".edge").forEach((edge) => { - const fromId = edge.getAttribute("data-from"); - const toId = edge.getAttribute("data-to"); - const fromHidden = fromId ? isNodeHidden(fromId) : false; - const toHidden = toId ? isNodeHidden(toId) : false; - edge.classList.toggle("hidden-by-group", fromHidden || toHidden); - }); - } - function syncGroupItemClass(groupId, visible) { - const groupItem = document.querySelector(`.group-item[data-group-id="${CSS.escape(groupId)}"]`); - if (groupItem) { - groupItem.classList.toggle("hidden-group", !visible); - } - } - // ts/group_toggle.ts { const metadata = parseReluneMetadata(); diff --git a/crates/relune-render-html/src/js/highlight.js b/crates/relune-render-html/src/js/highlight.js index 0b7fdc0..79f37d8 100644 --- a/crates/relune-render-html/src/js/highlight.js +++ b/crates/relune-render-html/src/js/highlight.js @@ -1,89 +1,5 @@ "use strict"; (() => { - // ts/metadata.ts - var METADATA_ELEMENT_ID = "relune-metadata"; - function parseReluneMetadata() { - const el = document.getElementById(METADATA_ELEMENT_ID); - const raw = el?.textContent; - if (raw == null || raw === "") { - return null; - } - try { - return JSON.parse(raw); - } catch { - return null; - } - } - function tableDisplayName(table) { - return table.label || table.table_name || table.id; - } - - // ts/viewer_api.ts - var VIEWER_RUNTIME_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.runtime"); - var VIEWER_READY_MODULES_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.ready_modules"); - var VIEWER_WAITERS_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.waiters"); - function getViewerRuntime() { - const viewerWindow = window; - if (viewerWindow[VIEWER_RUNTIME_KEY] === void 0) { - viewerWindow[VIEWER_RUNTIME_KEY] = {}; - } - return viewerWindow[VIEWER_RUNTIME_KEY]; - } - function readyModules() { - const viewerWindow = window; - if (viewerWindow[VIEWER_READY_MODULES_KEY] === void 0) { - viewerWindow[VIEWER_READY_MODULES_KEY] = /* @__PURE__ */ new Set(); - } - return viewerWindow[VIEWER_READY_MODULES_KEY]; - } - function runtimeWaiters() { - const viewerWindow = window; - if (viewerWindow[VIEWER_WAITERS_KEY] === void 0) { - viewerWindow[VIEWER_WAITERS_KEY] = []; - } - return viewerWindow[VIEWER_WAITERS_KEY]; - } - function markViewerModuleReady(module) { - readyModules().add(module); - flushViewerWaiters(); - } - function flushViewerWaiters() { - const ready = readyModules(); - const remaining = []; - for (const waiter of runtimeWaiters()) { - if (Array.from(waiter.modules).every((module) => ready.has(module))) { - waiter.callback(); - } else { - remaining.push(waiter); - } - } - const viewerWindow = window; - viewerWindow[VIEWER_WAITERS_KEY] = remaining; - } - function emitViewerEvent(name, detail) { - document.dispatchEvent(new CustomEvent(name, { detail })); - } - - // ts/highlight_state.ts - function createHighlightState(tables, edges) { - const tableById = new Map(tables.map((table) => [table.id, table])); - const inboundMap = {}; - const outboundMap = {}; - for (const edge of edges) { - (outboundMap[edge.from] ??= []).push({ node: edge.to, edge }); - (inboundMap[edge.to] ??= []).push({ node: edge.from, edge }); - } - return { - hoveredNode: null, - selectedNode: null, - traversalDepth: 1, - tableById, - inboundMap, - outboundMap, - edges - }; - } - // ts/highlight_actions.ts function collectNeighborhood(nodeId, state, depth = 1) { const neighborIds = /* @__PURE__ */ new Set(); @@ -143,6 +59,24 @@ ); } + // ts/metadata.ts + var METADATA_ELEMENT_ID = "relune-metadata"; + function parseReluneMetadata() { + const el = document.getElementById(METADATA_ELEMENT_ID); + const raw = el?.textContent; + if (raw == null || raw === "") { + return null; + } + try { + return JSON.parse(raw); + } catch { + return null; + } + } + function tableDisplayName(table) { + return table.label || table.table_name || table.id; + } + // ts/highlight_dom.ts var ALLOWED_DIFF_KINDS = /* @__PURE__ */ new Set(["added", "removed", "modified"]); var ALLOWED_SEVERITIES = /* @__PURE__ */ new Set(["error", "warning", "info", "hint"]); @@ -519,6 +453,72 @@ return button; } + // ts/highlight_state.ts + function createHighlightState(tables, edges) { + const tableById = new Map(tables.map((table) => [table.id, table])); + const inboundMap = {}; + const outboundMap = {}; + for (const edge of edges) { + (outboundMap[edge.from] ??= []).push({ node: edge.to, edge }); + (inboundMap[edge.to] ??= []).push({ node: edge.from, edge }); + } + return { + hoveredNode: null, + selectedNode: null, + traversalDepth: 1, + tableById, + inboundMap, + outboundMap, + edges + }; + } + + // ts/viewer_api.ts + var VIEWER_RUNTIME_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.runtime"); + var VIEWER_READY_MODULES_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.ready_modules"); + var VIEWER_WAITERS_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.waiters"); + function getViewerRuntime() { + const viewerWindow = window; + if (viewerWindow[VIEWER_RUNTIME_KEY] === void 0) { + viewerWindow[VIEWER_RUNTIME_KEY] = {}; + } + return viewerWindow[VIEWER_RUNTIME_KEY]; + } + function readyModules() { + const viewerWindow = window; + if (viewerWindow[VIEWER_READY_MODULES_KEY] === void 0) { + viewerWindow[VIEWER_READY_MODULES_KEY] = /* @__PURE__ */ new Set(); + } + return viewerWindow[VIEWER_READY_MODULES_KEY]; + } + function runtimeWaiters() { + const viewerWindow = window; + if (viewerWindow[VIEWER_WAITERS_KEY] === void 0) { + viewerWindow[VIEWER_WAITERS_KEY] = []; + } + return viewerWindow[VIEWER_WAITERS_KEY]; + } + function markViewerModuleReady(module) { + readyModules().add(module); + flushViewerWaiters(); + } + function flushViewerWaiters() { + const ready = readyModules(); + const remaining = []; + for (const waiter of runtimeWaiters()) { + if (Array.from(waiter.modules).every((module) => ready.has(module))) { + waiter.callback(); + } else { + remaining.push(waiter); + } + } + const viewerWindow = window; + viewerWindow[VIEWER_WAITERS_KEY] = remaining; + } + function emitViewerEvent(name, detail) { + document.dispatchEvent(new CustomEvent(name, { detail })); + } + // ts/highlight.ts { const metadata = parseReluneMetadata(); diff --git a/crates/relune-render-html/src/js/minimap.js b/crates/relune-render-html/src/js/minimap.js index 7c70aed..e220fbc 100644 --- a/crates/relune-render-html/src/js/minimap.js +++ b/crates/relune-render-html/src/js/minimap.js @@ -112,8 +112,8 @@ updateFrame(initialState); } hostSvg.addEventListener("click", (event) => { - const bounds2 = runtime.viewport?.getDiagramBounds(); - if (bounds2 === null || bounds2 === void 0) { + const clickBounds = runtime.viewport?.getDiagramBounds(); + if (clickBounds === null || clickBounds === void 0) { return; } const rect = hostSvg.getBoundingClientRect(); @@ -123,8 +123,8 @@ const percentX = (event.clientX - rect.left) / rect.width; const percentY = (event.clientY - rect.top) / rect.height; runtime.viewport?.center( - bounds2.x + bounds2.width * percentX, - bounds2.y + bounds2.height * percentY + clickBounds.x + clickBounds.width * percentX, + clickBounds.y + clickBounds.height * percentY ); }); } diff --git a/crates/relune-render-html/src/js/pan_zoom.js b/crates/relune-render-html/src/js/pan_zoom.js index 9cb6cea..7f492d4 100644 --- a/crates/relune-render-html/src/js/pan_zoom.js +++ b/crates/relune-render-html/src/js/pan_zoom.js @@ -1,49 +1,58 @@ "use strict"; (() => { - // ts/viewer_api.ts - var VIEWER_RUNTIME_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.runtime"); - var VIEWER_READY_MODULES_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.ready_modules"); - var VIEWER_WAITERS_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.waiters"); - function getViewerRuntime() { - const viewerWindow = window; - if (viewerWindow[VIEWER_RUNTIME_KEY] === void 0) { - viewerWindow[VIEWER_RUNTIME_KEY] = {}; - } - return viewerWindow[VIEWER_RUNTIME_KEY]; + // ts/pan_zoom_dom.ts + function getAvailableViewport(viewportEl) { + const rect = viewportEl.getBoundingClientRect(); + const leftInset = overlayInset(viewportEl, document.getElementById("search-panel"), "left"); + const rightInset = overlayInset(viewportEl, document.getElementById("detail-drawer"), "right"); + const topInset = Math.max( + overlayInset(viewportEl, document.querySelector("h1"), "top"), + overlayInset(viewportEl, document.getElementById("filter-reset-bar"), "top") + ); + const bottomInset = Math.max( + overlayInset(viewportEl, document.getElementById("viewer-controls"), "bottom"), + overlayInset(viewportEl, document.getElementById("minimap-shell"), "bottom") + ); + return { + left: leftInset, + top: topInset, + width: Math.max(rect.width - leftInset - rightInset, 120), + height: Math.max(rect.height - topInset - bottomInset, 120) + }; } - function readyModules() { - const viewerWindow = window; - if (viewerWindow[VIEWER_READY_MODULES_KEY] === void 0) { - viewerWindow[VIEWER_READY_MODULES_KEY] = /* @__PURE__ */ new Set(); + function overlayInset(viewportEl, element, side) { + if (!(element instanceof HTMLElement) || element.hasAttribute("hidden")) { + return 0; } - return viewerWindow[VIEWER_READY_MODULES_KEY]; - } - function runtimeWaiters() { - const viewerWindow = window; - if (viewerWindow[VIEWER_WAITERS_KEY] === void 0) { - viewerWindow[VIEWER_WAITERS_KEY] = []; + const viewportRect = viewportEl.getBoundingClientRect(); + const rect = element.getBoundingClientRect(); + if (rect.width <= 0 || rect.height <= 0) { + return 0; } - return viewerWindow[VIEWER_WAITERS_KEY]; - } - function markViewerModuleReady(module) { - readyModules().add(module); - flushViewerWaiters(); - } - function flushViewerWaiters() { - const ready = readyModules(); - const remaining = []; - for (const waiter of runtimeWaiters()) { - if (Array.from(waiter.modules).every((module) => ready.has(module))) { - waiter.callback(); - } else { - remaining.push(waiter); - } + switch (side) { + case "left": + return Math.max(0, rect.right - viewportRect.left + 16); + case "right": + return Math.max(0, viewportRect.right - rect.left + 16); + case "top": + return Math.max(0, rect.bottom - viewportRect.top + 16); + case "bottom": + return Math.max(0, viewportRect.bottom - rect.top + 16); + default: + return 0; } - const viewerWindow = window; - viewerWindow[VIEWER_WAITERS_KEY] = remaining; } - function emitViewerEvent(name, detail) { - document.dispatchEvent(new CustomEvent(name, { detail })); + function applyTransform(svg, canvasEl, zoomLevelEl, scale, panX, panY, diagram) { + const scaledWidth = diagram.width * scale; + const scaledHeight = diagram.height * scale; + svg.style.width = `${scaledWidth}px`; + svg.style.height = `${scaledHeight}px`; + canvasEl.style.width = `${scaledWidth}px`; + canvasEl.style.height = `${scaledHeight}px`; + canvasEl.style.transform = `translate(${panX}px, ${panY}px)`; + if (zoomLevelEl instanceof HTMLElement) { + zoomLevelEl.textContent = `${Math.round(scale * 100)}%`; + } } // ts/pan_zoom_state.ts @@ -119,57 +128,50 @@ }; } - // ts/pan_zoom_dom.ts - function getAvailableViewport(viewportEl) { - const rect = viewportEl.getBoundingClientRect(); - const leftInset = overlayInset(viewportEl, document.getElementById("search-panel"), "left"); - const rightInset = overlayInset(viewportEl, document.getElementById("detail-drawer"), "right"); - const topInset = Math.max( - overlayInset(viewportEl, document.querySelector("h1"), "top"), - overlayInset(viewportEl, document.getElementById("filter-reset-bar"), "top") - ); - const bottomInset = Math.max( - overlayInset(viewportEl, document.getElementById("viewer-controls"), "bottom"), - overlayInset(viewportEl, document.getElementById("minimap-shell"), "bottom") - ); - return { - left: leftInset, - top: topInset, - width: Math.max(rect.width - leftInset - rightInset, 120), - height: Math.max(rect.height - topInset - bottomInset, 120) - }; - } - function overlayInset(viewportEl, element, side) { - if (!(element instanceof HTMLElement) || element.hasAttribute("hidden")) { - return 0; + // ts/viewer_api.ts + var VIEWER_RUNTIME_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.runtime"); + var VIEWER_READY_MODULES_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.ready_modules"); + var VIEWER_WAITERS_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.waiters"); + function getViewerRuntime() { + const viewerWindow = window; + if (viewerWindow[VIEWER_RUNTIME_KEY] === void 0) { + viewerWindow[VIEWER_RUNTIME_KEY] = {}; } - const viewportRect = viewportEl.getBoundingClientRect(); - const rect = element.getBoundingClientRect(); - if (rect.width <= 0 || rect.height <= 0) { - return 0; + return viewerWindow[VIEWER_RUNTIME_KEY]; + } + function readyModules() { + const viewerWindow = window; + if (viewerWindow[VIEWER_READY_MODULES_KEY] === void 0) { + viewerWindow[VIEWER_READY_MODULES_KEY] = /* @__PURE__ */ new Set(); } - switch (side) { - case "left": - return Math.max(0, rect.right - viewportRect.left + 16); - case "right": - return Math.max(0, viewportRect.right - rect.left + 16); - case "top": - return Math.max(0, rect.bottom - viewportRect.top + 16); - case "bottom": - return Math.max(0, viewportRect.bottom - rect.top + 16); + return viewerWindow[VIEWER_READY_MODULES_KEY]; + } + function runtimeWaiters() { + const viewerWindow = window; + if (viewerWindow[VIEWER_WAITERS_KEY] === void 0) { + viewerWindow[VIEWER_WAITERS_KEY] = []; } + return viewerWindow[VIEWER_WAITERS_KEY]; } - function applyTransform(svg, canvasEl, zoomLevelEl, scale, panX, panY, diagram) { - const scaledWidth = diagram.width * scale; - const scaledHeight = diagram.height * scale; - svg.style.width = `${scaledWidth}px`; - svg.style.height = `${scaledHeight}px`; - canvasEl.style.width = `${scaledWidth}px`; - canvasEl.style.height = `${scaledHeight}px`; - canvasEl.style.transform = `translate(${panX}px, ${panY}px)`; - if (zoomLevelEl instanceof HTMLElement) { - zoomLevelEl.textContent = `${Math.round(scale * 100)}%`; + function markViewerModuleReady(module) { + readyModules().add(module); + flushViewerWaiters(); + } + function flushViewerWaiters() { + const ready = readyModules(); + const remaining = []; + for (const waiter of runtimeWaiters()) { + if (Array.from(waiter.modules).every((module) => ready.has(module))) { + waiter.callback(); + } else { + remaining.push(waiter); + } } + const viewerWindow = window; + viewerWindow[VIEWER_WAITERS_KEY] = remaining; + } + function emitViewerEvent(name, detail) { + document.dispatchEvent(new CustomEvent(name, { detail })); } // ts/pan_zoom.ts diff --git a/crates/relune-render-html/src/js/search.js b/crates/relune-render-html/src/js/search.js index 807f24b..ca8f4cb 100644 --- a/crates/relune-render-html/src/js/search.js +++ b/crates/relune-render-html/src/js/search.js @@ -42,6 +42,27 @@ return table.label || table.table_name || table.id; } + // ts/search_actions.ts + function computeSearchMatches(nodes, tableNames, query) { + const q = query.toLowerCase().trim(); + const results = []; + let matchCount = 0; + nodes.forEach((node) => { + if (q === "") { + results.push({ node, matches: true }); + matchCount += 1; + return; + } + const tableId = node.getAttribute("data-id") ?? node.getAttribute("data-table-id") ?? ""; + const tableName = tableNames[tableId] ?? tableId; + const nodeText = node.textContent?.toLowerCase() ?? ""; + const matches = tableName.toLowerCase().includes(q) || tableId.toLowerCase().includes(q) || nodeText.includes(q); + results.push({ node, matches }); + if (matches) matchCount += 1; + }); + return { results, matchCount, total: nodes.length }; + } + // ts/viewer_api.ts var VIEWER_RUNTIME_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.runtime"); var VIEWER_READY_MODULES_KEY = /* @__PURE__ */ Symbol.for("relune.viewer.ready_modules"); @@ -88,27 +109,6 @@ document.dispatchEvent(new CustomEvent(name, { detail })); } - // ts/search_actions.ts - function computeSearchMatches(nodes, tableNames, query) { - const q = query.toLowerCase().trim(); - const results = []; - let matchCount = 0; - nodes.forEach((node) => { - if (q === "") { - results.push({ node, matches: true }); - matchCount += 1; - return; - } - const tableId = node.getAttribute("data-id") ?? node.getAttribute("data-table-id") ?? ""; - const tableName = tableNames[tableId] ?? tableId; - const nodeText = node.textContent?.toLowerCase() ?? ""; - const matches = tableName.toLowerCase().includes(q) || tableId.toLowerCase().includes(q) || nodeText.includes(q); - results.push({ node, matches }); - if (matches) matchCount += 1; - }); - return { results, matchCount, total: nodes.length }; - } - // ts/search.ts { const searchInput = document.getElementById("table-search"); diff --git a/crates/relune-render-html/ts/filter_engine.ts b/crates/relune-render-html/ts/filter_engine.ts index 17706e2..6cb382e 100644 --- a/crates/relune-render-html/ts/filter_engine.ts +++ b/crates/relune-render-html/ts/filter_engine.ts @@ -1,14 +1,4 @@ import { syncEdgeDimming } from './edge_filters'; -import { parseReluneMetadata, type TableMetadata } from './metadata'; -import { emitViewerEvent, getViewerRuntime, markViewerModuleReady } from './viewer_api'; -import { - createFilterEngineState, - tableMatchesAllFacets, - hasActiveFilters, - activeFilterSummary, - type FacetId, - type FilterMode, -} from './filter_engine_state'; import { buildFilterModeSwitcher, syncModeSwitcher, @@ -19,6 +9,16 @@ import { renderActiveFilterSummary, syncFilterResetBar, } from './filter_engine_dom'; +import { + createFilterEngineState, + tableMatchesAllFacets, + hasActiveFilters, + activeFilterSummary, + type FacetId, + type FilterMode, +} from './filter_engine_state'; +import { parseReluneMetadata, type TableMetadata } from './metadata'; +import { emitViewerEvent, getViewerRuntime, markViewerModuleReady } from './viewer_api'; { const sectionEl = document.getElementById('filter-section'); @@ -271,7 +271,7 @@ import { }, getFacetSelection(facetId: FacetId): string[] { const facet = state.facets.get(facetId); - return facet ? [...facet.selectedValues].sort() : []; + return facet ? [...facet.selectedValues].toSorted() : []; }, setFacetSelection(facetId: FacetId, values: string[]): void { const facet = state.facets.get(facetId); diff --git a/crates/relune-render-html/ts/filter_engine_state.ts b/crates/relune-render-html/ts/filter_engine_state.ts index e8f1fab..44fc98d 100644 --- a/crates/relune-render-html/ts/filter_engine_state.ts +++ b/crates/relune-render-html/ts/filter_engine_state.ts @@ -71,7 +71,7 @@ function buildFacet( } } - const allValues = [...valueSet].sort((a, b) => + const allValues = [...valueSet].toSorted((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }), ); @@ -189,7 +189,7 @@ export function activeFilterSummary(state: FilterEngineState): FacetSummaryItem[ facetId: facet.id, label: facet.label, count: facet.selectedValues.size, - values: [...facet.selectedValues].sort(), + values: [...facet.selectedValues].toSorted(), }); } } diff --git a/crates/relune-render-html/ts/group_toggle.ts b/crates/relune-render-html/ts/group_toggle.ts index a027b0c..b806335 100644 --- a/crates/relune-render-html/ts/group_toggle.ts +++ b/crates/relune-render-html/ts/group_toggle.ts @@ -1,3 +1,9 @@ +import { + buildGroupListDOM, + applyGroupVisibility, + updateEdgeVisibility, + syncGroupItemClass, +} from './group_toggle_dom'; import { parseReluneMetadata, type GroupMetadata } from './metadata'; import { emitViewerEvent, @@ -6,12 +12,6 @@ import { markViewerModuleReady, reportSessionStorageError, } from './viewer_api'; -import { - buildGroupListDOM, - applyGroupVisibility, - updateEdgeVisibility, - syncGroupItemClass, -} from './group_toggle_dom'; { const metadata = parseReluneMetadata(); diff --git a/crates/relune-render-html/ts/highlight.ts b/crates/relune-render-html/ts/highlight.ts index 5b3fba4..8af3859 100644 --- a/crates/relune-render-html/ts/highlight.ts +++ b/crates/relune-render-html/ts/highlight.ts @@ -1,6 +1,3 @@ -import { parseReluneMetadata, type TableMetadata } from './metadata'; -import { emitViewerEvent, getViewerRuntime, markViewerModuleReady } from './viewer_api'; -import { createHighlightState } from './highlight_state'; import { computeHoverPreview, computeNeighborHighlights, @@ -19,6 +16,9 @@ import { type HoverPopoverElements, type PopoverPosition, } from './highlight_dom'; +import { createHighlightState } from './highlight_state'; +import { parseReluneMetadata, type TableMetadata } from './metadata'; +import { emitViewerEvent, getViewerRuntime, markViewerModuleReady } from './viewer_api'; { const metadata = parseReluneMetadata(); diff --git a/crates/relune-render-html/ts/highlight_actions.ts b/crates/relune-render-html/ts/highlight_actions.ts index 78e37c0..ee89bd2 100644 --- a/crates/relune-render-html/ts/highlight_actions.ts +++ b/crates/relune-render-html/ts/highlight_actions.ts @@ -1,5 +1,5 @@ -import type { TableMetadata } from './metadata'; import type { HighlightState } from './highlight_state'; +import type { TableMetadata } from './metadata'; interface HighlightNeighborhood { neighborIds: Set; diff --git a/crates/relune-render-html/ts/highlight_dom.ts b/crates/relune-render-html/ts/highlight_dom.ts index e6cfc25..59b4a49 100644 --- a/crates/relune-render-html/ts/highlight_dom.ts +++ b/crates/relune-render-html/ts/highlight_dom.ts @@ -1,11 +1,11 @@ +import type { HoverPreview, NeighborHighlight } from './highlight_actions'; +import type { HighlightState } from './highlight_state'; import { tableDisplayName, type EdgeMetadata, type IssueMetadata, type TableMetadata, } from './metadata'; -import type { HighlightState } from './highlight_state'; -import type { HoverPreview, NeighborHighlight } from './highlight_actions'; // ── Shared helpers ────────────────────────────────────────────────────────── diff --git a/crates/relune-render-html/ts/minimap.ts b/crates/relune-render-html/ts/minimap.ts index c930b67..73ce765 100644 --- a/crates/relune-render-html/ts/minimap.ts +++ b/crates/relune-render-html/ts/minimap.ts @@ -129,8 +129,8 @@ interface MinimapNode { } hostSvg.addEventListener('click', (event: MouseEvent) => { - const bounds = runtime.viewport?.getDiagramBounds(); - if (bounds === null || bounds === undefined) { + const clickBounds = runtime.viewport?.getDiagramBounds(); + if (clickBounds === null || clickBounds === undefined) { return; } @@ -142,8 +142,8 @@ interface MinimapNode { const percentX = (event.clientX - rect.left) / rect.width; const percentY = (event.clientY - rect.top) / rect.height; runtime.viewport?.center( - bounds.x + bounds.width * percentX, - bounds.y + bounds.height * percentY, + clickBounds.x + clickBounds.width * percentX, + clickBounds.y + clickBounds.height * percentY, ); }); } diff --git a/crates/relune-render-html/ts/pan_zoom.ts b/crates/relune-render-html/ts/pan_zoom.ts index 8c41d39..451756d 100644 --- a/crates/relune-render-html/ts/pan_zoom.ts +++ b/crates/relune-render-html/ts/pan_zoom.ts @@ -1,4 +1,4 @@ -import { emitViewerEvent, getViewerRuntime, markViewerModuleReady } from './viewer_api'; +import { getAvailableViewport, applyTransform } from './pan_zoom_dom'; import { clamp, parseViewBox, @@ -7,7 +7,7 @@ import { computeZoomAt, buildViewportState, } from './pan_zoom_state'; -import { getAvailableViewport, applyTransform } from './pan_zoom_dom'; +import { emitViewerEvent, getViewerRuntime, markViewerModuleReady } from './viewer_api'; { const viewportEl = document.getElementById('viewport'); diff --git a/crates/relune-render-html/ts/pan_zoom_dom.ts b/crates/relune-render-html/ts/pan_zoom_dom.ts index a8a5375..423f20b 100644 --- a/crates/relune-render-html/ts/pan_zoom_dom.ts +++ b/crates/relune-render-html/ts/pan_zoom_dom.ts @@ -52,6 +52,8 @@ function overlayInset( return Math.max(0, rect.bottom - viewportRect.top + 16); case 'bottom': return Math.max(0, viewportRect.bottom - rect.top + 16); + default: + return 0; } } diff --git a/crates/relune-render-html/ts/search.ts b/crates/relune-render-html/ts/search.ts index 9fd5adf..b9fd5b7 100644 --- a/crates/relune-render-html/ts/search.ts +++ b/crates/relune-render-html/ts/search.ts @@ -1,7 +1,7 @@ import { syncEdgeDimming } from './edge_filters'; import { parseReluneMetadata, tableDisplayName, type TableMetadata } from './metadata'; -import { emitViewerEvent, getViewerRuntime, markViewerModuleReady } from './viewer_api'; import { computeSearchMatches } from './search_actions'; +import { emitViewerEvent, getViewerRuntime, markViewerModuleReady } from './viewer_api'; { const searchInput = document.getElementById('table-search'); diff --git a/crates/relune-render-html/ts/viewer_api.ts b/crates/relune-render-html/ts/viewer_api.ts index 458375c..99a62cc 100644 --- a/crates/relune-render-html/ts/viewer_api.ts +++ b/crates/relune-render-html/ts/viewer_api.ts @@ -141,8 +141,8 @@ function flushViewerWaiters(): void { viewerWindow[VIEWER_WAITERS_KEY] = remaining; } -export function emitViewerEvent(name: string, detail: T): void { - document.dispatchEvent(new CustomEvent(name, { detail })); +export function emitViewerEvent(name: string, detail: unknown): void { + document.dispatchEvent(new CustomEvent(name, { detail })); } function noticeStack(): HTMLElement { @@ -184,6 +184,7 @@ export function reportSessionStorageError(action: string, error: unknown): void ); return; } + // eslint-disable-next-line no-console console.warn(`Session storage error while ${action}`, error); } diff --git a/playground/esbuild.config.mjs b/playground/esbuild.config.mjs index ff5bb2a..2c31b9c 100644 --- a/playground/esbuild.config.mjs +++ b/playground/esbuild.config.mjs @@ -1,41 +1,36 @@ -import * as esbuild from "esbuild"; -import { cp, mkdir, writeFile } from "node:fs/promises"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; +import { cp, mkdir, writeFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import * as esbuild from 'esbuild'; const __dirname = dirname(fileURLToPath(import.meta.url)); const rootDir = dirname(__dirname); -const distDir = join(__dirname, "dist"); -const assetsDir = join(distDir, "assets"); -const examplesDir = join(distDir, "examples"); +const distDir = join(__dirname, 'dist'); +const assetsDir = join(distDir, 'assets'); +const examplesDir = join(distDir, 'examples'); await mkdir(assetsDir, { recursive: true }); await mkdir(examplesDir, { recursive: true }); await esbuild.build({ absWorkingDir: __dirname, - entryPoints: [join(__dirname, "src", "main.ts")], + entryPoints: [join(__dirname, 'src', 'main.ts')], bundle: true, - format: "esm", + format: 'esm', outdir: assetsDir, - platform: "browser", - target: ["chrome120", "firefox120", "safari17"], - external: ["../pkg/relune_wasm.js"], + platform: 'browser', + target: ['chrome120', 'firefox120', 'safari17'], + external: ['../pkg/relune_wasm.js'], }); -await cp(join(__dirname, "index.html"), join(distDir, "index.html")); -await cp(join(__dirname, "src", "styles.css"), join(assetsDir, "styles.css")); -await cp(join(rootDir, "assets", "logo.png"), join(assetsDir, "logo.png")); -await cp( - join(rootDir, "fixtures", "sql", "simple_blog.sql"), - join(examplesDir, "simple_blog.sql"), -); -await cp( - join(rootDir, "fixtures", "sql", "ecommerce.sql"), - join(examplesDir, "ecommerce.sql"), -); +await cp(join(__dirname, 'index.html'), join(distDir, 'index.html')); +await cp(join(__dirname, 'src', 'styles.css'), join(assetsDir, 'styles.css')); +await cp(join(rootDir, 'assets', 'logo.png'), join(assetsDir, 'logo.png')); +await cp(join(rootDir, 'fixtures', 'sql', 'simple_blog.sql'), join(examplesDir, 'simple_blog.sql')); +await cp(join(rootDir, 'fixtures', 'sql', 'ecommerce.sql'), join(examplesDir, 'ecommerce.sql')); await cp( - join(rootDir, "fixtures", "sql", "multi_schema.sql"), - join(examplesDir, "multi_schema.sql"), + join(rootDir, 'fixtures', 'sql', 'multi_schema.sql'), + join(examplesDir, 'multi_schema.sql'), ); -await writeFile(join(distDir, ".nojekyll"), ""); +await writeFile(join(distDir, '.nojekyll'), ''); diff --git a/playground/index.html b/playground/index.html index 96ba3d1..5e75857 100644 --- a/playground/index.html +++ b/playground/index.html @@ -2,10 +2,7 @@ - + Relune Schema Workbench
- +
Schema Workbench Loading… @@ -46,7 +37,9 @@ rel="noopener" > - + GitHub @@ -71,7 +64,16 @@ aria-label="Collapse sidebar" title="Collapse sidebar" > - + @@ -84,9 +86,7 @@ Example - +
@@ -282,7 +282,8 @@ - Compare keeps before and after as separate editors instead of squeezing diff into the viewer mode. + Compare keeps before and after as separate editors instead of squeezing diff + into the viewer mode.
- @@ -1835,15 +1839,15 @@ function renderInspectPanel( (foreignKey) => `
${escapeHtml( - foreignKey.name || foreignKey.from_columns.join(", "), + foreignKey.name || foreignKey.from_columns.join(', '), )}
- ${escapeHtml(foreignKey.from_columns.join(", "))} → ${escapeHtml(foreignKey.to_table)}(${escapeHtml(foreignKey.to_columns.join(", "))}) + ${escapeHtml(foreignKey.from_columns.join(', '))} → ${escapeHtml(foreignKey.to_table)}(${escapeHtml(foreignKey.to_columns.join(', '))})
`, ) - .join("") + .join('') }
@@ -1858,14 +1862,14 @@ function renderInspectPanel( (index) => `
- ${escapeHtml(index.name || index.columns.join(", "))} - ${index.is_unique ? 'UNIQUE' : ""} + ${escapeHtml(index.name || index.columns.join(', '))} + ${index.is_unique ? 'UNIQUE' : ''}
-
${escapeHtml(index.columns.join(", "))}
+
${escapeHtml(index.columns.join(', '))}
`, ) - .join("") + .join('') }
@@ -1887,15 +1891,15 @@ function renderLintPanel(issues: readonly LintIssue[]): void { ${issue.severity} ${escapeHtml(issue.category)} ${escapeHtml(issue.rule_id)} - ${issue.table_name ? `${escapeHtml(issue.table_name)}` : ""} - ${issue.column_name ? `${escapeHtml(issue.column_name)}` : ""} + ${issue.table_name ? `${escapeHtml(issue.table_name)}` : ''} + ${issue.column_name ? `${escapeHtml(issue.column_name)}` : ''}
${escapeHtml(issue.message)}
- ${issue.hint ? `
${escapeHtml(issue.hint)}
` : ""} + ${issue.hint ? `
${escapeHtml(issue.hint)}
` : ''} `, ) - .join(""); + .join(''); } function renderCompareSummary(diff: SchemaDiff): void { @@ -1912,14 +1916,14 @@ function renderCompareSummary(diff: SchemaDiff): void { diff.modified_enums.length; compareSummaryCount.textContent = `${totalObjects}`; compareSummary.innerHTML = [ - ["Added tables", `${diff.added_tables.length}`], - ["Removed tables", `${diff.removed_tables.length}`], - ["Modified tables", `${diff.modified_tables.length}`], - ["Added views", `${diff.added_views.length}`], - ["Removed views", `${diff.removed_views.length}`], - ["Modified views", `${diff.modified_views.length}`], - ["Added enums", `${diff.added_enums.length}`], - ["Removed enums", `${diff.removed_enums.length}`], + ['Added tables', `${diff.added_tables.length}`], + ['Removed tables', `${diff.removed_tables.length}`], + ['Modified tables', `${diff.modified_tables.length}`], + ['Added views', `${diff.added_views.length}`], + ['Removed views', `${diff.removed_views.length}`], + ['Modified views', `${diff.modified_views.length}`], + ['Added enums', `${diff.added_enums.length}`], + ['Removed enums', `${diff.removed_enums.length}`], ] .map( ([label, value]) => ` @@ -1929,49 +1933,49 @@ function renderCompareSummary(diff: SchemaDiff): void { `, ) - .join(""); + .join(''); const changeCards: string[] = []; for (const tableName of diff.added_tables) { - changeCards.push(buildChangeCard("Added table", tableName, "Table exists only in after.")); + changeCards.push(buildChangeCard('Added table', tableName, 'Table exists only in after.')); } for (const tableName of diff.removed_tables) { - changeCards.push(buildChangeCard("Removed table", tableName, "Table exists only in before.")); + changeCards.push(buildChangeCard('Removed table', tableName, 'Table exists only in before.')); } for (const table of diff.modified_tables) { changeCards.push( buildChangeCard( - "Modified table", + 'Modified table', table.table_name, `${table.column_diffs.length} column changes · ${table.fk_diffs.length} foreign key changes · ${table.index_diffs.length} index changes`, ), ); } for (const viewName of diff.added_views) { - changeCards.push(buildChangeCard("Added view", viewName, "View exists only in after.")); + changeCards.push(buildChangeCard('Added view', viewName, 'View exists only in after.')); } for (const viewName of diff.removed_views) { - changeCards.push(buildChangeCard("Removed view", viewName, "View exists only in before.")); + changeCards.push(buildChangeCard('Removed view', viewName, 'View exists only in before.')); } for (const view of diff.modified_views) { changeCards.push( buildChangeCard( - "Modified view", + 'Modified view', view.view_name, `${view.column_diffs.length} column changes`, ), ); } for (const enumName of diff.added_enums) { - changeCards.push(buildChangeCard("Added enum", enumName, "Enum exists only in after.")); + changeCards.push(buildChangeCard('Added enum', enumName, 'Enum exists only in after.')); } for (const enumName of diff.removed_enums) { - changeCards.push(buildChangeCard("Removed enum", enumName, "Enum exists only in before.")); + changeCards.push(buildChangeCard('Removed enum', enumName, 'Enum exists only in before.')); } for (const schemaEnum of diff.modified_enums) { changeCards.push( buildChangeCard( - "Modified enum", + 'Modified enum', schemaEnum.enum_name, `${schemaEnum.value_diffs.length} enum value changes`, ), @@ -1981,7 +1985,7 @@ function renderCompareSummary(diff: SchemaDiff): void { compareObjectList.innerHTML = changeCards.length === 0 ? '
  • No schema changes detected.

  • ' - : changeCards.join(""); + : changeCards.join(''); } function buildChangeCard(kind: string, name: string, body: string): string { @@ -2013,7 +2017,7 @@ function renderMetricCards(entries: readonly [string, string][]): void { `, ) - .join(""); + .join(''); } function renderDiagnostics(diagnostics: readonly WasmDiagnostic[]): void { @@ -2037,7 +2041,7 @@ function renderDiagnostics(diagnostics: readonly WasmDiagnostic[]): void { `; }) - .join(""); + .join(''); } function configureActions(actions: { @@ -2049,9 +2053,9 @@ function configureActions(actions: { primaryAction = actions.primary ?? null; secondaryAction = actions.secondary ?? null; - copyOutputButton.textContent = copyAction?.label ?? "Copy"; - downloadPrimaryButton.textContent = primaryAction?.label ?? "Download"; - downloadSecondaryButton.textContent = secondaryAction?.label ?? "More"; + copyOutputButton.textContent = copyAction?.label ?? 'Copy'; + downloadPrimaryButton.textContent = primaryAction?.label ?? 'Download'; + downloadSecondaryButton.textContent = secondaryAction?.label ?? 'More'; copyOutputButton.hidden = copyAction === null; downloadPrimaryButton.hidden = primaryAction === null; @@ -2070,18 +2074,18 @@ function resetOutputPanels(): void { textOutputPanel.hidden = true; reviewPanel.hidden = true; reviewSuppressedPanel.hidden = true; - inspectTableList.innerHTML = ""; - inspectDetail.innerHTML = ""; - lintIssueList.innerHTML = ""; - compareSummary.innerHTML = ""; - compareObjectList.innerHTML = ""; - textOutput.textContent = ""; - textOutputMeta.textContent = ""; - reviewSummaryBadges.textContent = ""; + inspectTableList.innerHTML = ''; + inspectDetail.innerHTML = ''; + lintIssueList.innerHTML = ''; + compareSummary.innerHTML = ''; + compareObjectList.innerHTML = ''; + textOutput.textContent = ''; + textOutputMeta.textContent = ''; + reviewSummaryBadges.textContent = ''; reviewDialectNote.hidden = true; - reviewDialectNote.textContent = ""; - reviewFindingList.innerHTML = ""; - reviewSuppressedList.innerHTML = ""; + reviewDialectNote.textContent = ''; + reviewFindingList.innerHTML = ''; + reviewSuppressedList.innerHTML = ''; resetActions(); } @@ -2091,13 +2095,13 @@ function populateInspectTableOptions(tableNames: readonly string[], selectedTabl ...tableNames.map( (tableName) => ``, ), - ].join(""); + ].join(''); inspectTableSelect.value = selectedTable; } function splitPatterns(rawValue: string): string[] { return rawValue - .split(",") + .split(',') .map((value) => value.trim()) .filter((value) => value.length > 0); } @@ -2122,7 +2126,7 @@ function formatDuration(duration: WasmDuration): string { } function formatDiagnosticCode(diagnostic: WasmDiagnostic): string { - return `${diagnostic.code.prefix}${diagnostic.code.number.toString().padStart(3, "0")}`; + return `${diagnostic.code.prefix}${diagnostic.code.number.toString().padStart(3, '0')}`; } function totalDiffChanges(summary: DiffSummary): number { @@ -2147,46 +2151,50 @@ function totalDiffChanges(summary: DiffSummary): number { function exportFormatLabel(): string { switch (exportFormatSelect.value as ExportFormat) { - case "schema-json": - return "Schema JSON"; - case "graph-json": - return "Graph JSON"; - case "layout-json": - return "Layout JSON"; - case "mermaid": - return "Mermaid"; - case "d2": - return "D2"; - case "dot": - return "DOT"; + case 'schema-json': + return 'Schema JSON'; + case 'graph-json': + return 'Graph JSON'; + case 'layout-json': + return 'Layout JSON'; + case 'mermaid': + return 'Mermaid'; + case 'd2': + return 'D2'; + case 'dot': + return 'DOT'; + default: + return ''; } } function exportFilename(): string { switch (exportFormatSelect.value as ExportFormat) { - case "schema-json": - return "relune-schema.json"; - case "graph-json": - return "relune-graph.json"; - case "layout-json": - return "relune-layout.json"; - case "mermaid": - return "relune-diagram.mmd"; - case "d2": - return "relune-diagram.d2"; - case "dot": - return "relune-diagram.dot"; + case 'schema-json': + return 'relune-schema.json'; + case 'graph-json': + return 'relune-graph.json'; + case 'layout-json': + return 'relune-layout.json'; + case 'mermaid': + return 'relune-diagram.mmd'; + case 'd2': + return 'relune-diagram.d2'; + case 'dot': + return 'relune-diagram.dot'; + default: + return 'relune-export'; } } function exportMimeType(): string { switch (exportFormatSelect.value as ExportFormat) { - case "schema-json": - case "graph-json": - case "layout-json": - return "application/json;charset=utf-8"; + case 'schema-json': + case 'graph-json': + case 'layout-json': + return 'application/json;charset=utf-8'; default: - return "text/plain;charset=utf-8"; + return 'text/plain;charset=utf-8'; } } @@ -2197,14 +2205,14 @@ function setStatus(text: string): void { function showError(error: WasmErrorShape): void { errorBox.hidden = false; errorBox.innerHTML = ` - ${escapeHtml(error.code ?? "WORKBENCH_ERROR")} + ${escapeHtml(error.code ?? 'WORKBENCH_ERROR')}

    ${escapeHtml(error.message)}

    `; } function clearError(): void { errorBox.hidden = true; - errorBox.innerHTML = ""; + errorBox.innerHTML = ''; } function normalizeError(error: unknown): WasmErrorShape { @@ -2213,27 +2221,27 @@ function normalizeError(error: unknown): WasmErrorShape { } if ( error && - typeof error === "object" && - "message" in error && - typeof error.message === "string" + typeof error === 'object' && + 'message' in error && + typeof error.message === 'string' ) { return { message: error.message }; } - return { message: "Unknown playground error." }; + return { message: 'Unknown playground error.' }; } function isWasmErrorShape(value: unknown): value is WasmErrorShape { - if (!value || typeof value !== "object") { + if (!value || typeof value !== 'object') { return false; } - return "message" in value && typeof value.message === "string"; + return 'message' in value && typeof value.message === 'string'; } function downloadText(filename: string, content: string, mimeType: string): void { const blob = new Blob([content], { type: mimeType }); const blobUrl = URL.createObjectURL(blob); - const link = document.createElement("a"); + const link = document.createElement('a'); link.href = blobUrl; link.download = filename; link.click(); @@ -2265,23 +2273,23 @@ function readStoredState(): Partial { function readQueryState(): Partial { const params = new URLSearchParams(window.location.search); return sanitizeState({ - example: (params.get("example") as ExampleId | null) ?? undefined, - mode: (params.get("mode") as WorkbenchMode | null) ?? undefined, - theme: (params.get("theme") as Theme | null) ?? undefined, - layout: (params.get("layout") as LayoutAlgorithm | null) ?? undefined, - direction: (params.get("direction") as LayoutDirection | null) ?? undefined, - edgeStyle: (params.get("edges") as EdgeStyle | null) ?? undefined, - viewpoint: params.get("viewpoint") ?? undefined, - groupBy: (params.get("group") as GroupBy | null) ?? undefined, - focusTable: params.get("focus") ?? undefined, - depth: params.get("depth") ?? undefined, - includeTables: params.get("include") ?? undefined, - excludeTables: params.get("exclude") ?? undefined, - exportFormat: (params.get("export") as ExportFormat | null) ?? undefined, - inspectTable: params.get("table") ?? undefined, - lintRules: params.get("rules") ?? undefined, - compareView: (params.get("compare") as CompareView | null) ?? undefined, - compareReviewDialect: (params.get("reviewDialect") as ReviewDialect | null) ?? undefined, + example: (params.get('example') as ExampleId | null) ?? undefined, + mode: (params.get('mode') as WorkbenchMode | null) ?? undefined, + theme: (params.get('theme') as Theme | null) ?? undefined, + layout: (params.get('layout') as LayoutAlgorithm | null) ?? undefined, + direction: (params.get('direction') as LayoutDirection | null) ?? undefined, + edgeStyle: (params.get('edges') as EdgeStyle | null) ?? undefined, + viewpoint: params.get('viewpoint') ?? undefined, + groupBy: (params.get('group') as GroupBy | null) ?? undefined, + focusTable: params.get('focus') ?? undefined, + depth: params.get('depth') ?? undefined, + includeTables: params.get('include') ?? undefined, + excludeTables: params.get('exclude') ?? undefined, + exportFormat: (params.get('export') as ExportFormat | null) ?? undefined, + inspectTable: params.get('table') ?? undefined, + lintRules: params.get('rules') ?? undefined, + compareView: (params.get('compare') as CompareView | null) ?? undefined, + compareReviewDialect: (params.get('reviewDialect') as ReviewDialect | null) ?? undefined, }); } @@ -2306,31 +2314,31 @@ function sanitizeState(state: Partial): Partial if (isEdgeStyle(state.edgeStyle)) { sanitized.edgeStyle = state.edgeStyle; } - if (typeof state.viewpoint === "string") { + if (typeof state.viewpoint === 'string') { sanitized.viewpoint = state.viewpoint.trim(); } if (isGroupBy(state.groupBy)) { sanitized.groupBy = state.groupBy; } - if (typeof state.focusTable === "string") { + if (typeof state.focusTable === 'string') { sanitized.focusTable = state.focusTable; } - if (typeof state.depth === "string") { + if (typeof state.depth === 'string') { sanitized.depth = state.depth; } - if (typeof state.includeTables === "string") { + if (typeof state.includeTables === 'string') { sanitized.includeTables = state.includeTables; } - if (typeof state.excludeTables === "string") { + if (typeof state.excludeTables === 'string') { sanitized.excludeTables = state.excludeTables; } if (isExportFormat(state.exportFormat)) { sanitized.exportFormat = state.exportFormat; } - if (typeof state.inspectTable === "string") { + if (typeof state.inspectTable === 'string') { sanitized.inspectTable = state.inspectTable; } - if (typeof state.lintRules === "string") { + if (typeof state.lintRules === 'string') { sanitized.lintRules = state.lintRules; } if (isCompareView(state.compareView)) { @@ -2339,13 +2347,13 @@ function sanitizeState(state: Partial): Partial if (isReviewDialect(state.compareReviewDialect)) { sanitized.compareReviewDialect = state.compareReviewDialect; } - if (typeof state.sql === "string") { + if (typeof state.sql === 'string') { sanitized.sql = state.sql; } - if (typeof state.compareBeforeSql === "string") { + if (typeof state.compareBeforeSql === 'string') { sanitized.compareBeforeSql = state.compareBeforeSql; } - if (typeof state.compareAfterSql === "string") { + if (typeof state.compareAfterSql === 'string') { sanitized.compareAfterSql = state.compareAfterSql; } @@ -2385,51 +2393,51 @@ function collectState(): PersistedState { function syncQueryString(state: PersistedState): void { const params = new URLSearchParams(); - params.set("example", state.example); - params.set("mode", state.mode); - params.set("theme", state.theme); - params.set("layout", state.layout); - params.set("direction", state.direction); - params.set("edges", state.edgeStyle); - params.set("group", state.groupBy); + params.set('example', state.example); + params.set('mode', state.mode); + params.set('theme', state.theme); + params.set('layout', state.layout); + params.set('direction', state.direction); + params.set('edges', state.edgeStyle); + params.set('group', state.groupBy); if (state.viewpoint) { - params.set("viewpoint", state.viewpoint); + params.set('viewpoint', state.viewpoint); } if (state.focusTable) { - params.set("focus", state.focusTable); + params.set('focus', state.focusTable); } if (state.depth && state.depth !== DEFAULT_STATE.depth) { - params.set("depth", state.depth); + params.set('depth', state.depth); } if (state.includeTables) { - params.set("include", state.includeTables); + params.set('include', state.includeTables); } if (state.excludeTables) { - params.set("exclude", state.excludeTables); + params.set('exclude', state.excludeTables); } - if (state.mode === "export") { - params.set("export", state.exportFormat); + if (state.mode === 'export') { + params.set('export', state.exportFormat); } - if (state.mode === "inspect" && state.inspectTable) { - params.set("table", state.inspectTable); + if (state.mode === 'inspect' && state.inspectTable) { + params.set('table', state.inspectTable); } - if (state.mode === "lint" && state.lintRules) { - params.set("rules", state.lintRules); + if (state.mode === 'lint' && state.lintRules) { + params.set('rules', state.lintRules); } - if (state.mode === "compare") { - params.set("compare", state.compareView); + if (state.mode === 'compare') { + params.set('compare', state.compareView); if ( - state.compareView === "review" && + state.compareView === 'review' && state.compareReviewDialect !== DEFAULT_STATE.compareReviewDialect ) { - params.set("reviewDialect", state.compareReviewDialect); + params.set('reviewDialect', state.compareReviewDialect); } } const nextQuery = params.toString(); const nextUrl = nextQuery ? `?${nextQuery}` : window.location.pathname; - window.history.replaceState(null, "", nextUrl); + window.history.replaceState(null, '', nextUrl); } function setActionButtonsDisabled(disabled: boolean): void { @@ -2441,74 +2449,74 @@ function setActionButtonsDisabled(disabled: boolean): void { function isExampleId(value: unknown): value is ExampleId { return ( - value === "simple-blog" || - value === "ecommerce" || - value === "multi-schema" || + value === 'simple-blog' || + value === 'ecommerce' || + value === 'multi-schema' || value === CUSTOM_EXAMPLE_ID ); } function isWorkbenchMode(value: unknown): value is WorkbenchMode { return ( - value === "render" || - value === "inspect" || - value === "export" || - value === "lint" || - value === "compare" + value === 'render' || + value === 'inspect' || + value === 'export' || + value === 'lint' || + value === 'compare' ); } function isTheme(value: unknown): value is Theme { - return value === "light" || value === "dark"; + return value === 'light' || value === 'dark'; } function isLayoutAlgorithm(value: unknown): value is LayoutAlgorithm { - return value === "hierarchical" || value === "force-directed"; + return value === 'hierarchical' || value === 'force-directed'; } function isLayoutDirection(value: unknown): value is LayoutDirection { return ( - value === "top-to-bottom" || - value === "left-to-right" || - value === "right-to-left" || - value === "bottom-to-top" + value === 'top-to-bottom' || + value === 'left-to-right' || + value === 'right-to-left' || + value === 'bottom-to-top' ); } function isEdgeStyle(value: unknown): value is EdgeStyle { - return value === "curved" || value === "orthogonal" || value === "straight"; + return value === 'curved' || value === 'orthogonal' || value === 'straight'; } function isGroupBy(value: unknown): value is GroupBy { - return value === "none" || value === "schema" || value === "prefix"; + return value === 'none' || value === 'schema' || value === 'prefix'; } function isExportFormat(value: unknown): value is ExportFormat { return ( - value === "schema-json" || - value === "graph-json" || - value === "layout-json" || - value === "mermaid" || - value === "d2" || - value === "dot" + value === 'schema-json' || + value === 'graph-json' || + value === 'layout-json' || + value === 'mermaid' || + value === 'd2' || + value === 'dot' ); } function isCompareView(value: unknown): value is CompareView { return ( - value === "visual" || - value === "text" || - value === "markdown" || - value === "json" || - value === "review" + value === 'visual' || + value === 'text' || + value === 'markdown' || + value === 'json' || + value === 'review' ); } function isReviewDialect(value: unknown): value is ReviewDialect { - return value === "auto" || value === "postgres" || value === "mysql" || value === "sqlite"; + return value === 'auto' || value === 'postgres' || value === 'mysql' || value === 'sqlite'; } -function toBuiltinExampleId(value: ExampleId): Exclude { +function toBuiltinExampleId(value: ExampleId): Exclude { return value === CUSTOM_EXAMPLE_ID ? DEFAULT_EXAMPLE_ID : value; } @@ -2516,6 +2524,7 @@ function applyTheme(theme: Theme): void { document.documentElement.dataset.theme = theme; } +// eslint-disable-next-line typescript/no-unnecessary-type-parameters function getElement(id: string): T { const element = document.getElementById(id); if (!element) { @@ -2526,9 +2535,9 @@ function getElement(id: string): T { function escapeHtml(value: string): string { return value - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - .replaceAll("'", "'"); + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); } diff --git a/playground/src/styles.css b/playground/src/styles.css index f8d1851..42cd88b 100644 --- a/playground/src/styles.css +++ b/playground/src/styles.css @@ -48,9 +48,9 @@ --iframe-bg: #fffdf8; /* Typography */ - --font-display: "DM Sans", system-ui, sans-serif; - --font-body: "DM Sans", system-ui, sans-serif; - --font-mono: "JetBrains Mono", "SF Mono", "Consolas", monospace; + --font-display: 'DM Sans', system-ui, sans-serif; + --font-body: 'DM Sans', system-ui, sans-serif; + --font-mono: 'JetBrains Mono', 'SF Mono', 'Consolas', monospace; /* Geometry */ --radius-sm: 6px; @@ -60,7 +60,7 @@ --topbar-h: 72px; } -:root[data-theme="dark"] { +:root[data-theme='dark'] { color-scheme: dark; --bg: #0f0c0a; @@ -355,7 +355,7 @@ a { /* Wider invisible hit area */ .sidebar-handle::before { - content: ""; + content: ''; position: absolute; inset: 0 -6px; } @@ -478,7 +478,7 @@ a { } .ctrl-section__title::before { - content: "›"; + content: '›'; display: inline-block; margin-right: 6px; font-size: 14px; @@ -929,7 +929,7 @@ details[open] > .ctrl-section__title::before { /* Chevron indicator */ .diagnostics__label::before { - content: "›"; + content: '›'; display: inline-block; margin-right: 4px; font-size: 18px; @@ -1068,7 +1068,7 @@ details[open] > .ctrl-section__title::before { } .viewport::before { - content: ""; + content: ''; position: absolute; inset: 0; border-radius: var(--radius-md); @@ -1274,7 +1274,7 @@ details[open] > .ctrl-section__title::before { } .compare-summary__label::before { - content: "›"; + content: '›'; display: inline-block; margin-right: 4px; font-size: 18px;