mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
✨ Add improvements to AGENTS.md (#8586)
This commit is contained in:
parent
e855907b05
commit
7ec9261475
21
.github/workflows/tests.yml
vendored
21
.github/workflows/tests.yml
vendored
@ -34,6 +34,8 @@ jobs:
|
|||||||
corepack enable;
|
corepack enable;
|
||||||
corepack install;
|
corepack install;
|
||||||
pnpm install;
|
pnpm install;
|
||||||
|
pnpm run check-fmt:clj
|
||||||
|
pnpm run check-fmt:js
|
||||||
pnpm run lint:clj
|
pnpm run lint:clj
|
||||||
|
|
||||||
- name: Lint Frontend
|
- name: Lint Frontend
|
||||||
@ -42,6 +44,9 @@ jobs:
|
|||||||
corepack enable;
|
corepack enable;
|
||||||
corepack install;
|
corepack install;
|
||||||
pnpm 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:clj
|
||||||
pnpm run lint:js
|
pnpm run lint:js
|
||||||
pnpm run lint:scss
|
pnpm run lint:scss
|
||||||
@ -52,7 +57,8 @@ jobs:
|
|||||||
corepack enable;
|
corepack enable;
|
||||||
corepack install;
|
corepack install;
|
||||||
pnpm install;
|
pnpm install;
|
||||||
pnpm run lint:clj
|
pnpm run check-fmt
|
||||||
|
pnpm run lint
|
||||||
|
|
||||||
- name: Lint Exporter
|
- name: Lint Exporter
|
||||||
working-directory: ./exporter
|
working-directory: ./exporter
|
||||||
@ -60,7 +66,8 @@ jobs:
|
|||||||
corepack enable;
|
corepack enable;
|
||||||
corepack install;
|
corepack install;
|
||||||
pnpm install;
|
pnpm install;
|
||||||
pnpm run lint:clj
|
pnpm run check-fmt
|
||||||
|
pnpm run lint
|
||||||
|
|
||||||
- name: Lint Library
|
- name: Lint Library
|
||||||
working-directory: ./library
|
working-directory: ./library
|
||||||
@ -68,7 +75,8 @@ jobs:
|
|||||||
corepack enable;
|
corepack enable;
|
||||||
corepack install;
|
corepack install;
|
||||||
pnpm install;
|
pnpm install;
|
||||||
pnpm run lint:clj
|
pnpm run check-fmt
|
||||||
|
pnpm run lint
|
||||||
|
|
||||||
test-common:
|
test-common:
|
||||||
name: "Common Tests"
|
name: "Common Tests"
|
||||||
@ -79,12 +87,7 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run tests on JVM
|
- name: Run tests
|
||||||
working-directory: ./common
|
|
||||||
run: |
|
|
||||||
clojure -M:dev:test
|
|
||||||
|
|
||||||
- name: Run tests on NODE
|
|
||||||
working-directory: ./common
|
working-directory: ./common
|
||||||
run: |
|
run: |
|
||||||
./scripts/test
|
./scripts/test
|
||||||
|
|||||||
22
.gitignore
vendored
22
.gitignore
vendored
@ -1,11 +1,4 @@
|
|||||||
.pnp.*
|
.pnp.*
|
||||||
.yarn/*
|
|
||||||
!.yarn/patches
|
|
||||||
!.yarn/plugins
|
|
||||||
!.yarn/releases
|
|
||||||
!.yarn/sdks
|
|
||||||
!.yarn/versions
|
|
||||||
.pnpm-store
|
|
||||||
*-init.clj
|
*-init.clj
|
||||||
*.css.json
|
*.css.json
|
||||||
*.jar
|
*.jar
|
||||||
@ -20,8 +13,6 @@
|
|||||||
.nyc_output
|
.nyc_output
|
||||||
.rebel_readline_history
|
.rebel_readline_history
|
||||||
.repl
|
.repl
|
||||||
.shadow-cljs
|
|
||||||
.pnpm-store/
|
|
||||||
/*.jpg
|
/*.jpg
|
||||||
/*.md
|
/*.md
|
||||||
/*.png
|
/*.png
|
||||||
@ -36,6 +27,7 @@
|
|||||||
/playground/
|
/playground/
|
||||||
/backend/*.md
|
/backend/*.md
|
||||||
!/backend/AGENTS.md
|
!/backend/AGENTS.md
|
||||||
|
/backend/.shadow-cljs
|
||||||
/backend/*.sql
|
/backend/*.sql
|
||||||
/backend/*.txt
|
/backend/*.txt
|
||||||
/backend/assets/
|
/backend/assets/
|
||||||
@ -48,13 +40,13 @@
|
|||||||
/backend/experiments
|
/backend/experiments
|
||||||
/backend/scripts/_env.local
|
/backend/scripts/_env.local
|
||||||
/bundle*
|
/bundle*
|
||||||
/cd.md
|
|
||||||
/clj-profiler/
|
/clj-profiler/
|
||||||
/common/coverage
|
/common/coverage
|
||||||
/common/target
|
/common/target
|
||||||
/deploy
|
/common/.shadow-cljs
|
||||||
/docker/images/bundle*
|
/docker/images/bundle*
|
||||||
/exporter/target
|
/exporter/target
|
||||||
|
/exporter/.shadow-cljs
|
||||||
/frontend/.storybook/preview-body.html
|
/frontend/.storybook/preview-body.html
|
||||||
/frontend/.storybook/preview-head.html
|
/frontend/.storybook/preview-head.html
|
||||||
/frontend/playwright-report/
|
/frontend/playwright-report/
|
||||||
@ -68,9 +60,9 @@
|
|||||||
/frontend/storybook-static/
|
/frontend/storybook-static/
|
||||||
/frontend/target/
|
/frontend/target/
|
||||||
/frontend/test-results/
|
/frontend/test-results/
|
||||||
|
/frontend/.shadow-cljs
|
||||||
/other/
|
/other/
|
||||||
/scripts/
|
/nexus/
|
||||||
/telemetry/
|
|
||||||
/tmp/
|
/tmp/
|
||||||
/vendor/**/target
|
/vendor/**/target
|
||||||
/vendor/svgclean/bundle*.js
|
/vendor/svgclean/bundle*.js
|
||||||
@ -79,13 +71,11 @@
|
|||||||
/library/*.zip
|
/library/*.zip
|
||||||
/external
|
/external
|
||||||
/penpot-nitrate
|
/penpot-nitrate
|
||||||
|
|
||||||
clj-profiler/
|
|
||||||
node_modules
|
|
||||||
/test-results/
|
/test-results/
|
||||||
/playwright-report/
|
/playwright-report/
|
||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
/render-wasm/target/
|
/render-wasm/target/
|
||||||
|
/**/node_modules
|
||||||
/**/.yarn/*
|
/**/.yarn/*
|
||||||
/.pnpm-store
|
/.pnpm-store
|
||||||
|
|||||||
352
AGENTS.md
352
AGENTS.md
@ -1,4 +1,4 @@
|
|||||||
# Penpot – Copilot Instructions
|
# Penpot – Instructions
|
||||||
|
|
||||||
## Architecture Overview
|
## 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
|
orchestrates cross-component builds. `run-ci.sh` defines the CI
|
||||||
pipeline.
|
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
|
## Build, Test & Lint Commands
|
||||||
|
|
||||||
@ -28,27 +34,26 @@ Run `./scripts/setup` for setup all dependencies.
|
|||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Dev
|
# Build (Producution)
|
||||||
pnpm run watch:app # Full dev build (WASM + CLJS + assets)
|
|
||||||
|
|
||||||
# Production Build
|
|
||||||
./scripts/build
|
./scripts/build
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
pnpm run test # Build ClojureScript tests + run node target/tests/test.js
|
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
|
|
||||||
|
|
||||||
# Lint
|
# Lint
|
||||||
pnpm run lint:js # format and linter check for JS
|
pnpm run lint:js # Linter for JS/TS
|
||||||
pnpm run lint:clj # format and linter check for CLJ
|
pnpm run lint:clj # Linter for CLJ/CLJS/CLJC
|
||||||
pnpm run lint:scss # prettier check for SCSS
|
pnpm run lint:scss # Linter for SCSS
|
||||||
|
|
||||||
# Code formatting
|
# Check Code Formart
|
||||||
pnpm run fmt:clj # Format CLJ
|
pnpm run check-fmt:clj # Format CLJ/CLJS/CLJC
|
||||||
pnpm run fmt:js # prettier for JS
|
pnpm run check-fmt:js # Format JS/TS
|
||||||
pnpm run fmt:scss # prettier for SCSS
|
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
|
To run a focused ClojureScript unit test: edit
|
||||||
@ -58,28 +63,63 @@ run build:test && node target/tests/test.js`.
|
|||||||
|
|
||||||
### Backend (`cd backend`)
|
### Backend (`cd backend`)
|
||||||
|
|
||||||
```bash
|
Run `pnpm install` for install all dependencies.
|
||||||
# Tests (Kaocha)
|
|
||||||
clojure -M:dev:test # Full suite
|
|
||||||
clojure -M:dev:test --focus backend-tests.my-ns-test # Single namespace
|
|
||||||
|
|
||||||
# Lint / Format
|
```bash
|
||||||
pnpm run lint:clj
|
# Run full test suite
|
||||||
pnpm run fmt:clj
|
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`)
|
### 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
|
```bash
|
||||||
pnpm run test # Build + run node target/tests/test.js
|
clojure -M:dev:test # Run full test suite under JVM
|
||||||
pnpm run watch:test # Watch mode
|
clojure -M:dev:test --focus backend-tests.my-ns-test # Run single namespace under JVM
|
||||||
pnpm run lint:clj
|
|
||||||
pnpm run fmt:clj
|
# 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`)
|
### Render-WASM (`cd render-wasm`)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -93,6 +133,10 @@ cargo fmt --check
|
|||||||
|
|
||||||
### Namespace Structure
|
### 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:**
|
**Backend:**
|
||||||
- `app.rpc.commands.*` – RPC command implementations (`auth`, `files`, `teams`, etc.)
|
- `app.rpc.commands.*` – RPC command implementations (`auth`, `files`, `teams`, etc.)
|
||||||
- `app.http.*` – HTTP routes and middleware
|
- `app.http.*` – HTTP routes and middleware
|
||||||
@ -109,14 +153,26 @@ cargo fmt --check
|
|||||||
- `app.util.*` – Utilities (DOM, HTTP, i18n, keyboard shortcuts)
|
- `app.util.*` – Utilities (DOM, HTTP, i18n, keyboard shortcuts)
|
||||||
|
|
||||||
**Common:**
|
**Common:**
|
||||||
- `app.common.types.*` – Shared data types for shapes, files, pages
|
- `app.common.types.*` – Shared data types for shapes, files, pages using Malli schemas
|
||||||
- `app.common.schema` – Malli validation schemas
|
- `app.common.schema` – Malli abstraction layer, exposes the most used functions from malli
|
||||||
- `app.common.geom.*` – Geometry utilities
|
- `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
|
- `app.common.data.macros` – Performance macros used everywhere
|
||||||
|
|
||||||
|
|
||||||
### Backend RPC Commands
|
### Backend RPC Commands
|
||||||
|
|
||||||
All API calls go through a single RPC endpoint: `POST /api/rpc/command/<cmd-name>`.
|
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/<cmd-name>`. 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
|
```clojure
|
||||||
(sv/defmethod ::my-command
|
(sv/defmethod ::my-command
|
||||||
@ -129,12 +185,18 @@ All API calls go through a single RPC endpoint: `POST /api/rpc/command/<cmd-name
|
|||||||
{:id (uuid/next)})
|
{:id (uuid/next)})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Look under `src/app/rpc/commands/*.clj` to see more examples.
|
||||||
|
|
||||||
|
|
||||||
### Frontend State Management (Potok)
|
### Frontend State Management (Potok)
|
||||||
|
|
||||||
State is a single atom managed by a Potok store. Events implement protocols:
|
State is a single atom managed by a Potok store. Events implement protocols
|
||||||
|
(funcool/potok library):
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
(defn my-event [data]
|
(defn my-event
|
||||||
|
"doc string"
|
||||||
|
[data]
|
||||||
(ptk/reify ::my-event
|
(ptk/reify ::my-event
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state] ;; synchronous state transition
|
(update [_ state] ;; synchronous state transition
|
||||||
@ -148,19 +210,40 @@ State is a single atom managed by a Potok store. Events implement protocols:
|
|||||||
|
|
||||||
ptk/EffectEvent
|
ptk/EffectEvent
|
||||||
(effect [_ state _] ;; pure side effects (DOM, logging)
|
(effect [_ state _] ;; pure side effects (DOM, logging)
|
||||||
(.focus (dom/get-element "id")))))
|
(dom/focus (dom/get-element "id")))))
|
||||||
```
|
```
|
||||||
|
|
||||||
Dispatch with `(st/emit! (my-event data))`. Read state via reactive
|
The state is located under `app.main.store` namespace where we have
|
||||||
refs: `(deref refs/selected-shapes)`. Prefer helpers from
|
the `emit!` function responsible of emiting events.
|
||||||
`app.util.dom` instead of using direct dom calls, if no helper is
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```cljs
|
||||||
|
(ns some.ns
|
||||||
|
(:require
|
||||||
|
[app.main.data.my-events :refer [my-event]]
|
||||||
|
[app.main.store :as st]))
|
||||||
|
|
||||||
|
(defn on-click
|
||||||
|
[event]
|
||||||
|
(st/emit! (my-event)))
|
||||||
|
```
|
||||||
|
|
||||||
|
On `app.main.refs` we have reactive references which lookup into the main state
|
||||||
|
for just inner data or precalculated data. That references are very usefull but
|
||||||
|
should be used with care because, per example if we have complex operation, this
|
||||||
|
operation will be executed on each state change, and sometimes is better to have
|
||||||
|
simple references and use react `use-memo` for more granular memoization.
|
||||||
|
|
||||||
|
Prefer helpers from `app.util.dom` instead of using direct dom calls, if no helper is
|
||||||
available, prefer adding a new helper for handling it and the use the
|
available, prefer adding a new helper for handling it and the use the
|
||||||
new helper.
|
new helper.
|
||||||
|
|
||||||
|
|
||||||
### CSS Modules Pattern
|
### CSS (Modules Pattern)
|
||||||
|
|
||||||
Styles are co-located with components. Each `.cljs` file has a corresponding `.scss` file:
|
Styles are co-located with components. Each `.cljs` file has a corresponding
|
||||||
|
`.scss` file:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
;; In the component namespace:
|
;; In the component namespace:
|
||||||
@ -174,8 +257,24 @@ Styles are co-located with components. Each `.cljs` file has a corresponding `.s
|
|||||||
|
|
||||||
;; When you need concat an existing class:
|
;; When you need concat an existing class:
|
||||||
[:div {:class [existing-class (stl/css-case :some-class true :selected (= drawtool :rect))]}]
|
[:div {:class [existing-class (stl/css-case :some-class true :selected (= drawtool :rect))]}]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration tests (Playwright)
|
||||||
|
|
||||||
|
Integration tests are developed under `frontend/playwright` directory, we use
|
||||||
|
mocks for remove communication with backend.
|
||||||
|
|
||||||
|
The tests should be executed under `./frontend` directory:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
cd frontend/
|
||||||
|
|
||||||
|
pnpm run test:e2e # Playwright e2e tests
|
||||||
|
pnpm run test:e2e --grep "pattern" # Single e2e test by pattern
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensure everything installed with `./scripts/setup` script.
|
||||||
|
|
||||||
|
|
||||||
### Performance Macros (`app.common.data.macros`)
|
### Performance Macros (`app.common.data.macros`)
|
||||||
|
|
||||||
@ -187,7 +286,7 @@ Always prefer these macros over their `clojure.core` equivalents — they compil
|
|||||||
(dm/str "a" "b" "c") ;; string concatenation
|
(dm/str "a" "b" "c") ;; string concatenation
|
||||||
```
|
```
|
||||||
|
|
||||||
### Shared Code (cljc)
|
### Shared Code under Common (CLJC)
|
||||||
|
|
||||||
Files in `common/src/app/common/` use reader conditionals to target both runtimes:
|
Files in `common/src/app/common/` use reader conditionals to target both runtimes:
|
||||||
|
|
||||||
@ -196,37 +295,129 @@ Files in `common/src/app/common/` use reader conditionals to target both runtime
|
|||||||
:cljs (:require [cljs.core :as core]))
|
:cljs (:require [cljs.core :as core]))
|
||||||
```
|
```
|
||||||
|
|
||||||
Both frontend and backend depend on `common` as a local library (`penpot/common {:local/root "../common"}`).
|
Both frontend and backend depend on `common` as a local library (`penpot/common
|
||||||
|
{:local/root "../common"}`).
|
||||||
|
|
||||||
|
|
||||||
### Component Definition (Rumext / React)
|
|
||||||
|
|
||||||
The codebase has several kind of components, some of them use legacy
|
### Component Standards & Syntax (React & Rumext: mf/defc)
|
||||||
syntax. The current and the most recent syntax uses `*` suffix on the
|
|
||||||
name. This indicates to the `mf/defc` macro apply concrete rules on
|
|
||||||
how props should be treated.
|
|
||||||
|
|
||||||
```clojure
|
The codebase contains various component patterns. When creating or refactoring
|
||||||
|
components, follow the Modern Syntax rules outlined below.
|
||||||
|
|
||||||
|
1. The * Suffix Convention
|
||||||
|
|
||||||
|
The most recent syntax uses a * suffix in the component name (e.g.,
|
||||||
|
my-component*). This suffix signals the mf/defc macro to apply specific rules
|
||||||
|
for props handling and destructuring and optimization.
|
||||||
|
|
||||||
|
2. Component Definition
|
||||||
|
|
||||||
|
Modern components should use the following structure:
|
||||||
|
|
||||||
|
```clj
|
||||||
(mf/defc my-component*
|
(mf/defc my-component*
|
||||||
{::mf/wrap [mf/memo]} ;; React.memo
|
{::mf/wrap [mf/memo]} ;; Equivalent to React.memo
|
||||||
[{:keys [name on-click]}]
|
[{:keys [name on-click]}] ;; Destructured props
|
||||||
[:div {:class (stl/css :root)
|
[:div {:class (stl/css :root)
|
||||||
:on-click on-click}
|
:on-click on-click}
|
||||||
name])
|
name])
|
||||||
```
|
```
|
||||||
|
|
||||||
Hooks: `(mf/use-state)`, `(mf/use-effect)`, `(mf/use-memo)` – analgous to react hooks.
|
3. Hooks
|
||||||
|
|
||||||
|
Use the mf namespace for hooks to maintain consistency with the macro's
|
||||||
|
lifecycle management. These are analogous to standard React hooks:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(mf/use-state) ;; analogous to React.useState adapted to cljs semantics
|
||||||
|
(mf/use-effect) ;; analogous to React.useEffect
|
||||||
|
(mf/use-memo) ;; analogous to React.useMemo
|
||||||
|
(mf/use-fn) ;; analogous to React.useCallback
|
||||||
|
```
|
||||||
|
|
||||||
|
The `mf/use-state` in difference with React.useState, returns an atom-like
|
||||||
|
object, where you can use `swap!` or `reset!` for to perform an update and
|
||||||
|
`deref` for get the current value.
|
||||||
|
|
||||||
|
You also has `mf/deref` hook (which does not follow the `use-` naming pattern)
|
||||||
|
and it's purpose is watch (subscribe to changes) on atom or derived atom (from
|
||||||
|
okulary) and get the current value. Is mainly used for subscribe to lenses
|
||||||
|
defined in `app.main.refs` or (private lenses defined in namespaces).
|
||||||
|
|
||||||
|
Rumext also comes with improved syntax macros as alternative to `mf/use-effect`
|
||||||
|
and `mf/use-memo` functions. Examples:
|
||||||
|
|
||||||
|
|
||||||
The component usage should always follow the `[:> my-component*
|
Example for `mf/with-memo` macro:
|
||||||
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.
|
|
||||||
|
|
||||||
---
|
```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: `<emoji-code> <subject>`
|
Format: `<emoji-code> <subject>`
|
||||||
|
|
||||||
@ -263,3 +454,46 @@ applicable.
|
|||||||
| ⬇️ | `:arrow_down:` | Dependency downgrade |
|
| ⬇️ | `:arrow_down:` | Dependency downgrade |
|
||||||
| 🔥 | `:fire:` | Remove files or code |
|
| 🔥 | `:fire:` | Remove files or code |
|
||||||
| 🌐 | `:globe_with_meridians:` | Translations |
|
| 🌐 | `: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).
|
||||||
|
|||||||
@ -19,7 +19,9 @@
|
|||||||
"ws": "^8.17.0"
|
"ws": "^8.17.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint:clj": "cljfmt check --parallel=false src/ test/ && clj-kondo --parallel --lint src/",
|
"lint": "clj-kondo --parallel --lint ../common/src src/",
|
||||||
"fmt:clj": "cljfmt fix --parallel=true src/ test/"
|
"check-fmt": "cljfmt check --parallel=true src/ test/",
|
||||||
|
"fmt": "cljfmt fix --parallel=true src/ test/",
|
||||||
|
"test": "clojure -M:dev:test"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.10",
|
||||||
|
"prettier": "3.5.3",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"ws": "^8.18.2"
|
"ws": "^8.18.2"
|
||||||
},
|
},
|
||||||
@ -20,11 +21,15 @@
|
|||||||
"date-fns": "^4.1.0"
|
"date-fns": "^4.1.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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:clj": "cljfmt fix --parallel=true src/ test/",
|
||||||
|
"fmt:js": "prettier -c src/**/*.js -w",
|
||||||
"lint": "pnpm run lint:clj",
|
"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'\"",
|
"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",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
common/pnpm-lock.yaml
generated
10
common/pnpm-lock.yaml
generated
@ -18,6 +18,9 @@ importers:
|
|||||||
nodemon:
|
nodemon:
|
||||||
specifier: ^3.1.10
|
specifier: ^3.1.10
|
||||||
version: 3.1.11
|
version: 3.1.11
|
||||||
|
prettier:
|
||||||
|
specifier: 3.5.3
|
||||||
|
version: 3.5.3
|
||||||
source-map-support:
|
source-map-support:
|
||||||
specifier: ^0.5.21
|
specifier: ^0.5.21
|
||||||
version: 0.5.21
|
version: 0.5.21
|
||||||
@ -169,6 +172,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||||
engines: {node: '>=8.6'}
|
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:
|
pstree.remy@1.1.8:
|
||||||
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
|
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
|
||||||
|
|
||||||
@ -405,6 +413,8 @@ snapshots:
|
|||||||
|
|
||||||
picomatch@2.3.1: {}
|
picomatch@2.3.1: {}
|
||||||
|
|
||||||
|
prettier@3.5.3: {}
|
||||||
|
|
||||||
pstree.remy@1.1.8: {}
|
pstree.remy@1.1.8: {}
|
||||||
|
|
||||||
readdirp@3.6.0:
|
readdirp@3.6.0:
|
||||||
|
|||||||
@ -4,4 +4,5 @@ set -ex
|
|||||||
corepack enable;
|
corepack enable;
|
||||||
corepack install;
|
corepack install;
|
||||||
pnpm install;
|
pnpm install;
|
||||||
pnpm run test;
|
pnpm run test:js;
|
||||||
|
pnpm run test:jvm;
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
goog.require("cljs.core");
|
goog.require("cljs.core");
|
||||||
goog.provide("app.common.encoding_impl");
|
goog.provide("app.common.encoding_impl");
|
||||||
|
|
||||||
goog.scope(function() {
|
goog.scope(function () {
|
||||||
const core = cljs.core;
|
const core = cljs.core;
|
||||||
const global = goog.global;
|
const global = goog.global;
|
||||||
const self = app.common.encoding_impl;
|
const self = app.common.encoding_impl;
|
||||||
@ -28,8 +28,10 @@ goog.scope(function() {
|
|||||||
// Accept UUID hex format
|
// Accept UUID hex format
|
||||||
input = input.replace(/-/g, "");
|
input = input.replace(/-/g, "");
|
||||||
|
|
||||||
if ((input.length % 2) !== 0) {
|
if (input.length % 2 !== 0) {
|
||||||
throw new RangeError("Expected string to be an even number of characters")
|
throw new RangeError(
|
||||||
|
"Expected string to be an even number of characters",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const view = new Uint8Array(input.length / 2);
|
const view = new Uint8Array(input.length / 2);
|
||||||
@ -44,7 +46,11 @@ goog.scope(function() {
|
|||||||
function bufferToHex(source, isUuid) {
|
function bufferToHex(source, isUuid) {
|
||||||
if (source instanceof Uint8Array) {
|
if (source instanceof Uint8Array) {
|
||||||
} else if (ArrayBuffer.isView(source)) {
|
} 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)) {
|
} else if (Array.isArray(source)) {
|
||||||
source = Uint8Array.from(source);
|
source = Uint8Array.from(source);
|
||||||
}
|
}
|
||||||
@ -56,22 +62,28 @@ goog.scope(function() {
|
|||||||
const spacer = isUuid ? "-" : "";
|
const spacer = isUuid ? "-" : "";
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
return (hexMap[source[i++]] +
|
return (
|
||||||
hexMap[source[i++]] +
|
hexMap[source[i++]] +
|
||||||
hexMap[source[i++]] +
|
hexMap[source[i++]] +
|
||||||
hexMap[source[i++]] + spacer +
|
hexMap[source[i++]] +
|
||||||
hexMap[source[i++]] +
|
hexMap[source[i++]] +
|
||||||
hexMap[source[i++]] + spacer +
|
spacer +
|
||||||
hexMap[source[i++]] +
|
hexMap[source[i++]] +
|
||||||
hexMap[source[i++]] + spacer +
|
hexMap[source[i++]] +
|
||||||
hexMap[source[i++]] +
|
spacer +
|
||||||
hexMap[source[i++]] + spacer +
|
hexMap[source[i++]] +
|
||||||
hexMap[source[i++]] +
|
hexMap[source[i++]] +
|
||||||
hexMap[source[i++]] +
|
spacer +
|
||||||
hexMap[source[i++]] +
|
hexMap[source[i++]] +
|
||||||
hexMap[source[i++]] +
|
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++]] +
|
||||||
|
hexMap[source[i++]]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.hexToBuffer = hexToBuffer;
|
self.hexToBuffer = hexToBuffer;
|
||||||
@ -87,8 +99,10 @@ goog.scope(function() {
|
|||||||
// for base16 (hex), base32, or base64 encoding in a standards
|
// for base16 (hex), base32, or base64 encoding in a standards
|
||||||
// compliant manner.
|
// compliant manner.
|
||||||
|
|
||||||
function getBaseCodec (ALPHABET) {
|
function getBaseCodec(ALPHABET) {
|
||||||
if (ALPHABET.length >= 255) { throw new TypeError("Alphabet too long"); }
|
if (ALPHABET.length >= 255) {
|
||||||
|
throw new TypeError("Alphabet too long");
|
||||||
|
}
|
||||||
let BASE_MAP = new Uint8Array(256);
|
let BASE_MAP = new Uint8Array(256);
|
||||||
for (let j = 0; j < BASE_MAP.length; j++) {
|
for (let j = 0; j < BASE_MAP.length; j++) {
|
||||||
BASE_MAP[j] = 255;
|
BASE_MAP[j] = 255;
|
||||||
@ -96,22 +110,32 @@ goog.scope(function() {
|
|||||||
for (let i = 0; i < ALPHABET.length; i++) {
|
for (let i = 0; i < ALPHABET.length; i++) {
|
||||||
let x = ALPHABET.charAt(i);
|
let x = ALPHABET.charAt(i);
|
||||||
let xc = x.charCodeAt(0);
|
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;
|
BASE_MAP[xc] = i;
|
||||||
}
|
}
|
||||||
let BASE = ALPHABET.length;
|
let BASE = ALPHABET.length;
|
||||||
let LEADER = ALPHABET.charAt(0);
|
let LEADER = ALPHABET.charAt(0);
|
||||||
let FACTOR = Math.log(BASE) / Math.log(256); // log(BASE) / log(256), rounded up
|
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
|
let iFACTOR = Math.log(256) / Math.log(BASE); // log(256) / log(BASE), rounded up
|
||||||
function encode (source) {
|
function encode(source) {
|
||||||
if (source instanceof Uint8Array) {
|
if (source instanceof Uint8Array) {
|
||||||
} else if (ArrayBuffer.isView(source)) {
|
} 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)) {
|
} else if (Array.isArray(source)) {
|
||||||
source = Uint8Array.from(source);
|
source = Uint8Array.from(source);
|
||||||
}
|
}
|
||||||
if (!(source instanceof Uint8Array)) { throw new TypeError("Expected Uint8Array"); }
|
if (!(source instanceof Uint8Array)) {
|
||||||
if (source.length === 0) { return ""; }
|
throw new TypeError("Expected Uint8Array");
|
||||||
|
}
|
||||||
|
if (source.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
// Skip & count leading zeroes.
|
// Skip & count leading zeroes.
|
||||||
let zeroes = 0;
|
let zeroes = 0;
|
||||||
let length = 0;
|
let length = 0;
|
||||||
@ -129,12 +153,18 @@ goog.scope(function() {
|
|||||||
let carry = source[pbegin];
|
let carry = source[pbegin];
|
||||||
// Apply "b58 = b58 * 256 + ch".
|
// Apply "b58 = b58 * 256 + ch".
|
||||||
let i = 0;
|
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;
|
carry += (256 * b58[it1]) >>> 0;
|
||||||
b58[it1] = (carry % BASE) >>> 0;
|
b58[it1] = carry % BASE >>> 0;
|
||||||
carry = (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;
|
length = i;
|
||||||
pbegin++;
|
pbegin++;
|
||||||
}
|
}
|
||||||
@ -145,13 +175,19 @@ goog.scope(function() {
|
|||||||
}
|
}
|
||||||
// Translate the result into a string.
|
// Translate the result into a string.
|
||||||
let str = LEADER.repeat(zeroes);
|
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;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeUnsafe (source) {
|
function decodeUnsafe(source) {
|
||||||
if (typeof source !== "string") { throw new TypeError("Expected String"); }
|
if (typeof source !== "string") {
|
||||||
if (source.length === 0) { return new Uint8Array(); }
|
throw new TypeError("Expected String");
|
||||||
|
}
|
||||||
|
if (source.length === 0) {
|
||||||
|
return new Uint8Array();
|
||||||
|
}
|
||||||
let psz = 0;
|
let psz = 0;
|
||||||
// Skip and count leading '1's.
|
// Skip and count leading '1's.
|
||||||
let zeroes = 0;
|
let zeroes = 0;
|
||||||
@ -161,21 +197,29 @@ goog.scope(function() {
|
|||||||
psz++;
|
psz++;
|
||||||
}
|
}
|
||||||
// Allocate enough space in big-endian base256 representation.
|
// 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);
|
let b256 = new Uint8Array(size);
|
||||||
// Process the characters.
|
// Process the characters.
|
||||||
while (source[psz]) {
|
while (source[psz]) {
|
||||||
// Decode character
|
// Decode character
|
||||||
let carry = BASE_MAP[source.charCodeAt(psz)];
|
let carry = BASE_MAP[source.charCodeAt(psz)];
|
||||||
// Invalid character
|
// Invalid character
|
||||||
if (carry === 255) { return; }
|
if (carry === 255) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let i = 0;
|
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;
|
carry += (BASE * b256[it3]) >>> 0;
|
||||||
b256[it3] = (carry % 256) >>> 0;
|
b256[it3] = carry % 256 >>> 0;
|
||||||
carry = (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;
|
length = i;
|
||||||
psz++;
|
psz++;
|
||||||
}
|
}
|
||||||
@ -192,20 +236,22 @@ goog.scope(function() {
|
|||||||
return vch;
|
return vch;
|
||||||
}
|
}
|
||||||
|
|
||||||
function decode (string) {
|
function decode(string) {
|
||||||
let buffer = decodeUnsafe(string);
|
let buffer = decodeUnsafe(string);
|
||||||
if (buffer) { return buffer; }
|
if (buffer) {
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
throw new Error("Non-base" + BASE + " character");
|
throw new Error("Non-base" + BASE + " character");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
encode: encode,
|
encode: encode,
|
||||||
decodeUnsafe: decodeUnsafe,
|
decodeUnsafe: decodeUnsafe,
|
||||||
decode: decode
|
decode: decode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// MORE bases here: https://github.com/cryptocoinjs/base-x/tree/master
|
// MORE bases here: https://github.com/cryptocoinjs/base-x/tree/master
|
||||||
const BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
const BASE62 =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
self.bufferToBase62 = getBaseCodec(BASE62).encode;
|
self.bufferToBase62 = getBaseCodec(BASE62).encode;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
goog.provide("app.common.svg.path.arc_to_bezier");
|
goog.provide("app.common.svg.path.arc_to_bezier");
|
||||||
|
|
||||||
// https://raw.githubusercontent.com/fontello/svgpath/master/lib/a2c.js
|
// 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;
|
const self = app.common.svg.path.arc_to_bezier;
|
||||||
|
|
||||||
var TAU = Math.PI * 2;
|
var TAU = Math.PI * 2;
|
||||||
@ -27,20 +27,23 @@ goog.scope(function() {
|
|||||||
// we can use simplified math (without length normalization)
|
// we can use simplified math (without length normalization)
|
||||||
//
|
//
|
||||||
function unit_vector_angle(ux, uy, vx, vy) {
|
function unit_vector_angle(ux, uy, vx, vy) {
|
||||||
var sign = (ux * vy - uy * vx < 0) ? -1 : 1;
|
var sign = ux * vy - uy * vx < 0 ? -1 : 1;
|
||||||
var dot = ux * vx + uy * vy;
|
var dot = ux * vx + uy * vy;
|
||||||
|
|
||||||
// Add this to work with arbitrary vectors:
|
// Add this to work with arbitrary vectors:
|
||||||
// dot /= Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy);
|
// dot /= Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy);
|
||||||
|
|
||||||
// rounding errors, e.g. -1.0000000000000002 can screw up this
|
// rounding errors, e.g. -1.0000000000000002 can screw up this
|
||||||
if (dot > 1.0) { dot = 1.0; }
|
if (dot > 1.0) {
|
||||||
if (dot < -1.0) { dot = -1.0; }
|
dot = 1.0;
|
||||||
|
}
|
||||||
|
if (dot < -1.0) {
|
||||||
|
dot = -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
return sign * Math.acos(dot);
|
return sign * Math.acos(dot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Convert from endpoint to center parameterization,
|
// Convert from endpoint to center parameterization,
|
||||||
// see http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
|
// 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
|
// points. After that, rotate it to line up ellipse axes with coordinate
|
||||||
// axes.
|
// axes.
|
||||||
//
|
//
|
||||||
var x1p = cos_phi*(x1-x2)/2 + sin_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 y1p = (-sin_phi * (x1 - x2)) / 2 + (cos_phi * (y1 - y2)) / 2;
|
||||||
|
|
||||||
var rx_sq = rx * rx;
|
var rx_sq = rx * rx;
|
||||||
var ry_sq = ry * ry;
|
var ry_sq = ry * ry;
|
||||||
var x1p_sq = x1p * x1p;
|
var x1p_sq = x1p * x1p;
|
||||||
var y1p_sq = y1p * y1p;
|
var y1p_sq = y1p * y1p;
|
||||||
|
|
||||||
@ -66,33 +69,33 @@ goog.scope(function() {
|
|||||||
// Compute coordinates of the centre of this ellipse (cx', cy')
|
// Compute coordinates of the centre of this ellipse (cx', cy')
|
||||||
// in the new coordinate system.
|
// 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) {
|
if (radicant < 0) {
|
||||||
// due to rounding errors it might be e.g. -1.3877787807814457e-17
|
// due to rounding errors it might be e.g. -1.3877787807814457e-17
|
||||||
radicant = 0;
|
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);
|
radicant = Math.sqrt(radicant) * (fa === fs ? -1 : 1);
|
||||||
|
|
||||||
var cxp = radicant * rx/ry * y1p;
|
var cxp = ((radicant * rx) / ry) * y1p;
|
||||||
var cyp = radicant * -ry/rx * x1p;
|
var cyp = ((radicant * -ry) / rx) * x1p;
|
||||||
|
|
||||||
// Step 3.
|
// Step 3.
|
||||||
//
|
//
|
||||||
// Transform back to get centre coordinates (cx, cy) in the original
|
// Transform back to get centre coordinates (cx, cy) in the original
|
||||||
// coordinate system.
|
// coordinate system.
|
||||||
//
|
//
|
||||||
var cx = cos_phi*cxp - sin_phi*cyp + (x1+x2)/2;
|
var cx = cos_phi * cxp - sin_phi * cyp + (x1 + x2) / 2;
|
||||||
var cy = sin_phi*cxp + cos_phi*cyp + (y1+y2)/2;
|
var cy = sin_phi * cxp + cos_phi * cyp + (y1 + y2) / 2;
|
||||||
|
|
||||||
// Step 4.
|
// Step 4.
|
||||||
//
|
//
|
||||||
// Compute angles (theta1, delta_theta).
|
// Compute angles (theta1, delta_theta).
|
||||||
//
|
//
|
||||||
var v1x = (x1p - cxp) / rx;
|
var v1x = (x1p - cxp) / rx;
|
||||||
var v1y = (y1p - cyp) / ry;
|
var v1y = (y1p - cyp) / ry;
|
||||||
var v2x = (-x1p - cxp) / rx;
|
var v2x = (-x1p - cxp) / rx;
|
||||||
var v2y = (-y1p - cyp) / ry;
|
var v2y = (-y1p - cyp) / ry;
|
||||||
|
|
||||||
@ -106,7 +109,7 @@ goog.scope(function() {
|
|||||||
delta_theta += TAU;
|
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
|
// see http://math.stackexchange.com/questions/873224
|
||||||
//
|
//
|
||||||
function approximate_unit_arc(theta1, delta_theta) {
|
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 x1 = Math.cos(theta1);
|
||||||
var y1 = Math.sin(theta1);
|
var y1 = Math.sin(theta1);
|
||||||
var x2 = Math.cos(theta1 + delta_theta);
|
var x2 = Math.cos(theta1 + delta_theta);
|
||||||
var y2 = Math.sin(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) {
|
function calculate_beziers(x1, y1, x2, y2, fa, fs, rx, ry, phi) {
|
||||||
var sin_phi = Math.sin(phi * TAU / 360);
|
var sin_phi = Math.sin((phi * TAU) / 360);
|
||||||
var cos_phi = Math.cos(phi * TAU / 360);
|
var cos_phi = Math.cos((phi * TAU) / 360);
|
||||||
|
|
||||||
// Make sure radii are valid
|
// Make sure radii are valid
|
||||||
//
|
//
|
||||||
var x1p = cos_phi*(x1-x2)/2 + sin_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 y1p = (-sin_phi * (x1 - x2)) / 2 + (cos_phi * (y1 - y2)) / 2;
|
||||||
|
|
||||||
// console.log("L", x1p, y1p)
|
// console.log("L", x1p, y1p)
|
||||||
|
|
||||||
@ -145,7 +157,6 @@ goog.scope(function() {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Compensate out-of-range radii
|
// Compensate out-of-range radii
|
||||||
//
|
//
|
||||||
rx = Math.abs(rx);
|
rx = Math.abs(rx);
|
||||||
@ -157,25 +168,20 @@ goog.scope(function() {
|
|||||||
ry *= Math.sqrt(lambda);
|
ry *= Math.sqrt(lambda);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Get center parameters (cx, cy, theta1, delta_theta)
|
// 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 cc = get_arc_center(x1, y1, x2, y2, fa, fs, rx, ry, sin_phi, cos_phi);
|
||||||
|
|
||||||
|
|
||||||
var result = [];
|
var result = [];
|
||||||
var theta1 = cc[2];
|
var theta1 = cc[2];
|
||||||
var delta_theta = cc[3];
|
var delta_theta = cc[3];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Split an arc to multiple segments, so each segment
|
// Split an arc to multiple segments, so each segment
|
||||||
// will be less than τ/4 (= 90°)
|
// will be less than τ/4 (= 90°)
|
||||||
//
|
//
|
||||||
var segments = Math.max(Math.ceil(Math.abs(delta_theta) / (TAU / 4)), 1);
|
var segments = Math.max(Math.ceil(Math.abs(delta_theta) / (TAU / 4)), 1);
|
||||||
delta_theta /= segments;
|
delta_theta /= segments;
|
||||||
|
|
||||||
|
|
||||||
for (var i = 0; i < segments; i++) {
|
for (var i = 0; i < segments; i++) {
|
||||||
var item = approximate_unit_arc(theta1, delta_theta);
|
var item = approximate_unit_arc(theta1, delta_theta);
|
||||||
result.push(item);
|
result.push(item);
|
||||||
@ -195,8 +201,8 @@ goog.scope(function() {
|
|||||||
y *= ry;
|
y *= ry;
|
||||||
|
|
||||||
// rotate
|
// rotate
|
||||||
var xp = cos_phi*x - sin_phi*y;
|
var xp = cos_phi * x - sin_phi * y;
|
||||||
var yp = sin_phi*x + cos_phi*y;
|
var yp = sin_phi * x + cos_phi * y;
|
||||||
|
|
||||||
// translate
|
// translate
|
||||||
curve[i + 0] = xp + cc[0];
|
curve[i + 0] = xp + cc[0];
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -10,16 +10,18 @@
|
|||||||
goog.require("app.common.encoding_impl");
|
goog.require("app.common.encoding_impl");
|
||||||
goog.provide("app.common.uuid_impl");
|
goog.provide("app.common.uuid_impl");
|
||||||
|
|
||||||
goog.scope(function() {
|
goog.scope(function () {
|
||||||
const global = goog.global;
|
const global = goog.global;
|
||||||
const encoding = app.common.encoding_impl;
|
const encoding = app.common.encoding_impl;
|
||||||
const self = app.common.uuid_impl;
|
const self = app.common.uuid_impl;
|
||||||
|
|
||||||
const timeRef = 1640995200000; // ms since 2022-01-01T00:00:00
|
const timeRef = 1640995200000; // ms since 2022-01-01T00:00:00
|
||||||
|
|
||||||
const fill = (() => {
|
const fill = (() => {
|
||||||
if (typeof global.crypto !== "undefined" &&
|
if (
|
||||||
typeof global.crypto.getRandomValues !== "undefined") {
|
typeof global.crypto !== "undefined" &&
|
||||||
|
typeof global.crypto.getRandomValues !== "undefined"
|
||||||
|
) {
|
||||||
return (buf) => {
|
return (buf) => {
|
||||||
global.crypto.getRandomValues(buf);
|
global.crypto.getRandomValues(buf);
|
||||||
return buf;
|
return buf;
|
||||||
@ -30,7 +32,7 @@ goog.scope(function() {
|
|||||||
|
|
||||||
return (buf) => {
|
return (buf) => {
|
||||||
const bytes = randomBytes(buf.length);
|
const bytes = randomBytes(buf.length);
|
||||||
buf.set(bytes)
|
buf.set(bytes);
|
||||||
return buf;
|
return buf;
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@ -39,8 +41,10 @@ goog.scope(function() {
|
|||||||
|
|
||||||
return (buf) => {
|
return (buf) => {
|
||||||
for (let i = 0, r; i < buf.length; i++) {
|
for (let i = 0, r; i < buf.length; i++) {
|
||||||
if ((i & 0x03) === 0) { r = Math.random() * 0x100000000; }
|
if ((i & 0x03) === 0) {
|
||||||
buf[i] = r >>> ((i & 0x03) << 3) & 0xff;
|
r = Math.random() * 0x100000000;
|
||||||
|
}
|
||||||
|
buf[i] = (r >>> ((i & 0x03) << 3)) & 0xff;
|
||||||
}
|
}
|
||||||
return buf;
|
return buf;
|
||||||
};
|
};
|
||||||
@ -50,31 +54,38 @@ goog.scope(function() {
|
|||||||
function toHexString(buf) {
|
function toHexString(buf) {
|
||||||
const hexMap = encoding.hexMap;
|
const hexMap = encoding.hexMap;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
return (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++]] +
|
||||||
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) {
|
function getBigUint64(view, byteOffset, le) {
|
||||||
const a = view.getUint32(byteOffset, le);
|
const a = view.getUint32(byteOffset, le);
|
||||||
const b = view.getUint32(byteOffset + 4, le);
|
const b = view.getUint32(byteOffset + 4, le);
|
||||||
const leMask = Number(!!le);
|
const leMask = Number(!!le);
|
||||||
const beMask = Number(!le);
|
const beMask = Number(!le);
|
||||||
return ((BigInt(a * beMask + b * leMask) << 32n) |
|
return (
|
||||||
(BigInt(a * leMask + b * beMask)));
|
(BigInt(a * beMask + b * leMask) << 32n) | BigInt(a * leMask + b * beMask)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBigUint64(view, byteOffset, value, le) {
|
function setBigUint64(view, byteOffset, value, le) {
|
||||||
@ -83,8 +94,7 @@ goog.scope(function() {
|
|||||||
if (le) {
|
if (le) {
|
||||||
view.setUint32(byteOffset + 4, hi, le);
|
view.setUint32(byteOffset + 4, hi, le);
|
||||||
view.setUint32(byteOffset, lo, le);
|
view.setUint32(byteOffset, lo, le);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
view.setUint32(byteOffset, hi, le);
|
view.setUint32(byteOffset, hi, le);
|
||||||
view.setUint32(byteOffset + 4, lo, le);
|
view.setUint32(byteOffset + 4, lo, le);
|
||||||
}
|
}
|
||||||
@ -104,17 +114,18 @@ goog.scope(function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.shortID = (function () {
|
self.shortID = (function () {
|
||||||
const buff = new ArrayBuffer(8);
|
const buff = new ArrayBuffer(8);
|
||||||
const int8 = new Uint8Array(buff);
|
const int8 = new Uint8Array(buff);
|
||||||
const view = new DataView(buff);
|
const view = new DataView(buff);
|
||||||
|
|
||||||
const base = 0x0000_0000_0000_0000n;
|
const base = 0x0000_0000_0000_0000n;
|
||||||
|
|
||||||
return function shortID(ts) {
|
return function shortID(ts) {
|
||||||
const tss = currentTimestamp(timeRef);
|
const tss = currentTimestamp(timeRef);
|
||||||
const msb = (base
|
const msb =
|
||||||
| (nextLong() & 0xffff_ffff_0000_0000n)
|
base |
|
||||||
| (tss & 0x0000_0000_ffff_ffffn));
|
(nextLong() & 0xffff_ffff_0000_0000n) |
|
||||||
|
(tss & 0x0000_0000_ffff_ffffn);
|
||||||
setBigUint64(view, 0, msb, false);
|
setBigUint64(view, 0, msb, false);
|
||||||
return encoding.toBase62(int8);
|
return encoding.toBase62(int8);
|
||||||
};
|
};
|
||||||
@ -139,9 +150,9 @@ goog.scope(function() {
|
|||||||
const maxCs = 0x0000_0000_0000_3fffn; // 14 bits space
|
const maxCs = 0x0000_0000_0000_3fffn; // 14 bits space
|
||||||
|
|
||||||
let countCs = 0n;
|
let countCs = 0n;
|
||||||
let lastRd = 0n;
|
let lastRd = 0n;
|
||||||
let lastCs = 0n;
|
let lastCs = 0n;
|
||||||
let lastTs = 0n;
|
let lastTs = 0n;
|
||||||
let baseMsb = 0x0000_0000_0000_8000n;
|
let baseMsb = 0x0000_0000_0000_8000n;
|
||||||
let baseLsb = 0x8000_0000_0000_0000n;
|
let baseLsb = 0x8000_0000_0000_0000n;
|
||||||
|
|
||||||
@ -149,12 +160,9 @@ goog.scope(function() {
|
|||||||
lastCs = nextLong() & maxCs;
|
lastCs = nextLong() & maxCs;
|
||||||
|
|
||||||
const create = function create(ts, lastRd, lastCs) {
|
const create = function create(ts, lastRd, lastCs) {
|
||||||
const msb = (baseMsb
|
const msb = baseMsb | (lastRd & 0xffff_ffff_ffff_0fffn);
|
||||||
| (lastRd & 0xffff_ffff_ffff_0fffn));
|
|
||||||
|
|
||||||
const lsb = (baseLsb
|
const lsb = baseLsb | ((ts << 14n) & 0x3fff_ffff_ffff_c000n) | lastCs;
|
||||||
| ((ts << 14n) & 0x3fff_ffff_ffff_c000n)
|
|
||||||
| lastCs);
|
|
||||||
|
|
||||||
setBigUint64(view, 0, msb, false);
|
setBigUint64(view, 0, msb, false);
|
||||||
setBigUint64(view, 8, lsb, false);
|
setBigUint64(view, 8, lsb, false);
|
||||||
@ -167,10 +175,10 @@ goog.scope(function() {
|
|||||||
let ts = currentTimestamp(timeRef);
|
let ts = currentTimestamp(timeRef);
|
||||||
|
|
||||||
// Protect from clock regression
|
// Protect from clock regression
|
||||||
if ((ts - lastTs) < 0) {
|
if (ts - lastTs < 0) {
|
||||||
lastRd = (lastRd
|
lastRd =
|
||||||
& 0x0000_0000_0000_0f00n
|
(lastRd & 0x0000_0000_0000_0f00n) |
|
||||||
| (nextLong() & 0xffff_ffff_ffff_f0ffn));
|
(nextLong() & 0xffff_ffff_ffff_f0ffn);
|
||||||
countCs = 0n;
|
countCs = 0n;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -209,63 +217,63 @@ goog.scope(function() {
|
|||||||
|
|
||||||
// Parse ........-....-....-####-............
|
// Parse ........-....-....-####-............
|
||||||
int8[8] = (rest = parseInt(uuid.slice(19, 23), 16)) >>> 8;
|
int8[8] = (rest = parseInt(uuid.slice(19, 23), 16)) >>> 8;
|
||||||
int8[9] = rest & 0xff,
|
(int8[9] = rest & 0xff),
|
||||||
|
// Parse ........-....-....-....-############
|
||||||
// Parse ........-....-....-....-############
|
// (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes)
|
||||||
// (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes)
|
(int8[10] =
|
||||||
int8[10] = ((rest = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff;
|
((rest = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff);
|
||||||
int8[11] = (rest / 0x100000000) & 0xff;
|
int8[11] = (rest / 0x100000000) & 0xff;
|
||||||
int8[12] = (rest >>> 24) & 0xff;
|
int8[12] = (rest >>> 24) & 0xff;
|
||||||
int8[13] = (rest >>> 16) & 0xff;
|
int8[13] = (rest >>> 16) & 0xff;
|
||||||
int8[14] = (rest >>> 8) & 0xff;
|
int8[14] = (rest >>> 8) & 0xff;
|
||||||
int8[15] = rest & 0xff;
|
int8[15] = rest & 0xff;
|
||||||
}
|
};
|
||||||
|
|
||||||
const fromPair = (hi, lo) => {
|
const fromPair = (hi, lo) => {
|
||||||
view.setBigInt64(0, hi);
|
view.setBigInt64(0, hi);
|
||||||
view.setBigInt64(8, lo);
|
view.setBigInt64(8, lo);
|
||||||
return encoding.bufferToHex(int8, true);
|
return encoding.bufferToHex(int8, true);
|
||||||
}
|
};
|
||||||
|
|
||||||
const getHi = (uuid) => {
|
const getHi = (uuid) => {
|
||||||
fillBytes(uuid);
|
fillBytes(uuid);
|
||||||
return view.getBigInt64(0);
|
return view.getBigInt64(0);
|
||||||
}
|
};
|
||||||
|
|
||||||
const getLo = (uuid) => {
|
const getLo = (uuid) => {
|
||||||
fillBytes(uuid);
|
fillBytes(uuid);
|
||||||
return view.getBigInt64(8);
|
return view.getBigInt64(8);
|
||||||
}
|
};
|
||||||
|
|
||||||
const getBytes = (uuid) => {
|
const getBytes = (uuid) => {
|
||||||
fillBytes(uuid);
|
fillBytes(uuid);
|
||||||
return Int8Array.from(int8);
|
return Int8Array.from(int8);
|
||||||
}
|
};
|
||||||
|
|
||||||
const getUnsignedParts = (uuid) => {
|
const getUnsignedParts = (uuid) => {
|
||||||
fillBytes(uuid);
|
fillBytes(uuid);
|
||||||
const result = new Uint32Array(4);
|
const result = new Uint32Array(4);
|
||||||
|
|
||||||
result[0] = view.getUint32(0)
|
result[0] = view.getUint32(0);
|
||||||
result[1] = view.getUint32(4);
|
result[1] = view.getUint32(4);
|
||||||
result[2] = view.getUint32(8);
|
result[2] = view.getUint32(8);
|
||||||
result[3] = view.getUint32(12);
|
result[3] = view.getUint32(12);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
};
|
||||||
|
|
||||||
const fromUnsignedParts = (a, b, c, d) => {
|
const fromUnsignedParts = (a, b, c, d) => {
|
||||||
view.setUint32(0, a)
|
view.setUint32(0, a);
|
||||||
view.setUint32(4, b)
|
view.setUint32(4, b);
|
||||||
view.setUint32(8, c)
|
view.setUint32(8, c);
|
||||||
view.setUint32(12, d)
|
view.setUint32(12, d);
|
||||||
return encoding.bufferToHex(int8, true);
|
return encoding.bufferToHex(int8, true);
|
||||||
}
|
};
|
||||||
|
|
||||||
const fromArray = (u8data) => {
|
const fromArray = (u8data) => {
|
||||||
int8.set(u8data);
|
int8.set(u8data);
|
||||||
return encoding.bufferToHex(int8, true);
|
return encoding.bufferToHex(int8, true);
|
||||||
}
|
};
|
||||||
|
|
||||||
const setTag = (tag) => {
|
const setTag = (tag) => {
|
||||||
tag = BigInt.asUintN(64, "" + tag);
|
tag = BigInt.asUintN(64, "" + tag);
|
||||||
@ -273,9 +281,9 @@ goog.scope(function() {
|
|||||||
throw new Error("illegal arguments: tag value should fit in 4bits");
|
throw new Error("illegal arguments: tag value should fit in 4bits");
|
||||||
}
|
}
|
||||||
|
|
||||||
lastRd = (lastRd
|
lastRd =
|
||||||
& 0xffff_ffff_ffff_f0ffn
|
(lastRd & 0xffff_ffff_ffff_f0ffn) |
|
||||||
| ((tag << 8) & 0x0000_0000_0000_0f00n));
|
((tag << 8) & 0x0000_0000_0000_0f00n);
|
||||||
};
|
};
|
||||||
|
|
||||||
factory.create = create;
|
factory.create = create;
|
||||||
@ -290,9 +298,9 @@ goog.scope(function() {
|
|||||||
return factory;
|
return factory;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
self.shortV8 = function(uuid) {
|
self.shortV8 = function (uuid) {
|
||||||
const buff = encoding.hexToBuffer(uuid);
|
const buff = encoding.hexToBuffer(uuid);
|
||||||
const short = new Uint8Array(buff, 4);
|
const short = new Uint8Array(buff, 4);
|
||||||
return encoding.bufferToBase62(short);
|
return encoding.bufferToBase62(short);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -307,7 +315,7 @@ goog.scope(function() {
|
|||||||
return self.v8.fromPair(hi, lo);
|
return self.v8.fromPair(hi, lo);
|
||||||
};
|
};
|
||||||
|
|
||||||
self.fromBytes = function(data) {
|
self.fromBytes = function (data) {
|
||||||
if (data instanceof Uint8Array) {
|
if (data instanceof Uint8Array) {
|
||||||
return self.v8.fromArray(data);
|
return self.v8.fromArray(data);
|
||||||
} else if (data instanceof Int8Array) {
|
} else if (data instanceof Int8Array) {
|
||||||
@ -325,15 +333,15 @@ goog.scope(function() {
|
|||||||
return self.v8.getUnsignedParts(uuid);
|
return self.v8.getUnsignedParts(uuid);
|
||||||
};
|
};
|
||||||
|
|
||||||
self.fromUnsignedParts = function(a,b,c,d) {
|
self.fromUnsignedParts = function (a, b, c, d) {
|
||||||
return self.v8.fromUnsignedParts(a,b,c,d);
|
return self.v8.fromUnsignedParts(a, b, c, d);
|
||||||
};
|
};
|
||||||
|
|
||||||
self.getHi = function (uuid) {
|
self.getHi = function (uuid) {
|
||||||
return self.v8.getHi(uuid);
|
return self.v8.getHi(uuid);
|
||||||
}
|
};
|
||||||
|
|
||||||
self.getLo = function (uuid) {
|
self.getLo = function (uuid) {
|
||||||
return self.v8.getLo(uuid);
|
return self.v8.getLo(uuid);
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -67,8 +67,11 @@ export class WeakEqMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set(key, value) {
|
set(key, value) {
|
||||||
if (key === null || (typeof key !== 'object' && typeof key !== 'function')) {
|
if (
|
||||||
throw new TypeError('WeakEqMap keys must be objects (like WeakMap).');
|
key === null ||
|
||||||
|
(typeof key !== "object" && typeof key !== "function")
|
||||||
|
) {
|
||||||
|
throw new TypeError("WeakEqMap keys must be objects (like WeakMap).");
|
||||||
}
|
}
|
||||||
const hash = this._hash(key);
|
const hash = this._hash(key);
|
||||||
const bucket = this._getBucket(hash);
|
const bucket = this._getBucket(hash);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
#kaocha/v1
|
#kaocha/v1
|
||||||
{:tests [{:id :unit
|
{:tests [{:id :unit
|
||||||
:test-paths ["test"]}]
|
:test-paths ["test"]}]
|
||||||
:kaocha/reporter [kaocha.report/dots]}
|
:kaocha/reporter [kaocha.report/dots]}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ RUN set -ex; \
|
|||||||
curl \
|
curl \
|
||||||
bash \
|
bash \
|
||||||
git \
|
git \
|
||||||
|
ripgrep \
|
||||||
\
|
\
|
||||||
curl \
|
curl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
|||||||
@ -34,7 +34,8 @@
|
|||||||
"watch": "pnpm run watch:app",
|
"watch": "pnpm run watch:app",
|
||||||
"build:app": "clojure -M:dev:shadow-cljs release main",
|
"build:app": "clojure -M:dev:shadow-cljs release main",
|
||||||
"build": "pnpm run clear:shadow-cache && pnpm run build:app",
|
"build": "pnpm run clear:shadow-cache && pnpm run build:app",
|
||||||
"fmt:clj": "cljfmt fix --parallel=true src/",
|
"fmt": "cljfmt fix --parallel=true src/",
|
||||||
"lint:clj": "cljfmt check --parallel src/ && clj-kondo --parallel --lint src/"
|
"check-fmt": "cljfmt check --parallel=true src/",
|
||||||
|
"lint": "clj-kondo --parallel --lint src/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,12 +23,15 @@
|
|||||||
"build:app:main": "clojure -M:dev:shadow-cljs release main worker",
|
"build:app:main": "clojure -M:dev:shadow-cljs release main worker",
|
||||||
"build:app:worker": "clojure -M:dev:shadow-cljs release 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",
|
"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: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: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",
|
"fmt:scss": "prettier -c resources/styles -c src/**/*.scss -w",
|
||||||
"lint:clj": "cljfmt check --parallel=false src/ test/ && clj-kondo --parallel --lint src/",
|
"lint:clj": "clj-kondo --parallel --lint ../common/src src/",
|
||||||
"lint:js": "prettier -c src/**/*.stories.jsx -c playwright/**/*.js -c scripts/**/*.js text-editor/**/*.js",
|
"lint:js": "exit 0",
|
||||||
"lint:scss": "prettier -c resources/styles -c src/**/*.scss",
|
"lint:scss": "exit 0",
|
||||||
"build:test": "clojure -M:dev:shadow-cljs compile test",
|
"build:test": "clojure -M:dev:shadow-cljs compile test",
|
||||||
"test": "pnpm run build:test && node target/tests/test.js",
|
"test": "pnpm run build:test && node target/tests/test.js",
|
||||||
"test:storybook": "vitest run --project=storybook",
|
"test:storybook": "vitest run --project=storybook",
|
||||||
|
|||||||
@ -26,8 +26,9 @@
|
|||||||
"clear:shadow-cache": "rm -rf .shadow-cljs",
|
"clear:shadow-cache": "rm -rf .shadow-cljs",
|
||||||
"build": "pnpm run clear:shadow-cache && clojure -M:dev:shadow-cljs release library",
|
"build": "pnpm run clear:shadow-cache && clojure -M:dev:shadow-cljs release library",
|
||||||
"build:bundle": "./scripts/build",
|
"build:bundle": "./scripts/build",
|
||||||
"fmt:clj": "cljfmt fix --parallel=true src/ test/",
|
"fmt": "cljfmt fix --parallel=true src/ test/",
|
||||||
"lint:clj": "cljfmt check --parallel=false src/ test/ && clj-kondo --parallel --lint src/",
|
"check-fmt": "cljfmt check --parallel=true src/ test/",
|
||||||
|
"lint": "clj-kondo --parallel --lint src/",
|
||||||
"test": "node --test",
|
"test": "node --test",
|
||||||
"watch:test": "node --test --watch",
|
"watch:test": "node --test --watch",
|
||||||
"watch": "pnpm run clear:shadow-cache && clojure -M:dev:shadow-cljs watch library"
|
"watch": "pnpm run clear:shadow-cache && clojure -M:dev:shadow-cljs watch library"
|
||||||
|
|||||||
13
scripts/check-fmt
Executable file
13
scripts/check-fmt
Executable file
@ -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;
|
||||||
10
scripts/lint
10
scripts/lint
@ -2,16 +2,6 @@
|
|||||||
|
|
||||||
set -ex
|
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 common/src;
|
||||||
clj-kondo --parallel=true --lint frontend/src;
|
clj-kondo --parallel=true --lint frontend/src;
|
||||||
clj-kondo --parallel=true --lint backend/src;
|
clj-kondo --parallel=true --lint backend/src;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user