diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9fa432e7d3..4ba57dde95 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -34,6 +34,8 @@ jobs: corepack enable; corepack install; pnpm install; + pnpm run check-fmt:clj + pnpm run check-fmt:js pnpm run lint:clj - name: Lint Frontend @@ -42,6 +44,9 @@ jobs: corepack enable; corepack install; pnpm install; + pnpm run check-fmt:js + pnpm run check-fmt:clj + pnpm run check-fmt:scss pnpm run lint:clj pnpm run lint:js pnpm run lint:scss @@ -52,7 +57,8 @@ jobs: corepack enable; corepack install; pnpm install; - pnpm run lint:clj + pnpm run check-fmt + pnpm run lint - name: Lint Exporter working-directory: ./exporter @@ -60,7 +66,8 @@ jobs: corepack enable; corepack install; pnpm install; - pnpm run lint:clj + pnpm run check-fmt + pnpm run lint - name: Lint Library working-directory: ./library @@ -68,7 +75,8 @@ jobs: corepack enable; corepack install; pnpm install; - pnpm run lint:clj + pnpm run check-fmt + pnpm run lint test-common: name: "Common Tests" @@ -79,12 +87,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Run tests on JVM - working-directory: ./common - run: | - clojure -M:dev:test - - - name: Run tests on NODE + - name: Run tests working-directory: ./common run: | ./scripts/test diff --git a/.gitignore b/.gitignore index 9958d90cb8..d0a13534b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,4 @@ .pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions -.pnpm-store *-init.clj *.css.json *.jar @@ -20,8 +13,6 @@ .nyc_output .rebel_readline_history .repl -.shadow-cljs -.pnpm-store/ /*.jpg /*.md /*.png @@ -36,6 +27,7 @@ /playground/ /backend/*.md !/backend/AGENTS.md +/backend/.shadow-cljs /backend/*.sql /backend/*.txt /backend/assets/ @@ -48,13 +40,13 @@ /backend/experiments /backend/scripts/_env.local /bundle* -/cd.md /clj-profiler/ /common/coverage /common/target -/deploy +/common/.shadow-cljs /docker/images/bundle* /exporter/target +/exporter/.shadow-cljs /frontend/.storybook/preview-body.html /frontend/.storybook/preview-head.html /frontend/playwright-report/ @@ -68,9 +60,9 @@ /frontend/storybook-static/ /frontend/target/ /frontend/test-results/ +/frontend/.shadow-cljs /other/ -/scripts/ -/telemetry/ +/nexus/ /tmp/ /vendor/**/target /vendor/svgclean/bundle*.js @@ -79,13 +71,11 @@ /library/*.zip /external /penpot-nitrate - -clj-profiler/ -node_modules /test-results/ /playwright-report/ /blob-report/ /playwright/.cache/ /render-wasm/target/ +/**/node_modules /**/.yarn/* /.pnpm-store diff --git a/AGENTS.md b/AGENTS.md index d126301300..9505d47698 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ -# Penpot – Copilot Instructions +# Penpot – Instructions ## Architecture Overview @@ -18,7 +18,13 @@ The monorepo is managed with `pnpm` workspaces. The `manage.sh` orchestrates cross-component builds. `run-ci.sh` defines the CI pipeline. ---- +## Search Standards + +When searching code, always use `ripgrep` (rg) instead of grep if +available, as it respects `.gitignore` by default. + +If using grep, try to exclude node_modules and .shadow-cljs directories + ## Build, Test & Lint Commands @@ -28,27 +34,26 @@ Run `./scripts/setup` for setup all dependencies. ```bash -# Dev -pnpm run watch:app # Full dev build (WASM + CLJS + assets) - -# Production Build +# Build (Producution) ./scripts/build # Tests -pnpm run test # Build ClojureScript tests + run node target/tests/test.js -pnpm run watch:test # Watch + auto-rerun on change -pnpm run test:e2e # Playwright e2e tests -pnpm run test:e2e --grep "pattern" # Single e2e test by pattern +pnpm run test # Build ClojureScript tests + run node target/tests/test.js # Lint -pnpm run lint:js # format and linter check for JS -pnpm run lint:clj # format and linter check for CLJ -pnpm run lint:scss # prettier check for SCSS +pnpm run lint:js # Linter for JS/TS +pnpm run lint:clj # Linter for CLJ/CLJS/CLJC +pnpm run lint:scss # Linter for SCSS -# Code formatting -pnpm run fmt:clj # Format CLJ -pnpm run fmt:js # prettier for JS -pnpm run fmt:scss # prettier for SCSS +# Check Code Formart +pnpm run check-fmt:clj # Format CLJ/CLJS/CLJC +pnpm run check-fmt:js # Format JS/TS +pnpm run check-fmt:scss # Format SCSS + +# Code Format (Automatic Formating) +pnpm run fmt:clj # Format CLJ/CLJS/CLJC +pnpm run fmt:js # Format JS/TS +pnpm run fmt:scss # Format SCSS ``` To run a focused ClojureScript unit test: edit @@ -58,28 +63,63 @@ run build:test && node target/tests/test.js`. ### Backend (`cd backend`) -```bash -# Tests (Kaocha) -clojure -M:dev:test # Full suite -clojure -M:dev:test --focus backend-tests.my-ns-test # Single namespace +Run `pnpm install` for install all dependencies. -# Lint / Format -pnpm run lint:clj -pnpm run fmt:clj +```bash +# Run full test suite +pnpm run test + +# Run single namespace +pnpm run test --focus backend-tests.rpc-doc-test + +# Check Code Format +pnpm run check-fmt + +# Code Format (Automatic Formatting) +pnpm run fmt + +# Code Linter +pnpm run lint ``` -Test config is in `backend/tests.edn`; test namespaces match `.*-test$` under `test/`. +Test config is in `backend/tests.edn`; test namespaces match +`.*-test$` under `test/` directory. You should not touch this file, +just use it for reference. ### Common (`cd common`) +This contains code that should compile and run under different runtimes: JVM & JS so the commands are +separarated for each runtime. + ```bash -pnpm run test # Build + run node target/tests/test.js -pnpm run watch:test # Watch mode -pnpm run lint:clj -pnpm run fmt:clj +clojure -M:dev:test # Run full test suite under JVM +clojure -M:dev:test --focus backend-tests.my-ns-test # Run single namespace under JVM + +# Run full test suite under JS or JVM runtimes +pnpm run test:js +pnpm run test:jvm + +# Run single namespace (only on JVM) +pnpm run test:jvm --focus common-tests.my-ns-test + +# Lint +pnpm run lint:clj # Lint CLJ/CLJS/CLJC code + +# Check Format +pnpm run check-fmt:clj # Check CLJ/CLJS/CLJS code +pnpm run check-fmt:js # Check JS/TS code + +# Code Format (Automatic Formatting) +pnpm run fmt:clj # Check CLJ/CLJS/CLJS code +pnpm run fmt:js # Check JS/TS code ``` +To run a focused ClojureScript unit test: edit +`test/common_tests/runner.cljs` to narrow the test suite, then `pnpm +run build:test && node target/tests/test.js`. + + ### Render-WASM (`cd render-wasm`) ```bash @@ -93,6 +133,10 @@ cargo fmt --check ### Namespace Structure +The backend, frontend and exporter are developed using clojure and +clojurescript and code is organized in namespaces. This is a general +overview of the available namespaces. + **Backend:** - `app.rpc.commands.*` – RPC command implementations (`auth`, `files`, `teams`, etc.) - `app.http.*` – HTTP routes and middleware @@ -109,14 +153,26 @@ cargo fmt --check - `app.util.*` – Utilities (DOM, HTTP, i18n, keyboard shortcuts) **Common:** -- `app.common.types.*` – Shared data types for shapes, files, pages -- `app.common.schema` – Malli validation schemas -- `app.common.geom.*` – Geometry utilities +- `app.common.types.*` – Shared data types for shapes, files, pages using Malli schemas +- `app.common.schema` – Malli abstraction layer, exposes the most used functions from malli +- `app.common.geom.*` – Geometry and shape transformation helpers +- `app.common.data` – Generic helpers used around all application +- `app.common.math` – Generic math helpers used around all aplication +- `app.common.json` – Generic JSON encoding/decoding helpers - `app.common.data.macros` – Performance macros used everywhere + ### Backend RPC Commands -All API calls go through a single RPC endpoint: `POST /api/rpc/command/`. +The PRC methods are implement in a some kind of multimethod structure using +`app.util.serivices` namespace. All RPC methods are collected under `app.rpc` +namespace and exposed under `/api/rpc/command/`. The RPC method +accepts POST and GET requests indistinctly and uses `Accept` header for +negotiate the response encoding (which can be transit, the defaut or plain +json). It also accepts transit (defaut) or json as input, which should be +indicated using `Content-Type` header. + +This is an example: ```clojure (sv/defmethod ::my-command @@ -129,12 +185,18 @@ All API calls go through a single RPC endpoint: `POST /api/rpc/command/ my-component* -props]`, where props should be a map literal or symbol pointing to -javascript props objects. The javascript props object can be created -manually `#js {:data-foo "bar"}` or using `mf/spread-object` helper -macro. +Example for `mf/with-memo` macro: ---- +```clj +;; Using functions +(mf/use-effect + (mf/deps team-id) + (fn [] + (st/emit! (dd/initialize team-id)) + (fn [] + (st/emit! (dd/finalize team-id))))) -## Commit Guidelines +;; The same effect but using mf/with-effect +(mf/with-effect [team-id] + (st/emit! (dd/initialize team-id)) + (fn [] + (st/emit! (dd/finalize team-id)))) +``` + +Example for `mf/with-memo` macro: + +``` +;; Using functions +(mf/use-memo + (mf/deps projects team-id) + (fn [] + (->> (vals projects) + (filterv #(= team-id (:team-id %)))))) + +;; Using the macro +(mf/with-memo [projects team-id] + (->> (vals projects) + (filterv #(= team-id (:team-id %))))) +``` + +Prefer using the macros for it syntax simplicity. + + +4. Component Usage (Hiccup Syntax) + +When invoking a component within Hiccup, always use the [:> component* props] +pattern. + +Requirements for props: + +- Must be a map literal or a symbol pointing to a JavaScript props object. +- To create a JS props object, use the `#js` literal or the `mf/spread-object` helper macro. + +Examples: + +```clj +;; Using object literal (no need of #js because macro already interprets it) +[:> my-component* {:data-foo "bar"}] + +;; Using object literal (no need of #js because macro already interprets it) +(let [props #js {:data-foo "bar" + :className "myclass"}] + [:> my-component* props]) + +;; Using the spread helper +(let [props (mf/spread-object base-props {:extra "data"})] + [:> my-component* props]) +``` + +4. Checklist + +- [ ] Does the component name end with *? + + +## Commit Format Guidelines Format: ` ` @@ -263,3 +454,46 @@ applicable. | ⬇️ | `:arrow_down:` | Dependency downgrade | | 🔥 | `:fire:` | Remove files or code | | 🌐 | `:globe_with_meridians:` | Translations | + + +## SCSS Rules & Migration + +### General rules + +- Prefer CSS custom properties ( `margin: var(--sp-xs);`) instead of scss + variables and get the already defined properties from `_sizes.scss`. The SCSS + variables are allowed and still used, just prefer properties if they are + already defined. +- If a value isn't in the DS, use the `px2rem(n)` mixin: `@use "ds/_utils.scss" + as *; padding: px2rem(23);`. +- Do **not** create new SCSS variables for one-off values. +- Use physical directions with logical ones to support RTL/LTR naturally. + - ❌ `margin-left`, `padding-right`, `left`, `right`. + - ✅ `margin-inline-start`, `padding-inline-end`, `inset-inline-start`. +- Always use the `use-typography` mixin from `ds/typography.scss`. + - ✅ `@include t.use-typography("title-small");` +- Use `$br-*` for radius and `$b-*` for thickness from `ds/_borders.scss`. +- Use only tokens from `ds/colors.scss`. Do **NOT** use `design-tokens.scss` or + legacy color variables. +- Use mixins only those defined in`ds/mixins.scss`. Avoid legacy mixins like + `@include flexCenter;`. Write standard CSS (flex/grid) instead. + +### Syntax & Structure + +- Use the `@use` instead of `@import`. If you go to refactor existing SCSS file, + try to replace all `@import` with `@use`. Example: `@use "ds/_sizes.scss" as + *;` (Use `as *` to expose variables directly). +- Avoid deep selector nesting or high-specificity (IDs). Flatten selectors: + - ❌ `.card { .title { ... } }` + - ✅ `.card-title { ... }` +- Leverage component-level CSS variables for state changes (hover/focus) instead + of rewriting properties. + +### Checklist + +- [ ] No references to `common/refactor/` +- [ ] All `@import` converted to `@use` (only if refactoring) +- [ ] Physical properties (left/right) using logical properties (inline-start/end). +- [ ] Typography implemented via `use-typography()` mixin. +- [ ] Hardcoded pixel values wrapped in `px2rem()`. +- [ ] Selectors are flat (no deep nesting). diff --git a/backend/package.json b/backend/package.json index 63bf06eddf..8ad7cd3c1d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,7 +19,9 @@ "ws": "^8.17.0" }, "scripts": { - "lint:clj": "cljfmt check --parallel=false src/ test/ && clj-kondo --parallel --lint src/", - "fmt:clj": "cljfmt fix --parallel=true src/ test/" + "lint": "clj-kondo --parallel --lint ../common/src src/", + "check-fmt": "cljfmt check --parallel=true src/ test/", + "fmt": "cljfmt fix --parallel=true src/ test/", + "test": "clojure -M:dev:test" } } diff --git a/common/package.json b/common/package.json index 09de4e95aa..ac874c2b45 100644 --- a/common/package.json +++ b/common/package.json @@ -13,6 +13,7 @@ "devDependencies": { "concurrently": "^9.1.2", "nodemon": "^3.1.10", + "prettier": "3.5.3", "source-map-support": "^0.5.21", "ws": "^8.18.2" }, @@ -20,11 +21,15 @@ "date-fns": "^4.1.0" }, "scripts": { - "lint:clj": "cljfmt check --parallel=false src/ test/ && clj-kondo --parallel=true --lint src/", + "lint:clj": "clj-kondo --parallel=true --lint src/", + "check-fmt:clj": "cljfmt check --parallel=true src/ test/", + "check-fmt:js": "prettier -c src/**/*.js", "fmt:clj": "cljfmt fix --parallel=true src/ test/", + "fmt:js": "prettier -c src/**/*.js -w", "lint": "pnpm run lint:clj", "watch:test": "concurrently \"clojure -M:dev:shadow-cljs watch test\" \"nodemon -C -d 2 -w target/tests/ --exec 'node target/tests/test.js'\"", "build:test": "clojure -M:dev:shadow-cljs compile test", - "test": "pnpm run build:test && node target/tests/test.js" + "test:js": "pnpm run build:test && node target/tests/test.js", + "test:jvm": "clojure -M:dev:test" } } diff --git a/common/pnpm-lock.yaml b/common/pnpm-lock.yaml index 8536654155..7f63f16b3c 100644 --- a/common/pnpm-lock.yaml +++ b/common/pnpm-lock.yaml @@ -18,6 +18,9 @@ importers: nodemon: specifier: ^3.1.10 version: 3.1.11 + prettier: + specifier: 3.5.3 + version: 3.5.3 source-map-support: specifier: ^0.5.21 version: 0.5.21 @@ -169,6 +172,11 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + engines: {node: '>=14'} + hasBin: true + pstree.remy@1.1.8: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} @@ -405,6 +413,8 @@ snapshots: picomatch@2.3.1: {} + prettier@3.5.3: {} + pstree.remy@1.1.8: {} readdirp@3.6.0: diff --git a/common/scripts/test b/common/scripts/test index 6402c5afd1..b064f2b8d2 100755 --- a/common/scripts/test +++ b/common/scripts/test @@ -4,4 +4,5 @@ set -ex corepack enable; corepack install; pnpm install; -pnpm run test; +pnpm run test:js; +pnpm run test:jvm; diff --git a/common/src/app/common/encoding_impl.js b/common/src/app/common/encoding_impl.js index 9af7d0fd57..a08f51170c 100644 --- a/common/src/app/common/encoding_impl.js +++ b/common/src/app/common/encoding_impl.js @@ -10,7 +10,7 @@ goog.require("cljs.core"); goog.provide("app.common.encoding_impl"); -goog.scope(function() { +goog.scope(function () { const core = cljs.core; const global = goog.global; const self = app.common.encoding_impl; @@ -28,8 +28,10 @@ goog.scope(function() { // Accept UUID hex format input = input.replace(/-/g, ""); - if ((input.length % 2) !== 0) { - throw new RangeError("Expected string to be an even number of characters") + if (input.length % 2 !== 0) { + throw new RangeError( + "Expected string to be an even number of characters", + ); } const view = new Uint8Array(input.length / 2); @@ -44,7 +46,11 @@ goog.scope(function() { function bufferToHex(source, isUuid) { if (source instanceof Uint8Array) { } else if (ArrayBuffer.isView(source)) { - source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength); + source = new Uint8Array( + source.buffer, + source.byteOffset, + source.byteLength, + ); } else if (Array.isArray(source)) { source = Uint8Array.from(source); } @@ -56,22 +62,28 @@ goog.scope(function() { const spacer = isUuid ? "-" : ""; let i = 0; - return (hexMap[source[i++]] + - hexMap[source[i++]] + - hexMap[source[i++]] + - hexMap[source[i++]] + spacer + - hexMap[source[i++]] + - hexMap[source[i++]] + spacer + - hexMap[source[i++]] + - hexMap[source[i++]] + spacer + - hexMap[source[i++]] + - hexMap[source[i++]] + spacer + - hexMap[source[i++]] + - hexMap[source[i++]] + - hexMap[source[i++]] + - hexMap[source[i++]] + - hexMap[source[i++]] + - hexMap[source[i++]]); + return ( + hexMap[source[i++]] + + hexMap[source[i++]] + + hexMap[source[i++]] + + hexMap[source[i++]] + + spacer + + hexMap[source[i++]] + + hexMap[source[i++]] + + spacer + + hexMap[source[i++]] + + hexMap[source[i++]] + + spacer + + hexMap[source[i++]] + + hexMap[source[i++]] + + spacer + + hexMap[source[i++]] + + hexMap[source[i++]] + + hexMap[source[i++]] + + hexMap[source[i++]] + + hexMap[source[i++]] + + hexMap[source[i++]] + ); } self.hexToBuffer = hexToBuffer; @@ -87,8 +99,10 @@ goog.scope(function() { // for base16 (hex), base32, or base64 encoding in a standards // compliant manner. - function getBaseCodec (ALPHABET) { - if (ALPHABET.length >= 255) { throw new TypeError("Alphabet too long"); } + function getBaseCodec(ALPHABET) { + if (ALPHABET.length >= 255) { + throw new TypeError("Alphabet too long"); + } let BASE_MAP = new Uint8Array(256); for (let j = 0; j < BASE_MAP.length; j++) { BASE_MAP[j] = 255; @@ -96,22 +110,32 @@ goog.scope(function() { for (let i = 0; i < ALPHABET.length; i++) { let x = ALPHABET.charAt(i); let xc = x.charCodeAt(0); - if (BASE_MAP[xc] !== 255) { throw new TypeError(x + " is ambiguous"); } + if (BASE_MAP[xc] !== 255) { + throw new TypeError(x + " is ambiguous"); + } BASE_MAP[xc] = i; } let BASE = ALPHABET.length; let LEADER = ALPHABET.charAt(0); let FACTOR = Math.log(BASE) / Math.log(256); // log(BASE) / log(256), rounded up let iFACTOR = Math.log(256) / Math.log(BASE); // log(256) / log(BASE), rounded up - function encode (source) { + function encode(source) { if (source instanceof Uint8Array) { } else if (ArrayBuffer.isView(source)) { - source = new Uint8Array(source.buffer, source.byteOffset, source.byteLength); + source = new Uint8Array( + source.buffer, + source.byteOffset, + source.byteLength, + ); } else if (Array.isArray(source)) { source = Uint8Array.from(source); } - if (!(source instanceof Uint8Array)) { throw new TypeError("Expected Uint8Array"); } - if (source.length === 0) { return ""; } + if (!(source instanceof Uint8Array)) { + throw new TypeError("Expected Uint8Array"); + } + if (source.length === 0) { + return ""; + } // Skip & count leading zeroes. let zeroes = 0; let length = 0; @@ -129,12 +153,18 @@ goog.scope(function() { let carry = source[pbegin]; // Apply "b58 = b58 * 256 + ch". let i = 0; - for (let it1 = size - 1; (carry !== 0 || i < length) && (it1 !== -1); it1--, i++) { + for ( + let it1 = size - 1; + (carry !== 0 || i < length) && it1 !== -1; + it1--, i++ + ) { carry += (256 * b58[it1]) >>> 0; - b58[it1] = (carry % BASE) >>> 0; + b58[it1] = carry % BASE >>> 0; carry = (carry / BASE) >>> 0; } - if (carry !== 0) { throw new Error("Non-zero carry"); } + if (carry !== 0) { + throw new Error("Non-zero carry"); + } length = i; pbegin++; } @@ -145,13 +175,19 @@ goog.scope(function() { } // Translate the result into a string. let str = LEADER.repeat(zeroes); - for (; it2 < size; ++it2) { str += ALPHABET.charAt(b58[it2]); } + for (; it2 < size; ++it2) { + str += ALPHABET.charAt(b58[it2]); + } return str; } - function decodeUnsafe (source) { - if (typeof source !== "string") { throw new TypeError("Expected String"); } - if (source.length === 0) { return new Uint8Array(); } + function decodeUnsafe(source) { + if (typeof source !== "string") { + throw new TypeError("Expected String"); + } + if (source.length === 0) { + return new Uint8Array(); + } let psz = 0; // Skip and count leading '1's. let zeroes = 0; @@ -161,21 +197,29 @@ goog.scope(function() { psz++; } // Allocate enough space in big-endian base256 representation. - let size = (((source.length - psz) * FACTOR) + 1) >>> 0; // log(58) / log(256), rounded up. + let size = ((source.length - psz) * FACTOR + 1) >>> 0; // log(58) / log(256), rounded up. let b256 = new Uint8Array(size); // Process the characters. while (source[psz]) { // Decode character let carry = BASE_MAP[source.charCodeAt(psz)]; // Invalid character - if (carry === 255) { return; } + if (carry === 255) { + return; + } let i = 0; - for (let it3 = size - 1; (carry !== 0 || i < length) && (it3 !== -1); it3--, i++) { + for ( + let it3 = size - 1; + (carry !== 0 || i < length) && it3 !== -1; + it3--, i++ + ) { carry += (BASE * b256[it3]) >>> 0; - b256[it3] = (carry % 256) >>> 0; + b256[it3] = carry % 256 >>> 0; carry = (carry / 256) >>> 0; } - if (carry !== 0) { throw new Error("Non-zero carry"); } + if (carry !== 0) { + throw new Error("Non-zero carry"); + } length = i; psz++; } @@ -192,20 +236,22 @@ goog.scope(function() { return vch; } - function decode (string) { + function decode(string) { let buffer = decodeUnsafe(string); - if (buffer) { return buffer; } + if (buffer) { + return buffer; + } throw new Error("Non-base" + BASE + " character"); } return { encode: encode, decodeUnsafe: decodeUnsafe, - decode: decode + decode: decode, }; } // MORE bases here: https://github.com/cryptocoinjs/base-x/tree/master - const BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const BASE62 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; self.bufferToBase62 = getBaseCodec(BASE62).encode; - }); diff --git a/common/src/app/common/svg/path/arc_to_bezier.js b/common/src/app/common/svg/path/arc_to_bezier.js index 39dc8d447f..b4220a7d95 100644 --- a/common/src/app/common/svg/path/arc_to_bezier.js +++ b/common/src/app/common/svg/path/arc_to_bezier.js @@ -14,7 +14,7 @@ goog.provide("app.common.svg.path.arc_to_bezier"); // https://raw.githubusercontent.com/fontello/svgpath/master/lib/a2c.js -goog.scope(function() { +goog.scope(function () { const self = app.common.svg.path.arc_to_bezier; var TAU = Math.PI * 2; @@ -27,20 +27,23 @@ goog.scope(function() { // we can use simplified math (without length normalization) // function unit_vector_angle(ux, uy, vx, vy) { - var sign = (ux * vy - uy * vx < 0) ? -1 : 1; - var dot = ux * vx + uy * vy; + var sign = ux * vy - uy * vx < 0 ? -1 : 1; + var dot = ux * vx + uy * vy; // Add this to work with arbitrary vectors: // dot /= Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy); // rounding errors, e.g. -1.0000000000000002 can screw up this - if (dot > 1.0) { dot = 1.0; } - if (dot < -1.0) { dot = -1.0; } + if (dot > 1.0) { + dot = 1.0; + } + if (dot < -1.0) { + dot = -1.0; + } return sign * Math.acos(dot); } - // Convert from endpoint to center parameterization, // see http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes // @@ -53,11 +56,11 @@ goog.scope(function() { // points. After that, rotate it to line up ellipse axes with coordinate // axes. // - var x1p = cos_phi*(x1-x2)/2 + sin_phi*(y1-y2)/2; - var y1p = -sin_phi*(x1-x2)/2 + cos_phi*(y1-y2)/2; + var x1p = (cos_phi * (x1 - x2)) / 2 + (sin_phi * (y1 - y2)) / 2; + var y1p = (-sin_phi * (x1 - x2)) / 2 + (cos_phi * (y1 - y2)) / 2; - var rx_sq = rx * rx; - var ry_sq = ry * ry; + var rx_sq = rx * rx; + var ry_sq = ry * ry; var x1p_sq = x1p * x1p; var y1p_sq = y1p * y1p; @@ -66,33 +69,33 @@ goog.scope(function() { // Compute coordinates of the centre of this ellipse (cx', cy') // in the new coordinate system. // - var radicant = (rx_sq * ry_sq) - (rx_sq * y1p_sq) - (ry_sq * x1p_sq); + var radicant = rx_sq * ry_sq - rx_sq * y1p_sq - ry_sq * x1p_sq; if (radicant < 0) { // due to rounding errors it might be e.g. -1.3877787807814457e-17 radicant = 0; } - radicant /= (rx_sq * y1p_sq) + (ry_sq * x1p_sq); + radicant /= rx_sq * y1p_sq + ry_sq * x1p_sq; radicant = Math.sqrt(radicant) * (fa === fs ? -1 : 1); - var cxp = radicant * rx/ry * y1p; - var cyp = radicant * -ry/rx * x1p; + var cxp = ((radicant * rx) / ry) * y1p; + var cyp = ((radicant * -ry) / rx) * x1p; // Step 3. // // Transform back to get centre coordinates (cx, cy) in the original // coordinate system. // - var cx = cos_phi*cxp - sin_phi*cyp + (x1+x2)/2; - var cy = sin_phi*cxp + cos_phi*cyp + (y1+y2)/2; + var cx = cos_phi * cxp - sin_phi * cyp + (x1 + x2) / 2; + var cy = sin_phi * cxp + cos_phi * cyp + (y1 + y2) / 2; // Step 4. // // Compute angles (theta1, delta_theta). // - var v1x = (x1p - cxp) / rx; - var v1y = (y1p - cyp) / ry; + var v1x = (x1p - cxp) / rx; + var v1y = (y1p - cyp) / ry; var v2x = (-x1p - cxp) / rx; var v2y = (-y1p - cyp) / ry; @@ -106,7 +109,7 @@ goog.scope(function() { delta_theta += TAU; } - return [ cx, cy, theta1, delta_theta ]; + return [cx, cy, theta1, delta_theta]; } // @@ -114,24 +117,33 @@ goog.scope(function() { // see http://math.stackexchange.com/questions/873224 // function approximate_unit_arc(theta1, delta_theta) { - var alpha = 4/3 * Math.tan(delta_theta/4); + var alpha = (4 / 3) * Math.tan(delta_theta / 4); var x1 = Math.cos(theta1); var y1 = Math.sin(theta1); var x2 = Math.cos(theta1 + delta_theta); var y2 = Math.sin(theta1 + delta_theta); - return [ x1, y1, x1 - y1*alpha, y1 + x1*alpha, x2 + y2*alpha, y2 - x2*alpha, x2, y2 ]; + return [ + x1, + y1, + x1 - y1 * alpha, + y1 + x1 * alpha, + x2 + y2 * alpha, + y2 - x2 * alpha, + x2, + y2, + ]; } function calculate_beziers(x1, y1, x2, y2, fa, fs, rx, ry, phi) { - var sin_phi = Math.sin(phi * TAU / 360); - var cos_phi = Math.cos(phi * TAU / 360); + var sin_phi = Math.sin((phi * TAU) / 360); + var cos_phi = Math.cos((phi * TAU) / 360); // Make sure radii are valid // - var x1p = cos_phi*(x1-x2)/2 + sin_phi*(y1-y2)/2; - var y1p = -sin_phi*(x1-x2)/2 + cos_phi*(y1-y2)/2; + var x1p = (cos_phi * (x1 - x2)) / 2 + (sin_phi * (y1 - y2)) / 2; + var y1p = (-sin_phi * (x1 - x2)) / 2 + (cos_phi * (y1 - y2)) / 2; // console.log("L", x1p, y1p) @@ -145,7 +157,6 @@ goog.scope(function() { return []; } - // Compensate out-of-range radii // rx = Math.abs(rx); @@ -157,25 +168,20 @@ goog.scope(function() { ry *= Math.sqrt(lambda); } - // Get center parameters (cx, cy, theta1, delta_theta) // var cc = get_arc_center(x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi); - var result = []; var theta1 = cc[2]; var delta_theta = cc[3]; - - // Split an arc to multiple segments, so each segment // will be less than τ/4 (= 90°) // var segments = Math.max(Math.ceil(Math.abs(delta_theta) / (TAU / 4)), 1); delta_theta /= segments; - for (var i = 0; i < segments; i++) { var item = approximate_unit_arc(theta1, delta_theta); result.push(item); @@ -195,8 +201,8 @@ goog.scope(function() { y *= ry; // rotate - var xp = cos_phi*x - sin_phi*y; - var yp = sin_phi*x + cos_phi*y; + var xp = cos_phi * x - sin_phi * y; + var yp = sin_phi * x + cos_phi * y; // translate curve[i + 0] = xp + cc[0]; diff --git a/common/src/app/common/svg/path/parser.js b/common/src/app/common/svg/path/parser.js index 5bbcddd3a0..f427874528 100644 --- a/common/src/app/common/svg/path/parser.js +++ b/common/src/app/common/svg/path/parser.js @@ -31,74 +31,81 @@ class Segment { toPersistentMap() { const fromArray = (data) => { return cljs.PersistentArrayMap.fromArray(data); - } + }; let command, params; - switch(this.command) { - case "M": - command = MOVE_TO; - params = fromArray([K_X, this.params[0], K_Y, this.params[1]]); - break; + switch (this.command) { + case "M": + command = MOVE_TO; + params = fromArray([K_X, this.params[0], K_Y, this.params[1]]); + break; - case "Z": - command = CLOSE_PATH; - params = cljs.PersistentArrayMap.EMPTY; - break; + case "Z": + command = CLOSE_PATH; + params = cljs.PersistentArrayMap.EMPTY; + break; - case "L": - command = LINE_TO; - params = fromArray([K_X, this.params[0], K_Y, this.params[1]]); - break; + case "L": + command = LINE_TO; + params = fromArray([K_X, this.params[0], K_Y, this.params[1]]); + break; - case "C": - command = CURVE_TO; - params = fromArray([K_C1X, this.params[0], - K_C1Y, this.params[1], - K_C2X, this.params[2], - K_C2Y, this.params[3], - K_X, this.params[4], - K_Y, this.params[5]]); - break; - default: - command = null - params = null; + case "C": + command = CURVE_TO; + params = fromArray([ + K_C1X, + this.params[0], + K_C1Y, + this.params[1], + K_C2X, + this.params[2], + K_C2Y, + this.params[3], + K_X, + this.params[4], + K_Y, + this.params[5], + ]); + break; + default: + command = null; + params = null; } if (command === null || params === null) { throw new Error("invalid segment"); } - return fromArray([K_COMMAND, command, - K_PARAMS, params]) + return fromArray([K_COMMAND, command, K_PARAMS, params]); } } function validCommand(c) { switch (c) { - case "Z": - case "M": - case "L": - case "C": - case "Q": - case "A": - case "H": - case "V": - case "S": - case "T": - case "z": - case "m": - case "l": - case "c": - case "q": - case "a": - case "h": - case "v": - case "s": - case "t": - return true; - default: - return false; + case "Z": + case "M": + case "L": + case "C": + case "Q": + case "A": + case "H": + case "V": + case "S": + case "T": + case "z": + case "m": + case "l": + case "c": + case "q": + case "a": + case "h": + case "v": + case "s": + case "t": + return true; + default: + return false; } } @@ -118,11 +125,11 @@ class Parser { next() { const done = !this.hasNext(); if (done) { - return {done: true}; + return { done: true }; } else { return { done: false, - value: this.parseSegment() + value: this.parseSegment(), }; } } @@ -130,8 +137,10 @@ class Parser { hasNext() { if (this._currentIndex === 0) { const command = this._peekSegmentCommand(); - return ((this._currentIndex < this._endIndex) && - (command === "M" || command === "m")); + return ( + this._currentIndex < this._endIndex && + (command === "M" || command === "m") + ); } else { return this._currentIndex < this._endIndex; } @@ -148,7 +157,10 @@ class Parser { } // Check for remaining coordinates in the current command. - if ((ch === "+" || ch === "-" || ch === "." || (ch >= "0" && ch <= "9")) && this._prevCommand !== "Z") { + if ( + (ch === "+" || ch === "-" || ch === "." || (ch >= "0" && ch <= "9")) && + this._prevCommand !== "Z" + ) { if (this._prevCommand === "M") { command = "L"; } else if (this._prevCommand === "m") { @@ -177,7 +189,12 @@ class Parser { } else if (cmd === "M" || cmd === "L" || cmd === "T") { params = [this._parseNumber(), this._parseNumber()]; } else if (cmd === "S" || cmd === "Q") { - params = [this._parseNumber(), this._parseNumber(), this._parseNumber(), this._parseNumber()]; + params = [ + this._parseNumber(), + this._parseNumber(), + this._parseNumber(), + this._parseNumber(), + ]; } else if (cmd === "C") { params = [ this._parseNumber(), @@ -185,7 +202,7 @@ class Parser { this._parseNumber(), this._parseNumber(), this._parseNumber(), - this._parseNumber() + this._parseNumber(), ]; } else if (cmd === "A") { params = [ @@ -195,7 +212,7 @@ class Parser { this._parseArcFlag(), this._parseArcFlag(), this._parseNumber(), - this._parseNumber() + this._parseNumber(), ]; } else if (cmd === "Z") { this._skipOptionalSpaces(); @@ -217,7 +234,10 @@ class Parser { _isCurrentSpace() { var ch = this._string[this._currentIndex]; - return ch <= " " && (ch === " " || ch === "\n" || ch === "\t" || ch === "\r" || ch === "\f"); + return ( + ch <= " " && + (ch === " " || ch === "\n" || ch === "\t" || ch === "\r" || ch === "\f") + ); } _skipOptionalSpaces() { @@ -228,14 +248,19 @@ class Parser { } _skipOptionalSpacesOrDelimiter() { - if (this._currentIndex < this._endIndex && - !this._isCurrentSpace() && - this._string[this._currentIndex] !== ",") { + if ( + this._currentIndex < this._endIndex && + !this._isCurrentSpace() && + this._string[this._currentIndex] !== "," + ) { return false; } if (this._skipOptionalSpaces()) { - if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ",") { + if ( + this._currentIndex < this._endIndex && + this._string[this._currentIndex] === "," + ) { this._currentIndex += 1; this._skipOptionalSpaces(); } @@ -258,16 +283,25 @@ class Parser { this._skipOptionalSpaces(); // Read the sign. - if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "+") { + if ( + this._currentIndex < this._endIndex && + this._string[this._currentIndex] === "+" + ) { this._currentIndex += 1; - } else if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === "-") { + } else if ( + this._currentIndex < this._endIndex && + this._string[this._currentIndex] === "-" + ) { this._currentIndex += 1; sign = -1; } - if (this._currentIndex === this._endIndex || - ((this._string[this._currentIndex] < "0" || this._string[this._currentIndex] > "9") && - this._string[this._currentIndex] !== ".")) { + if ( + this._currentIndex === this._endIndex || + ((this._string[this._currentIndex] < "0" || + this._string[this._currentIndex] > "9") && + this._string[this._currentIndex] !== ".") + ) { // The first chacter of a number must be one of [0-9+-.]. return null; } @@ -275,9 +309,11 @@ class Parser { // Read the integer part, build right-to-left. var startIntPartIndex = this._currentIndex; - while (this._currentIndex < this._endIndex && - this._string[this._currentIndex] >= "0" && - this._string[this._currentIndex] <= "9") { + while ( + this._currentIndex < this._endIndex && + this._string[this._currentIndex] >= "0" && + this._string[this._currentIndex] <= "9" + ) { this._currentIndex += 1; // Advance to first non-digit. } @@ -293,19 +329,26 @@ class Parser { } // Read the decimals. - if (this._currentIndex < this._endIndex && this._string[this._currentIndex] === ".") { + if ( + this._currentIndex < this._endIndex && + this._string[this._currentIndex] === "." + ) { this._currentIndex += 1; // There must be a least one digit following the . - if (this._currentIndex >= this._endIndex || - this._string[this._currentIndex] < "0" || - this._string[this._currentIndex] > "9") { + if ( + this._currentIndex >= this._endIndex || + this._string[this._currentIndex] < "0" || + this._string[this._currentIndex] > "9" + ) { return null; } - while (this._currentIndex < this._endIndex && - this._string[this._currentIndex] >= "0" && - this._string[this._currentIndex] <= "9") { + while ( + this._currentIndex < this._endIndex && + this._string[this._currentIndex] >= "0" && + this._string[this._currentIndex] <= "9" + ) { frac *= 10; decimal += (this._string[this._currentIndex] - "0") / frac; this._currentIndex += 1; @@ -313,10 +356,14 @@ class Parser { } // Read the exponent part. - if (this._currentIndex !== startIndex && - this._currentIndex + 1 < this._endIndex && - (this._string[this._currentIndex] === "e" || this._string[this._currentIndex] === "E") && - (this._string[this._currentIndex + 1] !== "x" && this._string[this._currentIndex + 1] !== "m")) { + if ( + this._currentIndex !== startIndex && + this._currentIndex + 1 < this._endIndex && + (this._string[this._currentIndex] === "e" || + this._string[this._currentIndex] === "E") && + this._string[this._currentIndex + 1] !== "x" && + this._string[this._currentIndex + 1] !== "m" + ) { this._currentIndex += 1; // Read the sign of the exponent. @@ -328,17 +375,21 @@ class Parser { } // There must be an exponent. - if (this._currentIndex >= this._endIndex || - this._string[this._currentIndex] < "0" || - this._string[this._currentIndex] > "9") { + if ( + this._currentIndex >= this._endIndex || + this._string[this._currentIndex] < "0" || + this._string[this._currentIndex] > "9" + ) { return null; } - while (this._currentIndex < this._endIndex && - this._string[this._currentIndex] >= "0" && - this._string[this._currentIndex] <= "9") { + while ( + this._currentIndex < this._endIndex && + this._string[this._currentIndex] >= "0" && + this._string[this._currentIndex] <= "9" + ) { exponent *= 10; - exponent += (this._string[this._currentIndex] - "0"); + exponent += this._string[this._currentIndex] - "0"; this._currentIndex += 1; } } @@ -380,7 +431,7 @@ class Parser { this._skipOptionalSpacesOrDelimiter(); return flag; } -}; +} function absolutizePathData(pdata) { var currentX = null; @@ -389,212 +440,210 @@ function absolutizePathData(pdata) { var subpathX = null; var subpathY = null; - for (let i=0; i 1.0) ? 1.0 : (dot < -1.0) ? -1.0 : dot; + dot = dot > 1.0 ? 1.0 : dot < -1.0 ? -1.0 : dot; return sign * Math.acos(dot); } function getArcCenter(x1, y1, x2, y2, fa, fs, rx, ry, sinPhi, cosPhi) { - let x1p = (cosPhi * ((x1 - x2) / 2)) + (sinPhi * ((y1 - y2) / 2)); - let y1p = (-sinPhi * ((x1 - x2) / 2)) + (cosPhi * ((y1 - y2) / 2)); + let x1p = cosPhi * ((x1 - x2) / 2) + sinPhi * ((y1 - y2) / 2); + let y1p = -sinPhi * ((x1 - x2) / 2) + cosPhi * ((y1 - y2) / 2); let rxSq = rx * rx; let rySq = ry * ry; @@ -602,9 +651,9 @@ function getArcCenter(x1, y1, x2, y2, fa, fs, rx, ry, sinPhi, cosPhi) { let y1pSq = y1p * y1p; let radicant = rxSq * rySq - rxSq * y1pSq - rySq * x1pSq; - radicant = (radicant < 0) ? 0 : radicant; - radicant /= (rxSq * y1pSq + rySq * x1pSq); - radicant = (Math.sqrt(radicant) * ((fa === fs) ? -1 : 1)) + radicant = radicant < 0 ? 0 : radicant; + radicant /= rxSq * y1pSq + rySq * x1pSq; + radicant = Math.sqrt(radicant) * (fa === fs ? -1 : 1); let cxp = radicant * (rx / ry) * y1p; let cyp = radicant * (-ry / rx) * x1p; @@ -618,8 +667,8 @@ function getArcCenter(x1, y1, x2, y2, fa, fs, rx, ry, sinPhi, cosPhi) { let theta1 = unitVectorAngle(1, 0, v1x, v1y); let dtheta = unitVectorAngle(v1x, v1y, v2x, v2y); - dtheta = (fs === 0 && dtheta > 0) ? dtheta - Math.PI * 2 : dtheta; - dtheta = (fs === 1 && dtheta < 0) ? dtheta + Math.PI * 2 : dtheta; + dtheta = fs === 0 && dtheta > 0 ? dtheta - Math.PI * 2 : dtheta; + dtheta = fs === 1 && dtheta < 0 ? dtheta + Math.PI * 2 : dtheta; return [cx, cy, theta1, dtheta]; } @@ -639,7 +688,7 @@ function approximateUnitArc(theta1, dtheta) { x2 + y2 * alpha, y2 - x2 * alpha, x2, - y2 + y2, ]; } @@ -674,7 +723,7 @@ function processCurve(curve, cx, cy, rx, ry, sinPhi, cosPhi) { export function arcToBeziers(x1, y1, x2, y2, fa, fs, rx, ry, phi) { const tau = Math.PI * 2; - const phiTau = phi * tau / 360; + const phiTau = (phi * tau) / 360; const sinPhi = Math.sin(phiTau); const cosPhi = Math.cos(phiTau); @@ -688,7 +737,7 @@ export function arcToBeziers(x1, y1, x2, y2, fa, fs, rx, ry, phi) { } if (rx === 0 || ry === 0) { - // one of the radii is zero + // one of the radii is zero return []; } @@ -696,8 +745,8 @@ export function arcToBeziers(x1, y1, x2, y2, fa, fs, rx, ry, phi) { ry = Math.abs(ry); let lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry); - rx = (lambda > 1) ? rx * Math.sqrt(lambda) : rx; - ry = (lambda > 1) ? ry * Math.sqrt(lambda) : ry; + rx = lambda > 1 ? rx * Math.sqrt(lambda) : rx; + ry = lambda > 1 ? ry * Math.sqrt(lambda) : ry; const cc = getArcCenter(x1, y1, x2, y2, fa, fs, rx, ry, sinPhi, cosPhi); const cx = cc[0]; @@ -736,175 +785,183 @@ function simplifyPathData(pdata) { var subpathX = null; var subpathY = null; - for (let i=0; i { - if (typeof global.crypto !== "undefined" && - typeof global.crypto.getRandomValues !== "undefined") { + if ( + typeof global.crypto !== "undefined" && + typeof global.crypto.getRandomValues !== "undefined" + ) { return (buf) => { global.crypto.getRandomValues(buf); return buf; @@ -30,7 +32,7 @@ goog.scope(function() { return (buf) => { const bytes = randomBytes(buf.length); - buf.set(bytes) + buf.set(bytes); return buf; }; } else { @@ -39,8 +41,10 @@ goog.scope(function() { return (buf) => { for (let i = 0, r; i < buf.length; i++) { - if ((i & 0x03) === 0) { r = Math.random() * 0x100000000; } - buf[i] = r >>> ((i & 0x03) << 3) & 0xff; + if ((i & 0x03) === 0) { + r = Math.random() * 0x100000000; + } + buf[i] = (r >>> ((i & 0x03) << 3)) & 0xff; } return buf; }; @@ -50,31 +54,38 @@ goog.scope(function() { function toHexString(buf) { const hexMap = encoding.hexMap; let i = 0; - return (hexMap[buf[i++]] + - hexMap[buf[i++]] + - hexMap[buf[i++]] + - hexMap[buf[i++]] + '-' + - hexMap[buf[i++]] + - hexMap[buf[i++]] + '-' + - hexMap[buf[i++]] + - hexMap[buf[i++]] + '-' + - hexMap[buf[i++]] + - hexMap[buf[i++]] + '-' + - hexMap[buf[i++]] + - hexMap[buf[i++]] + - hexMap[buf[i++]] + - hexMap[buf[i++]] + - hexMap[buf[i++]] + - hexMap[buf[i++]]); - }; + return ( + hexMap[buf[i++]] + + hexMap[buf[i++]] + + hexMap[buf[i++]] + + hexMap[buf[i++]] + + "-" + + hexMap[buf[i++]] + + hexMap[buf[i++]] + + "-" + + hexMap[buf[i++]] + + hexMap[buf[i++]] + + "-" + + hexMap[buf[i++]] + + hexMap[buf[i++]] + + "-" + + hexMap[buf[i++]] + + hexMap[buf[i++]] + + hexMap[buf[i++]] + + hexMap[buf[i++]] + + hexMap[buf[i++]] + + hexMap[buf[i++]] + ); + } function getBigUint64(view, byteOffset, le) { const a = view.getUint32(byteOffset, le); const b = view.getUint32(byteOffset + 4, le); const leMask = Number(!!le); const beMask = Number(!le); - return ((BigInt(a * beMask + b * leMask) << 32n) | - (BigInt(a * leMask + b * beMask))); + return ( + (BigInt(a * beMask + b * leMask) << 32n) | BigInt(a * leMask + b * beMask) + ); } function setBigUint64(view, byteOffset, value, le) { @@ -83,8 +94,7 @@ goog.scope(function() { if (le) { view.setUint32(byteOffset + 4, hi, le); view.setUint32(byteOffset, lo, le); - } - else { + } else { view.setUint32(byteOffset, hi, le); view.setUint32(byteOffset + 4, lo, le); } @@ -104,17 +114,18 @@ goog.scope(function() { } self.shortID = (function () { - const buff = new ArrayBuffer(8); + const buff = new ArrayBuffer(8); const int8 = new Uint8Array(buff); - const view = new DataView(buff); + const view = new DataView(buff); const base = 0x0000_0000_0000_0000n; return function shortID(ts) { const tss = currentTimestamp(timeRef); - const msb = (base - | (nextLong() & 0xffff_ffff_0000_0000n) - | (tss & 0x0000_0000_ffff_ffffn)); + const msb = + base | + (nextLong() & 0xffff_ffff_0000_0000n) | + (tss & 0x0000_0000_ffff_ffffn); setBigUint64(view, 0, msb, false); return encoding.toBase62(int8); }; @@ -139,9 +150,9 @@ goog.scope(function() { const maxCs = 0x0000_0000_0000_3fffn; // 14 bits space let countCs = 0n; - let lastRd = 0n; - let lastCs = 0n; - let lastTs = 0n; + let lastRd = 0n; + let lastCs = 0n; + let lastTs = 0n; let baseMsb = 0x0000_0000_0000_8000n; let baseLsb = 0x8000_0000_0000_0000n; @@ -149,12 +160,9 @@ goog.scope(function() { lastCs = nextLong() & maxCs; const create = function create(ts, lastRd, lastCs) { - const msb = (baseMsb - | (lastRd & 0xffff_ffff_ffff_0fffn)); + const msb = baseMsb | (lastRd & 0xffff_ffff_ffff_0fffn); - const lsb = (baseLsb - | ((ts << 14n) & 0x3fff_ffff_ffff_c000n) - | lastCs); + const lsb = baseLsb | ((ts << 14n) & 0x3fff_ffff_ffff_c000n) | lastCs; setBigUint64(view, 0, msb, false); setBigUint64(view, 8, lsb, false); @@ -167,10 +175,10 @@ goog.scope(function() { let ts = currentTimestamp(timeRef); // Protect from clock regression - if ((ts - lastTs) < 0) { - lastRd = (lastRd - & 0x0000_0000_0000_0f00n - | (nextLong() & 0xffff_ffff_ffff_f0ffn)); + if (ts - lastTs < 0) { + lastRd = + (lastRd & 0x0000_0000_0000_0f00n) | + (nextLong() & 0xffff_ffff_ffff_f0ffn); countCs = 0n; continue; } @@ -209,63 +217,63 @@ goog.scope(function() { // Parse ........-....-....-####-............ int8[8] = (rest = parseInt(uuid.slice(19, 23), 16)) >>> 8; - int8[9] = rest & 0xff, - - // Parse ........-....-....-....-############ - // (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes) - int8[10] = ((rest = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff; + (int8[9] = rest & 0xff), + // Parse ........-....-....-....-############ + // (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes) + (int8[10] = + ((rest = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff); int8[11] = (rest / 0x100000000) & 0xff; int8[12] = (rest >>> 24) & 0xff; int8[13] = (rest >>> 16) & 0xff; int8[14] = (rest >>> 8) & 0xff; int8[15] = rest & 0xff; - } + }; const fromPair = (hi, lo) => { view.setBigInt64(0, hi); view.setBigInt64(8, lo); return encoding.bufferToHex(int8, true); - } + }; const getHi = (uuid) => { fillBytes(uuid); return view.getBigInt64(0); - } + }; const getLo = (uuid) => { fillBytes(uuid); return view.getBigInt64(8); - } + }; const getBytes = (uuid) => { fillBytes(uuid); return Int8Array.from(int8); - } + }; const getUnsignedParts = (uuid) => { fillBytes(uuid); const result = new Uint32Array(4); - result[0] = view.getUint32(0) + result[0] = view.getUint32(0); result[1] = view.getUint32(4); result[2] = view.getUint32(8); result[3] = view.getUint32(12); return result; - } + }; const fromUnsignedParts = (a, b, c, d) => { - view.setUint32(0, a) - view.setUint32(4, b) - view.setUint32(8, c) - view.setUint32(12, d) + view.setUint32(0, a); + view.setUint32(4, b); + view.setUint32(8, c); + view.setUint32(12, d); return encoding.bufferToHex(int8, true); - } + }; const fromArray = (u8data) => { int8.set(u8data); return encoding.bufferToHex(int8, true); - } + }; const setTag = (tag) => { tag = BigInt.asUintN(64, "" + tag); @@ -273,9 +281,9 @@ goog.scope(function() { throw new Error("illegal arguments: tag value should fit in 4bits"); } - lastRd = (lastRd - & 0xffff_ffff_ffff_f0ffn - | ((tag << 8) & 0x0000_0000_0000_0f00n)); + lastRd = + (lastRd & 0xffff_ffff_ffff_f0ffn) | + ((tag << 8) & 0x0000_0000_0000_0f00n); }; factory.create = create; @@ -290,9 +298,9 @@ goog.scope(function() { return factory; })(); - self.shortV8 = function(uuid) { + self.shortV8 = function (uuid) { const buff = encoding.hexToBuffer(uuid); - const short = new Uint8Array(buff, 4); + const short = new Uint8Array(buff, 4); return encoding.bufferToBase62(short); }; @@ -307,7 +315,7 @@ goog.scope(function() { return self.v8.fromPair(hi, lo); }; - self.fromBytes = function(data) { + self.fromBytes = function (data) { if (data instanceof Uint8Array) { return self.v8.fromArray(data); } else if (data instanceof Int8Array) { @@ -325,15 +333,15 @@ goog.scope(function() { return self.v8.getUnsignedParts(uuid); }; - self.fromUnsignedParts = function(a,b,c,d) { - return self.v8.fromUnsignedParts(a,b,c,d); + self.fromUnsignedParts = function (a, b, c, d) { + return self.v8.fromUnsignedParts(a, b, c, d); }; self.getHi = function (uuid) { return self.v8.getHi(uuid); - } + }; self.getLo = function (uuid) { return self.v8.getLo(uuid); - } + }; }); diff --git a/common/src/app/common/weak/impl_weak_map.js b/common/src/app/common/weak/impl_weak_map.js index 2379ea7e14..2c6fa8db53 100644 --- a/common/src/app/common/weak/impl_weak_map.js +++ b/common/src/app/common/weak/impl_weak_map.js @@ -67,8 +67,11 @@ export class WeakEqMap { } set(key, value) { - if (key === null || (typeof key !== 'object' && typeof key !== 'function')) { - throw new TypeError('WeakEqMap keys must be objects (like WeakMap).'); + if ( + key === null || + (typeof key !== "object" && typeof key !== "function") + ) { + throw new TypeError("WeakEqMap keys must be objects (like WeakMap)."); } const hash = this._hash(key); const bucket = this._getBucket(hash); diff --git a/common/tests.edn b/common/tests.edn index 9f487a7eaf..0a0582fed6 100644 --- a/common/tests.edn +++ b/common/tests.edn @@ -1,4 +1,4 @@ #kaocha/v1 - {:tests [{:id :unit - :test-paths ["test"]}] - :kaocha/reporter [kaocha.report/dots]} +{:tests [{:id :unit + :test-paths ["test"]}] + :kaocha/reporter [kaocha.report/dots]} diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index efa134d999..07fbab0bb4 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -18,6 +18,7 @@ RUN set -ex; \ curl \ bash \ git \ + ripgrep \ \ curl \ ca-certificates \ diff --git a/exporter/package.json b/exporter/package.json index 70b64bea7d..ba6570f3cc 100644 --- a/exporter/package.json +++ b/exporter/package.json @@ -34,7 +34,8 @@ "watch": "pnpm run watch:app", "build:app": "clojure -M:dev:shadow-cljs release main", "build": "pnpm run clear:shadow-cache && pnpm run build:app", - "fmt:clj": "cljfmt fix --parallel=true src/", - "lint:clj": "cljfmt check --parallel src/ && clj-kondo --parallel --lint src/" + "fmt": "cljfmt fix --parallel=true src/", + "check-fmt": "cljfmt check --parallel=true src/", + "lint": "clj-kondo --parallel --lint src/" } } diff --git a/frontend/package.json b/frontend/package.json index f1fb0b3feb..b2a1c7da1d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,12 +23,15 @@ "build:app:main": "clojure -M:dev:shadow-cljs release main worker", "build:app:worker": "clojure -M:dev:shadow-cljs release worker", "build:app": "pnpm run clear:shadow-cache && pnpm run build:app:main && pnpm run build:app:libs", + "check-fmt:clj": "cljfmt check --parallel=true src/ test/", + "check-fmt:js": "prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js -c text-editor/**/*.js", + "check-fmt:scss": "prettier -c resources/styles -c src/**/*.scss", "fmt:clj": "cljfmt fix --parallel=true src/ test/", "fmt:js": "prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js -c text-editor/**/*.js -w", "fmt:scss": "prettier -c resources/styles -c src/**/*.scss -w", - "lint:clj": "cljfmt check --parallel=false src/ test/ && clj-kondo --parallel --lint src/", - "lint:js": "prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js text-editor/**/*.js", - "lint:scss": "prettier -c resources/styles -c src/**/*.scss", + "lint:clj": "clj-kondo --parallel --lint ../common/src src/", + "lint:js": "exit 0", + "lint:scss": "exit 0", "build:test": "clojure -M:dev:shadow-cljs compile test", "test": "pnpm run build:test && node target/tests/test.js", "test:storybook": "vitest run --project=storybook", diff --git a/library/package.json b/library/package.json index c3f3d1c32a..f5dff418e8 100644 --- a/library/package.json +++ b/library/package.json @@ -26,8 +26,9 @@ "clear:shadow-cache": "rm -rf .shadow-cljs", "build": "pnpm run clear:shadow-cache && clojure -M:dev:shadow-cljs release library", "build:bundle": "./scripts/build", - "fmt:clj": "cljfmt fix --parallel=true src/ test/", - "lint:clj": "cljfmt check --parallel=false src/ test/ && clj-kondo --parallel --lint src/", + "fmt": "cljfmt fix --parallel=true src/ test/", + "check-fmt": "cljfmt check --parallel=true src/ test/", + "lint": "clj-kondo --parallel --lint src/", "test": "node --test", "watch:test": "node --test --watch", "watch": "pnpm run clear:shadow-cache && clojure -M:dev:shadow-cljs watch library" diff --git a/scripts/check-fmt b/scripts/check-fmt new file mode 100755 index 0000000000..ce5c635630 --- /dev/null +++ b/scripts/check-fmt @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -ex + +cljfmt --parallel=true check \ + common/src/ \ + common/test/ \ + frontend/src/ \ + frontend/test/ \ + backend/src/ \ + backend/test/ \ + exporter/src/ \ + library/src; diff --git a/scripts/lint b/scripts/lint index d17e6d3c86..4ab59aed13 100755 --- a/scripts/lint +++ b/scripts/lint @@ -2,16 +2,6 @@ set -ex -cljfmt check --parallel=true \ - common/src/ \ - common/test/ \ - frontend/src/ \ - frontend/test/ \ - backend/src/ \ - backend/test/ \ - exporter/src/ \ - library/src; - clj-kondo --parallel=true --lint common/src; clj-kondo --parallel=true --lint frontend/src; clj-kondo --parallel=true --lint backend/src;