Compare commits

..

1 Commits

Author SHA1 Message Date
Elena Torro
369979ffe6 WIP 2025-11-26 18:12:35 +01:00
116 changed files with 2780 additions and 3370 deletions

305
.circleci/config.yml Normal file
View File

@@ -0,0 +1,305 @@
version: 2.1
jobs:
lint:
docker:
- image: penpotapp/devenv:latest
working_directory: ~/repo
resource_class: medium+
steps:
- checkout
- run:
name: "fmt check"
working_directory: "."
command: |
yarn install
yarn run fmt:clj:check
- run:
name: "lint clj common"
working_directory: "."
command: |
yarn run lint:clj:common
- run:
name: "lint clj frontend"
working_directory: "."
command: |
yarn run lint:clj:frontend
- run:
name: "lint clj backend"
working_directory: "."
command: |
yarn run lint:clj:backend
- run:
name: "lint clj exporter"
working_directory: "."
command: |
yarn run lint:clj:exporter
- run:
name: "lint clj library"
working_directory: "."
command: |
yarn run lint:clj:library
test-common:
docker:
- image: penpotapp/devenv:latest
working_directory: ~/repo
resource_class: medium+
environment:
JAVA_OPTS: -Xmx4g -Xms100m -XX:+UseSerialGC
NODE_OPTIONS: --max-old-space-size=4096
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "common/deps.edn"}}-{{ checksum "common/yarn.lock" }}
- run:
name: "JVM tests"
working_directory: "./common"
command: |
clojure -M:dev:test
- run:
name: "NODE tests"
working_directory: "./common"
command: |
yarn install
yarn run test
- save_cache:
paths:
- ~/.m2
- ~/.yarn
- ~/.gitlibs
- ~/.cache/ms-playwright
key: v1-dependencies-{{ checksum "common/deps.edn"}}-{{ checksum "common/yarn.lock" }}
test-frontend:
docker:
- image: penpotapp/devenv:latest
working_directory: ~/repo
resource_class: medium+
environment:
JAVA_OPTS: -Xmx4g -Xms100m -XX:+UseSerialGC
NODE_OPTIONS: --max-old-space-size=4096
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "frontend/deps.edn"}}-{{ checksum "frontend/yarn.lock" }}
- run:
name: "install dependencies"
working_directory: "./frontend"
# We install playwright here because the dependent tasks
# uses the same cache as this task so we prepopulate it
command: |
yarn install
yarn run playwright install chromium --with-deps
- run:
name: "lint scss on frontend"
working_directory: "./frontend"
command: |
yarn run lint:scss
- run:
name: "unit tests"
working_directory: "./frontend"
command: |
yarn run test
- save_cache:
paths:
- ~/.m2
- ~/.yarn
- ~/.gitlibs
- ~/.cache/ms-playwright
key: v1-dependencies-{{ checksum "frontend/deps.edn"}}-{{ checksum "frontend/yarn.lock" }}
test-library:
docker:
- image: penpotapp/devenv:latest
working_directory: ~/repo
resource_class: medium+
environment:
JAVA_OPTS: -Xmx6g
NODE_OPTIONS: --max-old-space-size=4096
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "frontend/deps.edn"}}-{{ checksum "frontend/yarn.lock" }}
- run:
name: Install dependencies and build
working_directory: "./library"
command: |
yarn install
- run:
name: Build and Test
working_directory: "./library"
command: |
./scripts/build
yarn run test
test-components:
docker:
- image: penpotapp/devenv:latest
working_directory: ~/repo
resource_class: medium+
environment:
JAVA_OPTS: -Xmx6g -Xms2g
NODE_OPTIONS: --max-old-space-size=4096
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "frontend/deps.edn"}}-{{ checksum "frontend/yarn.lock" }}
- run:
name: Install dependencies
working_directory: "./frontend"
command: |
yarn install
yarn run playwright install chromium
- run:
name: Build Storybook
working_directory: "./frontend"
command: yarn run build:storybook
- run:
name: Serve Storybook and run tests
working_directory: "./frontend"
command: |
npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
"npx http-server storybook-static --port 6006 --silent" \
"npx wait-on tcp:6006 && yarn test:storybook"
test-backend:
docker:
- image: penpotapp/devenv:latest
- image: cimg/postgres:14.5
environment:
POSTGRES_USER: penpot_test
POSTGRES_PASSWORD: penpot_test
POSTGRES_DB: penpot_test
- image: cimg/redis:7.0.5
working_directory: ~/repo
resource_class: medium+
environment:
JAVA_OPTS: -Xmx4g -Xms100m -XX:+UseSerialGC
NODE_OPTIONS: --max-old-space-size=4096
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "backend/deps.edn" }}
- run:
name: "tests"
working_directory: "./backend"
command: |
clojure -M:dev:test --reporter kaocha.report/documentation
environment:
PENPOT_TEST_DATABASE_URI: "postgresql://localhost/penpot_test"
PENPOT_TEST_DATABASE_USERNAME: penpot_test
PENPOT_TEST_DATABASE_PASSWORD: penpot_test
PENPOT_TEST_REDIS_URI: "redis://localhost/1"
- save_cache:
paths:
- ~/.m2
- ~/.gitlibs
key: v1-dependencies-{{ checksum "backend/deps.edn" }}
test-render-wasm:
docker:
- image: penpotapp/devenv:latest
working_directory: ~/repo
resource_class: medium+
environment:
steps:
- checkout
- run:
name: "fmt check"
working_directory: "./render-wasm"
command: |
cargo fmt --check
- run:
name: "lint"
working_directory: "./render-wasm"
command: |
./lint
- run:
name: "cargo tests"
working_directory: "./render-wasm"
command: |
./test
workflows:
penpot:
jobs:
- test-frontend:
requires:
- lint: success
- test-library:
requires:
- lint: success
- test-components:
requires:
- lint: success
- test-backend:
requires:
- lint: success
- test-common:
requires:
- lint: success
- lint
- test-render-wasm

View File

@@ -21,21 +21,6 @@ jobs:
with:
gh_ref: ${{ github.ref_name }}
notify:
name: Notifications
needs: build-docker
steps:
- name: Notify Mattermost
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
MATTERMOST_CHANNEL: bot-alerts-cicd
TEXT: |
🐳 *[PENPOT] Docker image available.*
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@infra
publish-final-tag:
if: ${{ !contains(github.ref_name, '-RC') && !contains(github.ref_name, '-alpha') && !contains(github.ref_name, '-beta') && contains(github.ref_name, '.') }}
needs: build-docker

View File

@@ -8,6 +8,8 @@ on:
pull_request:
types:
- opened
- edited
- reopened
- synchronize
push:
branches:
@@ -15,12 +17,12 @@ on:
- staging
concurrency:
group: ${{ github.event.pull_request.number || github.ref }}
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: "Linter"
name: "Code Linter"
runs-on: ubuntu-24.04
container: penpotapp/devenv:latest
@@ -30,7 +32,10 @@ jobs:
- name: Check clojure code format
run: |
./scripts/lint
corepack enable;
corepack install;
yarn install
yarn run fmt:clj:check
test-common:
name: "Common Tests"
@@ -49,7 +54,10 @@ jobs:
- name: Run tests on NODE
working-directory: ./common
run: |
./scripts/test
corepack enable;
corepack install;
yarn install;
yarn run test;
test-frontend:
name: "Frontend Tests"
@@ -63,36 +71,25 @@ jobs:
- name: Unit Tests
working-directory: ./frontend
run: |
./scripts/test
corepack enable;
corepack install;
yarn install;
yarn run test;
- name: Component Tests
working-directory: ./frontend
run: |
./scripts/test-components
yarn run playwright install chromium --with-deps;
yarn run build:storybook
test-render-wasm:
name: "Render WASM Tests"
runs-on: ubuntu-24.04
container: penpotapp/devenv:latest
npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
"npx http-server storybook-static --port 6006 --silent" \
"npx wait-on tcp:6006 && yarn test:storybook"
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Format
working-directory: ./render-wasm
- name: Check SCSS Format
working-directory: ./frontend
run: |
cargo fmt --check
- name: Lint
working-directory: ./render-wasm
run: |
./lint
- name: Test
working-directory: ./render-wasm
run: |
./test
yarn run lint:scss;
test-backend:
name: "Backend Tests"
@@ -145,7 +142,11 @@ jobs:
- name: Run tests
working-directory: ./library
run: |
./scripts/test
corepack enable;
corepack install;
yarn install;
yarn run build:bundle;
yarn run test;
build-integration:
name: "Build Integration Bundle"
@@ -159,7 +160,17 @@ jobs:
- name: Build Bundle
working-directory: ./frontend
run: |
./scripts/build 0.0.0
corepack enable;
corepack install;
yarn install
yarn run build:app:assets
yarn run build:app
yarn run build:app:libs
- name: Build WASM
working-directory: "./render-wasm"
run: |
./build release
- name: Store Bundle Cache
uses: actions/cache@v4
@@ -167,7 +178,6 @@ jobs:
key: "integration-bundle-${{ github.sha }}"
path: frontend/resources/public
test-integration-1:
name: "Integration Tests 1/4"
runs-on: ubuntu-24.04
@@ -187,7 +197,11 @@ jobs:
- name: Run Tests
working-directory: ./frontend
run: |
./scripts/test-e2e --shard="1/4";
corepack enable;
corepack install;
yarn install;
yarn run playwright install chromium --with-deps;
yarn run test:e2e -x --workers=2 --reporter=list --shard="1/4";
- name: Upload test result
uses: actions/upload-artifact@v4
@@ -217,7 +231,11 @@ jobs:
- name: Run Tests
working-directory: ./frontend
run: |
./scripts/test-e2e --shard="2/4";
corepack enable;
corepack install;
yarn install;
yarn run playwright install chromium --with-deps;
yarn run test:e2e -x --workers=2 --reporter=list --shard "2/4";
- name: Upload test result
uses: actions/upload-artifact@v4
@@ -247,7 +265,11 @@ jobs:
- name: Run Tests
working-directory: ./frontend
run: |
./scripts/test-e2e --shard="3/4";
corepack enable;
corepack install;
yarn install;
yarn run playwright install chromium --with-deps;
yarn run test:e2e -x --workers=2 --reporter=list --shard "3/4";
- name: Upload test result
uses: actions/upload-artifact@v4
@@ -259,7 +281,7 @@ jobs:
retention-days: 3
test-integration-4:
name: "Integration Tests 4/4"
name: "Integration Tests 3/4"
runs-on: ubuntu-24.04
container: penpotapp/devenv:latest
needs: build-integration
@@ -277,7 +299,11 @@ jobs:
- name: Run Tests
working-directory: ./frontend
run: |
./scripts/test-e2e --shard="4/4";
corepack enable;
corepack install;
yarn install;
yarn run playwright install chromium --with-deps;
yarn run test:e2e -x --workers=2 --reporter=list --shard "4/4";
- name: Upload test result
uses: actions/upload-artifact@v4

1
.gitignore vendored
View File

@@ -80,4 +80,3 @@ node_modules
/playwright/.cache/
/render-wasm/target/
/**/.yarn/*
/.pnpm-store

View File

@@ -1,18 +1,5 @@
# CHANGELOG
## 2.13.0 (Unreleased)
### :boom: Breaking changes & Deprecations
### :rocket: Epics and highlights
### :heart: Community contributions (Thank you!)
### :sparkles: New features & Enhancements
### :bug: Bugs fixed
## 2.12.0 (Unreleased)
### :boom: Breaking changes & Deprecations

View File

@@ -1,8 +1,7 @@
From: {{profile.fullname}} <{{profile.email}}> / {{profile.id}}
Subject: {{feedback-subject}}
Type: {{feedback-type}}
{% if feedback-error-href %}
{%- if feedback-error-href %}
HREF: {{feedback-error-href}}
{% endif -%}

View File

@@ -57,7 +57,7 @@
:uid uuid/zero})
body (t/encode {:events events})
headers {"content-type" "application/transit+json"
"origin" (str (cf/get :public-uri))
"origin" (cf/get :public-uri)
"cookie" (u/map->query-string {:auth-token token})}
params {:uri uri
:timeout 12000

View File

@@ -9,7 +9,7 @@
[app.common.exceptions :as ex]
[selmer.parser :as sp]))
;; (sp/cache-off!)
(sp/cache-off!)
(defn render
[path context]

View File

@@ -318,35 +318,3 @@
;; check that we have all no objects
(let [rows (th/db-exec! ["select * from storage_object where deleted_at is null"])]
(t/is (= 0 (count rows))))))
(t/deftest tempfile-bucket-test
(let [storage (-> (:app.storage/storage th/*system*)
(configure-storage-backend))
content1 (sto/content "content1")
now (ct/now)
object1 (sto/put-object! storage {::sto/content content1
::sto/touched-at (ct/plus now {:minutes 1})
:bucket "tempfile"
:content-type "text/plain"})]
(binding [ct/*clock* (clock/fixed now)]
(let [res (th/run-task! :storage-gc-touched {})]
(t/is (= 0 (:freeze res)))
(t/is (= 0 (:delete res)))))
(binding [ct/*clock* (clock/fixed (ct/plus now {:minutes 1}))]
(let [res (th/run-task! :storage-gc-touched {})]
(t/is (= 0 (:freeze res)))
(t/is (= 1 (:delete res)))))
(binding [ct/*clock* (clock/fixed (ct/plus now {:hours 1}))]
(let [res (th/run-task! :storage-gc-deleted {})]
(t/is (= 0 (:deleted res)))))
(binding [ct/*clock* (clock/fixed (ct/plus now {:hours 2}))]
(let [res (th/run-task! :storage-gc-deleted {})]
(t/is (= 0 (:deleted res)))))))

View File

@@ -17,7 +17,7 @@
org.slf4j/slf4j-api {:mvn/version "2.0.17"}
pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.40"}
selmer/selmer {:mvn/version "1.12.69"}
selmer/selmer {:mvn/version "1.12.62"}
criterium/criterium {:mvn/version "0.4.6"}
metosin/jsonista {:mvn/version "0.3.13"}
@@ -29,6 +29,7 @@
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
integrant/integrant {:mvn/version "1.0.0"}
funcool/tubax {:mvn/version "2021.05.20-0"}
funcool/cuerdas {:mvn/version "2025.06.16-414"}
funcool/promesa
{:git/sha "46048fc0d4bf5466a2a4121f5d52aefa6337f2e8"
@@ -47,8 +48,12 @@
com.sun.mail/jakarta.mail {:mvn/version "2.0.2"}
org.la4j/la4j {:mvn/version "0.6.0"}
;; exception printing
fipp/fipp {:mvn/version "0.6.29"}
me.flowthing/pp {:mvn/version "2024-11-13.77"}
io.aviso/pretty {:mvn/version "1.4.4"}
environ/environ {:mvn/version "1.2.0"}}
:paths ["src" "vendor" "target/classes"]

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env bash
set -ex
corepack enable;
corepack install;
yarn install;
yarn run test;

View File

@@ -9,10 +9,10 @@
(:refer-clojure :exclude [get-in select-keys str with-open max])
#?(:cljs (:require-macros [app.common.data.macros]))
(:require
#?(:clj [cljs.analyzer.api :as aapi])
#?(:clj [clojure.core :as c]
:cljs [cljs.core :as c])
[app.common.data :as d]
[cljs.analyzer.api :as aapi]
[cuerdas.core :as str]))
(defmacro select-keys
@@ -44,43 +44,42 @@
[& params]
`(str/concat ~@params))
#?(:clj
(defmacro export
"A helper macro that allows reexport a var in a current namespace."
[v]
(if (boolean (:ns &env))
(defmacro export
"A helper macro that allows reexport a var in a current namespace."
[v]
(if (boolean (:ns &env))
;; Code for ClojureScript
(let [mdata (aapi/resolve &env v)
arglists (second (get-in mdata [:meta :arglists]))
sym (symbol (c/name v))
andsym (symbol "&")
procarg #(if (= % andsym) % (gensym "param"))]
(if (pos? (count arglists))
`(def
~(with-meta sym (:meta mdata))
(fn ~@(for [args arglists]
(let [args (map procarg args)]
(if (some #(= andsym %) args)
(let [[sargs dargs] (split-with #(not= andsym %) args)]
`([~@sargs ~@dargs] (apply ~v ~@sargs ~@(rest dargs))))
`([~@args] (~v ~@args)))))))
`(def ~(with-meta sym (:meta mdata)) ~v)))
;; Code for ClojureScript
(let [mdata (aapi/resolve &env v)
arglists (second (get-in mdata [:meta :arglists]))
sym (symbol (c/name v))
andsym (symbol "&")
procarg #(if (= % andsym) % (gensym "param"))]
(if (pos? (count arglists))
`(def
~(with-meta sym (:meta mdata))
(fn ~@(for [args arglists]
(let [args (map procarg args)]
(if (some #(= andsym %) args)
(let [[sargs dargs] (split-with #(not= andsym %) args)]
`([~@sargs ~@dargs] (apply ~v ~@sargs ~@(rest dargs))))
`([~@args] (~v ~@args)))))))
`(def ~(with-meta sym (:meta mdata)) ~v)))
;; Code for Clojure
(let [vr (resolve v)
m (meta vr)
n (:name m)
n (with-meta n
(cond-> {}
(:dynamic m) (assoc :dynamic true)
(:protocol m) (assoc :protocol (:protocol m))))]
`(let [m# (meta ~vr)]
(def ~n (deref ~vr))
(alter-meta! (var ~n) merge (dissoc m# :name))
;; (when (:macro m#)
;; (.setMacro (var ~n)))
~vr)))))
;; Code for Clojure
(let [vr (resolve v)
m (meta vr)
n (:name m)
n (with-meta n
(cond-> {}
(:dynamic m) (assoc :dynamic true)
(:protocol m) (assoc :protocol (:protocol m))))]
`(let [m# (meta ~vr)]
(def ~n (deref ~vr))
(alter-meta! (var ~n) merge (dissoc m# :name))
;; (when (:macro m#)
;; (.setMacro (var ~n)))
~vr))))
(defmacro fmt
"String interpolation helper. Can only be used with strings known at

View File

@@ -14,7 +14,8 @@
[app.common.schema :as sm]
[clojure.core :as c]
[clojure.spec.alpha :as s]
[cuerdas.core :as str])
[cuerdas.core :as str]
[expound.alpha :as expound])
#?(:clj
(:import
clojure.lang.IPersistentMap)))
@@ -109,6 +110,13 @@
(contains? data :explain))
(explain (:explain data) opts)
(and (contains? data ::s/problems)
(contains? data ::s/value)
(contains? data ::s/spec))
(binding [s/*explain-out* expound/printer]
(with-out-str
(s/explain-out (update data ::s/problems #(take (:length opts 10) %)))))
(contains? data ::sm/explain)
(sm/humanize-explain (::sm/explain data) opts)))

View File

@@ -43,6 +43,8 @@
"
#?(:cljs (:require-macros [app.common.logging :as l]))
(:require
#?(:clj [clojure.edn :as edn]
:cljs [cljs.reader :as edn])
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.pprint :as pp]

View File

@@ -18,6 +18,7 @@
[app.common.logic.shapes :as cls]
[app.common.logic.variant-properties :as clvp]
[app.common.path-names :as cpn]
[app.common.spec :as us]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn]
@@ -34,7 +35,8 @@
[app.common.types.typography :as cty]
[app.common.types.variant :as ctv]
[app.common.uuid :as uuid]
[clojure.set :as set]))
[clojure.set :as set]
[clojure.spec.alpha :as s]))
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
(log/set-level! :warn)
@@ -471,10 +473,10 @@
If an asset id is given, only shapes linked to this particular asset will
be synchronized."
[changes file-id asset-type asset-id library-id libraries current-file-id]
(assert (contains? #{:colors :components :typographies} asset-type))
(assert (or (nil? asset-id) (uuid? asset-id)))
(assert (uuid? file-id))
(assert (uuid? library-id))
(s/assert #{:colors :components :typographies} asset-type)
(s/assert (s/nilable ::us/uuid) asset-id)
(s/assert ::us/uuid file-id)
(s/assert ::us/uuid library-id)
(container-log :info asset-id
:msg "Sync file with library"
@@ -508,10 +510,10 @@
If an asset id is given, only shapes linked to this particular asset will
be synchronized."
[changes file-id asset-type asset-id library-id libraries current-file-id]
(assert (contains? #{:colors :components :typographies} asset-type))
(assert (or (nil? asset-id) (uuid? asset-id)))
(assert (uuid? file-id))
(assert (uuid? library-id))
(s/assert #{:colors :components :typographies} asset-type)
(s/assert (s/nilable ::us/uuid) asset-id)
(s/assert ::us/uuid file-id)
(s/assert ::us/uuid library-id)
(container-log :info asset-id
:msg "Sync local components with library"

View File

@@ -8,8 +8,6 @@
(:refer-clojure :exclude [deref merge parse-uuid parse-long parse-double parse-boolean type keys])
#?(:cljs (:require-macros [app.common.schema :refer [ignoring]]))
(:require
#?(:clj [malli.dev.pretty :as mdp])
#?(:clj [malli.dev.virhe :as v])
[app.common.data :as d]
[app.common.math :as mth]
[app.common.pprint :as pp]
@@ -21,6 +19,8 @@
[clojure.core :as c]
[cuerdas.core :as str]
[malli.core :as m]
[malli.dev.pretty :as mdp]
[malli.dev.virhe :as v]
[malli.error :as me]
[malli.generator :as mg]
[malli.registry :as mr]
@@ -245,30 +245,27 @@
:level (d/nilv level 8)
:length (d/nilv length 12)})))))
#?(:clj
(defmethod v/-format ::schemaless-explain
[_ explanation printer]
{:body [:group
(v/-block "Value" (v/-visit (me/error-value explanation printer) printer) printer) :break :break
(v/-block "Errors" (v/-visit (me/humanize (me/with-spell-checking explanation)) printer) printer)]}))
(defmethod v/-format ::schemaless-explain
[_ explanation printer]
{:body [:group
(v/-block "Value" (v/-visit (me/error-value explanation printer) printer) printer) :break :break
(v/-block "Errors" (v/-visit (me/humanize (me/with-spell-checking explanation)) printer) printer)]})
#?(:clj
(defmethod v/-format ::explain
[_ {:keys [schema] :as explanation} printer]
{:body [:group
(v/-block "Value" (v/-visit (me/error-value explanation printer) printer) printer) :break :break
(v/-block "Errors" (v/-visit (me/humanize (me/with-spell-checking explanation)) printer) printer) :break :break
(v/-block "Schema" (v/-visit schema printer) printer)]}))
(defmethod v/-format ::explain
[_ {:keys [schema] :as explanation} printer]
{:body [:group
(v/-block "Value" (v/-visit (me/error-value explanation printer) printer) printer) :break :break
(v/-block "Errors" (v/-visit (me/humanize (me/with-spell-checking explanation)) printer) printer) :break :break
(v/-block "Schema" (v/-visit schema printer) printer)]})
#?(:clj
(defn pretty-explain
"A helper that allows print a console-friendly output for the explain;
should not be used for other purposes"
[explain & {:keys [variant message]
:or {variant ::explain
message "Validation Error"}}]
(let [explain (fn [] (me/with-error-messages explain))]
((mdp/prettifier variant message explain default-options)))))
(defn pretty-explain
"A helper that allows print a console-friendly output for the
explain; should not be used for other purposes"
[explain & {:keys [variant message]
:or {variant ::explain
message "Validation Error"}}]
(let [explain (fn [] (me/with-error-messages explain))]
((mdp/prettifier variant message explain default-options))))
(defmacro ignoring
[expr]
@@ -302,13 +299,6 @@
::explain explain}))))
value))))
(defn coercer
[schema & {:as opts}]
(let [decode-fn (decoder schema json-transformer)
check-fn (check-fn schema opts)]
(fn [data]
(-> data decode-fn check-fn))))
(defn check
"A helper intended to be used on assertions for validate/check the
schema over provided data. Raises an assertion exception.
@@ -852,6 +842,38 @@
choices))]
{:pred pred}))})
;; (register!
;; {:type ::inst
;; :pred tm/instant?
;; :type-properties
;; {:title "inst"
;; :description "Satisfies Inst protocol"
;; :error/message "should be an instant"
;; :gen/gen (->> (sg/small-int :min 0 :max 100000)
;; (sg/fmap (fn [v] (tm/parse-inst v))))
;; :decode/string tm/parse-inst
;; :encode/string tm/format-inst
;; :decode/json tm/parse-inst
;; :encode/json tm/format-inst
;; ::oapi/type "string"
;; ::oapi/format "iso"}})
;; (register!
;; {:type ::timestamp
;; :pred tm/instant?
;; :type-properties
;; {:title "inst"
;; :description "Satisfies Inst protocol, the same as ::inst but encodes to epoch"
;; :error/message "should be an instant"
;; :gen/gen (->> (sg/small-int)
;; (sg/fmap (fn [v] (tm/parse-inst v))))
;; :decode/string tm/parse-inst
;; :encode/string inst-ms
;; :decode/json tm/parse-inst
;; :encode/json inst-ms
;; ::oapi/type "string"
;; ::oapi/format "number"}})
#?(:clj
(register!
@@ -929,7 +951,7 @@
:pred #(and (string? %) (not (str/blank? %)))
:property-pred
(fn [{:keys [min max] :as props}]
(if (or min max)
(if (seq props)
(fn [value]
(let [size (count value)]
(cond
@@ -1003,9 +1025,6 @@
(def valid-safe-number?
(lazy-validator ::safe-number))
(def valid-safe-int?
(lazy-validator ::safe-int))
(def valid-text?
(validator ::text))

View File

@@ -25,6 +25,48 @@ RUN set -ex; \
binutils \
build-essential autoconf libtool pkg-config
################################################################################
## IMAGE MAGICK
################################################################################
FROM base AS build-imagemagick
ENV IMAGEMAGICK_VERSION=7.1.1-47 \
DEBIAN_FRONTEND=noninteractive
RUN set -ex; \
apt-get -qq update; \
apt-get -qq upgrade; \
apt-get -qqy --no-install-recommends install \
libltdl-dev \
libpng-dev \
libjpeg-dev \
libtiff-dev \
libwebp-dev \
libopenexr-dev \
libfftw3-dev \
libzip-dev \
liblcms2-dev \
liblzma-dev \
libzstd-dev \
libheif-dev \
librsvg2-dev \
; \
rm -rf /var/lib/apt/lists/*
RUN set -eux; \
curl -LfsSo /tmp/magick.tar.gz https://github.com/ImageMagick/ImageMagick/archive/refs/tags/${IMAGEMAGICK_VERSION}.tar.gz; \
mkdir -p /tmp/magick; \
cd /tmp/magick; \
tar -xf /tmp/magick.tar.gz --strip-components=1; \
./configure --prefix=/opt/imagick; \
make -j 2; \
make install; \
rm -rf /opt/imagick/lib/libMagick++*; \
rm -rf /opt/imagick/include; \
rm -rf /opt/imagick/share;
################################################################################
## NODE SETUP
################################################################################
@@ -65,7 +107,7 @@ RUN set -eux; \
FROM base AS setup-jvm
ENV CLOJURE_VERSION=1.12.3.1577
ENV CLOJURE_VERSION=1.12.2.1565
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \
@@ -343,7 +385,7 @@ ENV LANG='C.UTF-8' \
RUSTUP_HOME="/opt/rustup" \
PATH="/opt/jdk/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH"
COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick
COPY --from=build-imagemagick /opt/imagick /opt/imagick
COPY --from=setup-jvm /opt/jdk /opt/jdk
COPY --from=setup-jvm /opt/clojure /opt/clojure
COPY --from=setup-node /opt/node /opt/node

View File

@@ -67,11 +67,6 @@ services:
- PENPOT_LDAP_ATTRS_FULLNAME=cn
- PENPOT_LDAP_ATTRS_PHOTO=jpegPhoto
networks:
default:
aliases:
- main
minio:
image: "minio/minio:RELEASE.2025-04-03T14-56-28Z"
command: minio server /mnt/data --console-address ":9001"
@@ -83,6 +78,10 @@ services:
- MINIO_ROOT_USER=minioadmin
- MINIO_ROOT_PASSWORD=minioadmin
ports:
- 9000:9000
- 9001:9001
networks:
default:
aliases:

View File

@@ -12,7 +12,7 @@ http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 0;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
@@ -223,6 +223,16 @@ http {
add_header X-Cache-Status $upstream_cache_status;
}
location ~ ^/js/config.js$ {
add_header Cache-Control "no-store, no-cache, max-age=0" always;
}
location ~* \.(js|css|jpg|svg|png|mjs|map)$ {
# We set no cache only on devenv
add_header Cache-Control "no-store, no-cache, max-age=0" always;
# add_header Cache-Control "max-age=604800" always; # 7 days
}
location ~ ^/(/|css|fonts|images|js|wasm|mjs|map) {
}
@@ -230,10 +240,9 @@ http {
return 301 " /404";
}
add_header Cache-Control "no-store";
add_header Connection close always;
# This header is what we need to use on prod
# add_header Cache-Control "public, must-revalidate, max-age=0";
add_header Last-Modified $date_gmt;
add_header Cache-Control "no-store, no-cache, max-age=0" always;
if_modified_since off;
try_files $uri /index.html$is_args$args /index.html =404;
}
}

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -107,25 +107,6 @@ desc: Streamline your design workflow with Penpot's Components guide! Learn to c
<li>Select the variant copy, press right-click, and select the menu option <strong>Restore variant</strong> (will show if the main component still exists).</li>
</ul>
<h3 id="component-variants-toggle">Toggle for boolean variants</h3>
<p>When a variant property represents a boolean state, Penpot can display it as a toggle instead of a dropdown. This offers a quicker and more visual way to switch between two opposite values when working with copies.</p>
<p>The toggle appears in place of the property values dropdown, <strong>only when a copy is selected</strong>.</p>
<figure>
<img src="/img/variants/07-variants-boolean.webp" alt="Boolean variant option" />
</figure>
<h4>Accepted value pairs</h4>
<p>For Penpot to recognize the property as a boolean and display the toggle, the property must be defined with exactly two opposing values. These can be any of the following pairs:</p>
<ul>
<li><code>true</code> / <code>false</code></li>
<li><code>on</code> / <code>off</code></li>
<li><code>yes</code> / <code>no</code></li>
</ul>
<p>The order of the values does not matter. Penpot automatically maps them to ON and OFF states:</p>
<ul>
<li><strong>ON state:</strong> <code>true</code>, <code>yes</code>, <code>on</code></li>
<li><strong>OFF state:</strong> <code>false</code>, <code>no</code>, <code>off</code></li>
</ul>
<h3 id="component-use-variants">Use variants</h3>
<p>Once you have created your variants, you can place a copy of a component with variants into your design and then switch between its different versions.</p>

View File

@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8",
"packageManager": "yarn@4.10.3+sha512.c38cafb5c7bb273f3926d04e55e1d8c9dfa7d9c3ea1f36a4868fa028b9e5f72298f0b7f401ad5eb921749eb012eb1c3bb74bf7503df3ee43fd600d14a018266f",
"repository": {
"type": "git",
"url": "https://github.com/penpot/penpot"
@@ -16,9 +16,9 @@
"date-fns": "^4.1.0",
"generic-pool": "^3.9.0",
"inflation": "^2.1.0",
"ioredis": "^5.8.2",
"playwright": "^1.57.0",
"raw-body": "^3.0.2",
"ioredis": "^5.8.1",
"playwright": "^1.55.1",
"raw-body": "^3.0.1",
"source-map-support": "^0.5.21",
"svgo": "penpot/svgo#v3.1",
"xml-js": "^1.6.11",

View File

@@ -243,7 +243,7 @@ __metadata:
languageName: node
linkType: hard
"bytes@npm:~3.1.2":
"bytes@npm:3.1.2":
version: 3.1.2
resolution: "bytes@npm:3.1.2"
checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e
@@ -442,7 +442,7 @@ __metadata:
languageName: node
linkType: hard
"depd@npm:~2.0.0":
"depd@npm:2.0.0, depd@npm:~2.0.0":
version: 2.0.0
resolution: "depd@npm:2.0.0"
checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c
@@ -577,9 +577,9 @@ __metadata:
date-fns: "npm:^4.1.0"
generic-pool: "npm:^3.9.0"
inflation: "npm:^2.1.0"
ioredis: "npm:^5.8.2"
playwright: "npm:^1.57.0"
raw-body: "npm:^3.0.2"
ioredis: "npm:^5.8.1"
playwright: "npm:^1.55.1"
raw-body: "npm:^3.0.1"
source-map-support: "npm:^0.5.21"
svgo: "penpot/svgo#v3.1"
ws: "npm:^8.18.3"
@@ -682,16 +682,16 @@ __metadata:
languageName: node
linkType: hard
"http-errors@npm:~2.0.1":
version: 2.0.1
resolution: "http-errors@npm:2.0.1"
"http-errors@npm:2.0.0":
version: 2.0.0
resolution: "http-errors@npm:2.0.0"
dependencies:
depd: "npm:~2.0.0"
inherits: "npm:~2.0.4"
setprototypeof: "npm:~1.2.0"
statuses: "npm:~2.0.2"
toidentifier: "npm:~1.0.1"
checksum: 10c0/fb38906cef4f5c83952d97661fe14dc156cb59fe54812a42cd448fa57b5c5dfcb38a40a916957737bd6b87aab257c0648d63eb5b6a9ca9f548e105b6072712d4
depd: "npm:2.0.0"
inherits: "npm:2.0.4"
setprototypeof: "npm:1.2.0"
statuses: "npm:2.0.1"
toidentifier: "npm:1.0.1"
checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19
languageName: node
linkType: hard
@@ -715,6 +715,15 @@ __metadata:
languageName: node
linkType: hard
"iconv-lite@npm:0.7.0":
version: 0.7.0
resolution: "iconv-lite@npm:0.7.0"
dependencies:
safer-buffer: "npm:>= 2.1.2 < 3.0.0"
checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f
languageName: node
linkType: hard
"iconv-lite@npm:^0.6.2":
version: 0.6.3
resolution: "iconv-lite@npm:0.6.3"
@@ -724,15 +733,6 @@ __metadata:
languageName: node
linkType: hard
"iconv-lite@npm:~0.7.0":
version: 0.7.0
resolution: "iconv-lite@npm:0.7.0"
dependencies:
safer-buffer: "npm:>= 2.1.2 < 3.0.0"
checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f
languageName: node
linkType: hard
"ieee754@npm:^1.2.1":
version: 1.2.1
resolution: "ieee754@npm:1.2.1"
@@ -754,16 +754,16 @@ __metadata:
languageName: node
linkType: hard
"inherits@npm:~2.0.3, inherits@npm:~2.0.4":
"inherits@npm:2.0.4, inherits@npm:~2.0.3":
version: 2.0.4
resolution: "inherits@npm:2.0.4"
checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2
languageName: node
linkType: hard
"ioredis@npm:^5.8.2":
version: 5.8.2
resolution: "ioredis@npm:5.8.2"
"ioredis@npm:^5.8.1":
version: 5.8.1
resolution: "ioredis@npm:5.8.1"
dependencies:
"@ioredis/commands": "npm:1.4.0"
cluster-key-slot: "npm:^1.1.0"
@@ -774,7 +774,7 @@ __metadata:
redis-errors: "npm:^1.2.0"
redis-parser: "npm:^3.0.0"
standard-as-callback: "npm:^2.1.0"
checksum: 10c0/305e385f811d49908899e32c2de69616cd059f909afd9e0a53e54f596b1a5835ee3449bfc6a3c49afbc5a2fd27990059e316cc78f449c94024957bd34c826d88
checksum: 10c0/4ed66444017150da027bce940a24bf726994691e2a7b3aa11d52f8aeb37f258068cc171af4d9c61247acafc28eb086fa8a7c79420b8e8d2907d2f74f39584465
languageName: node
linkType: hard
@@ -1105,27 +1105,27 @@ __metadata:
languageName: node
linkType: hard
"playwright-core@npm:1.57.0":
version: 1.57.0
resolution: "playwright-core@npm:1.57.0"
"playwright-core@npm:1.55.1":
version: 1.55.1
resolution: "playwright-core@npm:1.55.1"
bin:
playwright-core: cli.js
checksum: 10c0/798e35d83bf48419a8c73de20bb94d68be5dde68de23f95d80a0ebe401e3b83e29e3e84aea7894d67fa6c79d2d3d40cc5bcde3e166f657ce50987aaa2421b6a9
checksum: 10c0/39837a8c1232ec27486eac8c3fcacc0b090acc64310f7f9004b06715370fc426f944e3610fe8c29f17cd3d68280ed72c75f660c02aa5b5cf0eb34bab0031308f
languageName: node
linkType: hard
"playwright@npm:^1.57.0":
version: 1.57.0
resolution: "playwright@npm:1.57.0"
"playwright@npm:^1.55.1":
version: 1.55.1
resolution: "playwright@npm:1.55.1"
dependencies:
fsevents: "npm:2.3.2"
playwright-core: "npm:1.57.0"
playwright-core: "npm:1.55.1"
dependenciesMeta:
fsevents:
optional: true
bin:
playwright: cli.js
checksum: 10c0/ab03c99a67b835bdea9059f516ad3b6e42c21025f9adaa161a4ef6bc7ca716dcba476d287140bb240d06126eb23f889a8933b8f5f1f1a56b80659d92d1358899
checksum: 10c0/b84a97b0d764403df512f5bbb10c7343974e151a28202cc06f90883a13e8a45f4491a0597f0ae5fb03a026746cbc0d200f0f32195bfaa381aee5ca5770626771
languageName: node
linkType: hard
@@ -1160,15 +1160,15 @@ __metadata:
languageName: node
linkType: hard
"raw-body@npm:^3.0.2":
version: 3.0.2
resolution: "raw-body@npm:3.0.2"
"raw-body@npm:^3.0.1":
version: 3.0.1
resolution: "raw-body@npm:3.0.1"
dependencies:
bytes: "npm:~3.1.2"
http-errors: "npm:~2.0.1"
iconv-lite: "npm:~0.7.0"
unpipe: "npm:~1.0.0"
checksum: 10c0/d266678d08e1e7abea62c0ce5864344e980fa81c64f6b481e9842c5beaed2cdcf975f658a3ccd67ad35fc919c1f6664ccc106067801850286a6cbe101de89f29
bytes: "npm:3.1.2"
http-errors: "npm:2.0.0"
iconv-lite: "npm:0.7.0"
unpipe: "npm:1.0.0"
checksum: 10c0/892f4fbd21ecab7e2fed0f045f7af9e16df7e8050879639d4e482784a2f4640aaaa33d916a0e98013f23acb82e09c2e3c57f84ab97104449f728d22f65a7d79a
languageName: node
linkType: hard
@@ -1269,7 +1269,7 @@ __metadata:
languageName: node
linkType: hard
"setprototypeof@npm:~1.2.0":
"setprototypeof@npm:1.2.0":
version: 1.2.0
resolution: "setprototypeof@npm:1.2.0"
checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc
@@ -1367,10 +1367,10 @@ __metadata:
languageName: node
linkType: hard
"statuses@npm:~2.0.2":
version: 2.0.2
resolution: "statuses@npm:2.0.2"
checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f
"statuses@npm:2.0.1":
version: 2.0.1
resolution: "statuses@npm:2.0.1"
checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0
languageName: node
linkType: hard
@@ -1499,7 +1499,7 @@ __metadata:
languageName: node
linkType: hard
"toidentifier@npm:~1.0.1":
"toidentifier@npm:1.0.1":
version: 1.0.1
resolution: "toidentifier@npm:1.0.1"
checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1
@@ -1531,7 +1531,7 @@ __metadata:
languageName: node
linkType: hard
"unpipe@npm:~1.0.0":
"unpipe@npm:1.0.0":
version: 1.0.0
resolution: "unpipe@npm:1.0.0"
checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c

View File

@@ -0,0 +1,18 @@
diff --git a/lib/zip-fs.js b/lib/zip-fs.js
index 1444c0f00e5f1ad6c13521f90a7f3c6659d81116..90e38baef5365c2abbcb9337f7ab37f800e883a4 100644
--- a/lib/zip-fs.js
+++ b/lib/zip-fs.js
@@ -33,12 +33,7 @@ import { initShimAsyncCodec } from "./core/util/stream-codec-shim.js";
import { terminateWorkers } from "./core/codec-pool.js";
let baseURL;
-try {
- baseURL = import.meta.url;
- // eslint-disable-next-line no-unused-vars
-} catch (_) {
- // ignored
-}
+
configure({ baseURL });
configureWebWorker(configure);

View File

@@ -1,17 +0,0 @@
diff --git a/lib/zip-core-base.js b/lib/zip-core-base.js
index 142155c8c3ab1a6caa7c370e8a2931a954556ca9..61b0fe6efb91312f437750e0002218f218afad8b 100644
--- a/lib/zip-core-base.js
+++ b/lib/zip-core-base.js
@@ -28,12 +28,6 @@
import { configure } from "./core/configuration.js";
-try {
- configure({ baseURI: import.meta.url });
-} catch {
- // ignored
-}
-
export * from "./zip-core-reader.js";
export * from "./zip-core-writer.js";
export {

View File

@@ -8,11 +8,6 @@
metosin/reitit-core {:mvn/version "0.9.1"}
funcool/okulary {:mvn/version "2022.04.11-16"}
funcool/tubax
{:git/tag "v2025.11.28"
:git/sha "2d9a986"
:git/url "https://github.com/funcool/tubax.git"}
funcool/potok2
{:git/tag "v2.2"
:git/sha "0f7e15a"
@@ -25,8 +20,8 @@
:git/url "https://github.com/funcool/beicon.git"}
funcool/rumext
{:git/tag "v2.25"
:git/sha "27e5a1a"
{:git/tag "v2.24"
:git/sha "17a0c94"
:git/url "https://github.com/funcool/rumext.git"}
instaparse/instaparse {:mvn/version "1.5.0"}
@@ -47,7 +42,7 @@
:dev
{:extra-paths ["dev"]
:extra-deps
{thheller/shadow-cljs {:mvn/version "3.2.2"}
{thheller/shadow-cljs {:mvn/version "3.2.0"}
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "RELEASE"}

View File

@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8",
"packageManager": "yarn@4.10.3+sha512.c38cafb5c7bb273f3926d04e55e1d8c9dfa7d9c3ea1f36a4868fa028b9e5f72298f0b7f401ad5eb921749eb012eb1c3bb74bf7503df3ee43fd600d14a018266f",
"browserslist": [
"defaults"
],
@@ -14,10 +14,10 @@
"url": "https://github.com/penpot/penpot"
},
"resolutions": {
"@zip.js/zip.js@npm:^2.7.44": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
"@vitejs/plugin-react": "^4.2.0",
"playwright": "1.52.0",
"playwright-core": "1.52.0",
"@zip.js/zip.js@npm:^2.7.44": "patch:@zip.js/zip.js@npm%3A2.8.11#~/.yarn/patches/@zip.js-zip.js-npm-2.8.11-b131c96df8.patch"
"playwright-core": "1.52.0"
},
"scripts": {
"build:app:assets": "node ./scripts/build-app-assets.js",
@@ -27,7 +27,6 @@
"build:storybook:cljs": "clojure -M:dev:shadow-cljs compile storybook",
"build:app:libs": "node ./scripts/build-libs.js",
"build:app:main": "clojure -M:dev:shadow-cljs release main worker",
"build:app:worker": "clojure -M:dev:shadow-cljs release worker",
"build:app": "yarn run clear:shadow-cache && yarn run build:app:main && yarn run build:app:libs",
"e2e:server": "node ./scripts/e2e-server.js",
"fmt:clj": "cljfmt fix --parallel=true src/ test/",
@@ -53,74 +52,83 @@
"watch:storybook:assets": "node ./scripts/watch-storybook.js"
},
"devDependencies": {
"@penpot/draft-js": "portal:./packages/draft-js",
"@penpot/mousetrap": "portal:./packages/mousetrap",
"@penpot/plugins-runtime": "1.3.2",
"@penpot/svgo": "penpot/svgo#v3.1",
"@penpot/text-editor": "portal:./text-editor",
"@playwright/test": "1.57.0",
"@storybook/addon-docs": "10.1.0",
"@storybook/addon-themes": "10.1.0",
"@storybook/addon-vitest": "10.1.0",
"@storybook/react-vite": "10.1.0",
"@tokens-studio/sd-transforms": "1.2.11",
"@types/node": "^22.19.1",
"@playwright/test": "1.52.0",
"@storybook/addon-docs": "10.0.4",
"@storybook/addon-themes": "10.0.4",
"@storybook/addon-vitest": "10.0.4",
"@storybook/react-vite": "10.0.4",
"@types/node": "^22.15.21",
"@vitest/browser": "3.2.4",
"@vitest/coverage-v8": "3.2.4",
"@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.8.11#~/.yarn/patches/@zip.js-zip.js-npm-2.8.11-b131c96df8.patch",
"autoprefixer": "^10.4.22",
"compression": "^1.8.1",
"autoprefixer": "^10.4.21",
"concurrently": "^9.2.1",
"date-fns": "^4.1.0",
"esbuild": "^0.25.12",
"eventsource-parser": "^3.0.6",
"esbuild": "^0.25.9",
"express": "^5.1.0",
"fancy-log": "^2.0.0",
"getopts": "^2.3.0",
"gettext-parser": "^8.0.0",
"highlight.js": "^11.10.0",
"js-beautify": "^1.15.4",
"jsdom": "^27.2.0",
"lodash": "^4.17.21",
"lodash.debounce": "^4.0.8",
"gulp-concat": "^2.6.1",
"gulp-gzip": "^1.4.2",
"gulp-mustache": "^5.0.0",
"gulp-postcss": "^10.0.0",
"gulp-rename": "^2.0.0",
"gulp-sourcemaps": "^3.0.0",
"gulp-svg-sprite": "^2.0.3",
"jsdom": "^27.0.0",
"map-stream": "0.0.7",
"marked": "^17.0.1",
"marked": "^15.0.12",
"mkdirp": "^3.0.1",
"mustache": "^4.2.0",
"nodemon": "^3.1.11",
"nodemon": "^3.1.10",
"npm-run-all": "^4.1.5",
"opentype.js": "^1.3.4",
"p-limit": "^7.2.0",
"playwright": "1.57.0",
"postcss": "^8.5.6",
"p-limit": "^6.2.0",
"playwright": "1.56.1",
"postcss": "^8.5.4",
"postcss-clean": "^1.2.2",
"postcss-modules": "^6.0.1",
"prettier": "3.7.1",
"prettier": "3.5.3",
"pretty-time": "^1.1.0",
"prop-types": "^15.8.1",
"randomcolor": "^0.6.2",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-error-boundary": "^6.0.0",
"react-virtualized": "^9.22.6",
"rimraf": "^6.1.2",
"rxjs": "8.0.0-alpha.14",
"rimraf": "^6.0.1",
"sass": "^1.89.0",
"sass-embedded": "^1.89.0",
"sax": "^1.4.3",
"source-map-support": "^0.5.21",
"storybook": "10.1.0",
"style-dictionary": "5.0.0-rc.1",
"storybook": "10.0.4",
"svg-sprite": "^2.0.4",
"tdigest": "^0.1.2",
"tinycolor2": "^1.6.0",
"typescript": "^5.9.3",
"ua-parser-js": "2.0.6",
"vite": "^6.4.1",
"vitest": "^3.2.4",
"typescript": "^5.9.2",
"vite": "^6.3.5",
"vitest": "^3.2.0",
"wasm-pack": "^0.13.1",
"watcher": "^2.3.1",
"workerpool": "^10.0.1",
"workerpool": "^9.3.2"
},
"dependencies": {
"@penpot/draft-js": "portal:./vendor/draft-js",
"@penpot/hljs": "portal:./vendor/hljs",
"@penpot/mousetrap": "portal:./vendor/mousetrap",
"@penpot/plugins-runtime": "1.3.2",
"@penpot/svgo": "penpot/svgo#v3.1",
"@penpot/text-editor": "portal:./text-editor",
"@tokens-studio/sd-transforms": "1.2.11",
"@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
"compression": "^1.8.1",
"date-fns": "^4.1.0",
"eventsource-parser": "^3.0.6",
"js-beautify": "^1.15.4",
"lodash": "^4.17.21",
"lodash.debounce": "^4.0.8",
"opentype.js": "^1.3.4",
"postcss-modules": "^6.0.1",
"randomcolor": "^0.6.2",
"react": "19.1.1",
"react-dom": "19.1.1",
"react-error-boundary": "^6.0.0",
"react-virtualized": "^9.22.6",
"rxjs": "8.0.0-alpha.14",
"sax": "^1.4.1",
"source-map-support": "^0.5.21",
"style-dictionary": "5.0.0-rc.1",
"tdigest": "^0.1.2",
"tinycolor2": "^1.6.0",
"ua-parser-js": "2.0.5",
"xregexp": "^5.1.2"
}
}

View File

@@ -1,17 +0,0 @@
diff --git a/lib/zip-core-base.js b/lib/zip-core-base.js
index 142155c8c3ab1a6caa7c370e8a2931a954556ca9..61b0fe6efb91312f437750e0002218f218afad8b 100644
--- a/lib/zip-core-base.js
+++ b/lib/zip-core-base.js
@@ -28,12 +28,6 @@
import { configure } from "./core/configuration.js";
-try {
- configure({ baseURI: import.meta.url });
-} catch {
- // ignored
-}
-
export * from "./zip-core-reader.js";
export * from "./zip-core-writer.js";
export {

View File

@@ -22,9 +22,9 @@ export default defineConfig({
workers: 1,
/* Timeout for expects (longer in CI) */
timeout: 80000,
timeout: 60000,
expect: {
timeout: process.env.CI ? 40000 : 5000,
timeout: process.env.CI ? 30000 : 5000,
},
/* Reporter to use. See https://playwright.dev/docs/test-reporters */

View File

File diff suppressed because it is too large Load Diff

View File

@@ -73,7 +73,7 @@ export class BasePage {
}
static async mockConfigFlags(page, flags) {
const url = "**/js/config.js";
const url = "**/js/config.js?ts=*";
return await page.route(url, (route) =>
route.fulfill({
status: 200,

View File

@@ -42,7 +42,7 @@ export class WasmWorkspacePage extends WorkspacePage {
}
async waitForFirstRenderWithoutUI() {
await this.waitForFirstRender();
await waitForFirstRender();
await this.hideUI();
}

View File

@@ -258,22 +258,6 @@ test("Renders a file with nested frames with inherited blur", async ({
await expect(workspace.canvas).toHaveScreenshot();
});
test("Renders a file with nested clipping frames", async ({ page }) => {
const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile();
await workspace.mockGetFile(
"render-wasm/get-file-frame-nested-clipping.json",
);
await workspace.goToWorkspace({
id: "44471494-966a-8178-8006-c5bd93f0fe72",
pageId: "44471494-966a-8178-8006-c5bd93f0fe73",
});
await workspace.waitForFirstRenderWithoutUI();
await expect(workspace.canvas).toHaveScreenshot();
});
test("Renders a clipped frame with a large blur drop shadow", async ({
page,
}) => {

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -9,399 +9,403 @@ test.beforeEach(async ({ page }) => {
]);
});
test("Team with unlimited subscription has specific icon in menu", async ({
page,
}) => {
await DashboardPage.mockRPC(
test.describe("Subscriptions: dashboard", () => {
test("Team with unlimited subscription has specific icon in menu", async ({
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await DashboardPage.mockRPC(
await DashboardPage.mockRPC(
page,
"get-projects?team-id=*",
"dashboard/get-projects-second-team.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToSecondTeamDashboard();
await expect(page.getByTestId("subscription-icon")).toBeVisible();
});
test("The Unlimited subscription has its name in the sidebar dropdown", async ({
page,
"get-projects?team-id=*",
"dashboard/get-projects-second-team.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToSecondTeamDashboard();
await expect(page.getByTestId("subscription-icon")).toBeVisible();
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage-one-editor.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToDashboard();
await expect(page.getByTestId("subscription-name")).toHaveText(
"Unlimited plan (trial)",
);
});
test("When the subscription status is unpaid, the sidebar dropdown displays the name Professional for the Unlimited subscription", async ({
page,
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-unpaid-subscription.json",
);
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToDashboard();
await expect(page.getByTestId("subscription-name")).toHaveText(
"Professional plan",
);
});
test("When the subscription status is canceled, the sidebar dropdown displays the name Professional for the Enterprise subscription", async ({
page,
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-enterprise-canceled-subscription.json",
);
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToDashboard();
await expect(page.getByTestId("subscription-name")).toHaveText(
"Professional plan",
);
});
});
test("The Unlimited subscription has its name in the sidebar dropdown", async ({
page,
}) => {
await DashboardPage.mockRPC(
test.describe("Subscriptions: team members and invitations", () => {
test("Team settings has susbscription name and no manage subscription link when is member", async ({
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in.json",
);
await DashboardPage.mockRPC(
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-member.json",
);
await DashboardPage.mockRPC(
page,
"get-projects?team-id=*",
"dashboard/get-projects-second-team.json",
);
await DashboardPage.mockRPC(
page,
"get-team-members?team-id=*",
"subscription/get-team-members-subscription-member.json",
);
await DashboardPage.mockRPC(
page,
"get-team-stats?team-id=*",
"dashboard/get-team-stats.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToSecondTeamSettingsSection();
await expect(page.getByText("Unlimited (trial)")).toBeVisible();
await expect(
page.getByRole("button", { name: "Manage your subscription" }),
).not.toBeVisible();
});
test("Team settings has susbscription name and manage subscription link when is owner", async ({
page,
"get-subscription-usage",
"subscription/get-subscription-usage-one-editor.json",
);
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
await DashboardPage.mockRPC(
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await DashboardPage.mockRPC(
page,
"get-projects?team-id=*",
"dashboard/get-projects-second-team.json",
);
await DashboardPage.mockRPC(
page,
"get-team-members?team-id=*",
"subscription/get-team-members-subscription-owner.json",
);
await DashboardPage.mockRPC(
page,
"get-team-stats?team-id=*",
"dashboard/get-team-stats.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToSecondTeamSettingsSection();
await expect(page.getByText("Unlimited (trial)")).toBeVisible();
await expect(
page.getByRole("button", { name: "Manage your subscription" }),
).toBeVisible();
});
test("Members tab has warning message when user has more seats than editors.", async ({
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await DashboardPage.mockRPC(
page,
"get-projects?team-id=*",
"dashboard/get-projects-second-team.json",
);
await DashboardPage.mockRPC(
page,
"get-team-members?team-id=*",
"subscription/get-team-members-subscription-eight-member.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToSecondTeamMembersSection();
const ctas = page.getByTestId("cta");
await expect(ctas).toHaveCount(2);
await expect(
page.getByText("Inviting people while on the unlimited plan"),
).toBeVisible();
});
test("Invitations tab has warning message when user has more seats than editors.", async ({
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToDashboard();
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await expect(page.getByTestId("subscription-name")).toHaveText(
"Unlimited plan (trial)",
);
});
test("The sidebar dropdown displays the correct subscription name when status is Unpaid", async ({
page,
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-unpaid-subscription.json",
);
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToDashboard();
await expect(page.getByTestId("subscription-name")).toHaveText(
"Professional plan",
);
});
test("The sidebar dropdown displays the correct subscription name when status is cancelled", async ({
page,
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-enterprise-canceled-subscription.json",
);
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToDashboard();
await expect(page.getByTestId("subscription-name")).toHaveText(
"Professional plan",
);
});
test("Team settings has susbscription name and no manage subscription link when is member", async ({
page,
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in.json",
);
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-member.json",
);
await DashboardPage.mockRPC(
page,
"get-projects?team-id=*",
"dashboard/get-projects-second-team.json",
);
await DashboardPage.mockRPC(
page,
"get-team-members?team-id=*",
"subscription/get-team-members-subscription-member.json",
);
await DashboardPage.mockRPC(
page,
"get-team-stats?team-id=*",
"dashboard/get-team-stats.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToSecondTeamSettingsSection();
await expect(page.getByText("Unlimited (trial)")).toBeVisible();
await expect(
page.getByRole("button", { name: "Manage your subscription" }),
).not.toBeVisible();
});
test("Team settings has susbscription name and manage subscription link when is owner", async ({
page,
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await DashboardPage.mockRPC(
page,
"get-projects?team-id=*",
"dashboard/get-projects-second-team.json",
);
await DashboardPage.mockRPC(
page,
"get-team-members?team-id=*",
"subscription/get-team-members-subscription-owner.json",
);
await DashboardPage.mockRPC(
page,
"get-team-stats?team-id=*",
"dashboard/get-team-stats.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToSecondTeamSettingsSection();
await expect(page.getByText("Unlimited (trial)")).toBeVisible();
await expect(
page.getByRole("button", { name: "Manage your subscription" }),
).toBeVisible();
});
test("Members tab has warning message when user has more seats than editors", async ({
page,
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await DashboardPage.mockRPC(
page,
"get-projects?team-id=*",
"dashboard/get-projects-second-team.json",
);
await DashboardPage.mockRPC(
page,
"get-team-members?team-id=*",
"subscription/get-team-members-subscription-eight-member.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToSecondTeamMembersSection();
const ctas = page.getByTestId("cta");
await expect(ctas).toHaveCount(2);
await expect(
page.getByText("Inviting people while on the unlimited plan"),
).toBeVisible();
});
test("Invitations tab has warning message when user has more seats than editors", async ({
page,
}) => {
await DashboardPage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",
);
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await DashboardPage.mockRPC(
page,
"get-projects?team-id=*",
"dashboard/get-projects-second-team.json",
);
await DashboardPage.mockRPC(
page,
"get-team-members?team-id=*",
"subscription/get-team-members-subscription-eight-member.json",
);
await DashboardPage.mockRPC(
page,
"get-team-invitations?team-id=*",
"subscription/get-team-invitations.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToSecondTeamInvitationsSection();
const ctas = page.getByTestId("cta");
await expect(ctas).toHaveCount(2);
await expect(
page.getByText("Inviting people while on the unlimited plan"),
).toBeVisible();
await DashboardPage.mockRPC(
page,
"get-team-info",
"subscription/get-team-info-subscriptions.json",
);
const dashboardPage = new DashboardPage(page);
await dashboardPage.setupDashboardFull();
await DashboardPage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-subscription-owner.json",
);
await DashboardPage.mockRPC(
page,
"get-projects?team-id=*",
"dashboard/get-projects-second-team.json",
);
await DashboardPage.mockRPC(
page,
"get-team-members?team-id=*",
"subscription/get-team-members-subscription-eight-member.json",
);
await DashboardPage.mockRPC(
page,
"get-team-invitations?team-id=*",
"subscription/get-team-invitations.json",
);
await dashboardPage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await dashboardPage.goToSecondTeamInvitationsSection();
const ctas = page.getByTestId("cta");
await expect(ctas).toHaveCount(2);
await expect(
page.getByText("Inviting people while on the unlimited plan"),
).toBeVisible();
});
});

View File

@@ -499,7 +499,7 @@ test.describe("Tokens: Tokens Tab", () => {
await valueField.fill("");
// TODO: We need to fix this translation
await expect(
tokensUpdateCreateModal.getByText("Token value cannot be empty"),
tokensUpdateCreateModal.getByText("Empty field"),
).toBeVisible();
await valueSaturationSelector.click({ position: { x: 50, y: 50 } });
await expect(valueField).toHaveValue(/^#[A-Fa-f\d]+$/);

View File

@@ -1,6 +0,0 @@
patchedDependencies:
'@zip.js/zip.js': patches/@zip.js__zip.js.patch
shamefullyHoist: true
recursiveInstall: true

View File

@@ -25,14 +25,14 @@
<link rel="icon" href="images/favicon.png" />
{{# manifest}}
<script>window.penpotWorkerURI="{{& worker_main}}"</script>
<script src="{{& config}}"></script>
<script src="{{& polyfills}}"></script>
<script defer src="{{& config}}"></script>
<script defer src="{{& polyfills}}"></script>
{{/manifest}}
<script type="module">
globalThis.penpotTranslations = JSON.parse({{& translations}});
globalThis.penpotVersion = "%version%";
globalThis.penpotBuildDate = "%buildDate%";
<script>
window.penpotTranslations = JSON.parse({{& translations}});
window.penpotVersion = "%version%";
window.penpotBuildDate = "%buildDate%";
</script>
<!--cookie-consent-->
</head>
@@ -44,11 +44,9 @@
<section id="modal"></section>
{{# manifest}}
<script type="module" src="{{& libs}}"></script>
<script type="module">
import { init } from "{{& main}}";
init();
</script>
<script defer src="js/libs.js?ts={{& ts}}"></script>
<script defer src="{{& shared}}"></script>
<script defer src="{{& main}}"></script>
{{/manifest}}
</body>
</html>

View File

@@ -19,11 +19,9 @@
</head>
<body>
{{# manifest}}
<script type="module" src="{{& libs}}"></script>
<script type="module">
import { init } from "{{& rasterizer}}";
init();
</script>
<script src="js/libs.js?ts={{& ts}}"></script>
<script src="{{& shared}}"></script>
<script src="{{& rasterizer}}"></script>
{{/manifest}}
</body>
</html>

View File

@@ -18,11 +18,9 @@
<body>
<div id="app"></div>
{{# manifest}}
<script type="module" src="{{& libs}}"></script>
<script type="module">
import { init } from "{{& render}}";
init();
</script>
<script src="js/libs.js?ts={{& ts}}"></script>
<script src="{{& shared}}"></script>
<script src="{{& render}}"></script>
{{/manifest}}
</body>
</html>

View File

@@ -193,30 +193,26 @@ async function readShadowManifest() {
const index = {
ts: ts,
config: "./js/config.js",
polyfills: "./js/polyfills.js",
worker_main: "./js/worker/main.js",
libs: "./js/libs.js",
config: "js/config.js?ts=" + ts,
polyfills: "js/polyfills.js?ts=" + ts,
worker_main: "js/worker/main.js?ts=" + ts,
};
for (let item of content) {
index[item.name] = "./js/" + item["output-name"] + "";
index[item.name] = "js/" + item["output-name"];
}
return index;
} catch (cause) {
const index = {
return {
ts: ts,
config: "./js/config.js",
polyfills: "./js/polyfills.js",
main: "./js/main.js",
shared: "./js/shared.js",
worker_main: "./js/worker/main.js",
rasterizer: "./js/rasterizer.js",
libs: "./js/libs.js",
config: "js/config.js?ts=" + ts,
polyfills: "js/polyfills.js?ts=" + ts,
main: "js/main.js?ts=" + ts,
shared: "js/shared.js?ts=" + ts,
worker_main: "js/worker/main.js?ts=" + ts,
rasterizer: "js/rasterizer.js?ts=" + ts,
};
return index;
}
}

View File

@@ -26,10 +26,7 @@ yarn install || exit 1;
rm -rf resources/public;
rm -rf target/dist;
mkdir -p resources/public;
mkdir -p target/dist;
yarn run build:app:main $EXTRA_PARAMS || exit 1
yarn run build:app:main --config-merge "{:release-version \"${CURRENT_HASH}-${TS}\"}" $EXTRA_PARAMS || exit 1
if [ "$INCLUDE_WASM" = "yes" ]; then
yarn run build:wasm || exit 1;
@@ -38,18 +35,19 @@ fi
yarn run build:app:libs || exit 1;
yarn run build:app:assets || exit 1;
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./resources/public/index.html;
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./resources/public/render.html;
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./resources/public/rasterizer.html;
sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./resources/public/index.html;
sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./resources/public/rasterizer.html;
mkdir -p target/dist;
rsync -avr resources/public/ target/dist/
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./target/dist/index.html;
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./target/dist/render.html;
sed -i -re "s/\%version\%/$CURRENT_VERSION/g" ./target/dist/rasterizer.html;
sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./target/dist/index.html;
sed -i -re "s/\%buildDate\%/$BUILD_DATE/g" ./target/dist/rasterizer.html;
if [ "$INCLUDE_WASM" = "yes" ]; then
sed -i "s/version=develop/version=$CURRENT_VERSION/g" ./resources/public/js/render_wasm.js;
sed -i "s/version=develop/version=$CURRENT_VERSION/g" ./target/dist/js/render_wasm.js;
fi
rsync -avr resources/public/ target/dist/;
if [ "$INCLUDE_STORYBOOK" = "yes" ]; then
# build storybook
yarn run build:storybook || exit 1;

View File

@@ -31,9 +31,9 @@ const rebuildNotify = {
const config = {
entryPoints: ["target/index.js"],
bundle: true,
format: "esm",
format: "iife",
banner: {
js: '"use strict";\nvar global = globalThis;',
js: '"use strict"; var global = globalThis;',
},
outfile: "resources/public/js/libs.js",
plugins: [fixReactVirtualized, rebuildNotify],

View File

@@ -1,9 +0,0 @@
#!/usr/bin/env bash
set -ex
corepack enable;
corepack install;
yarn install;
yarn run lint:scss;
yarn run test;

View File

@@ -1,13 +0,0 @@
#!/usr/bin/env bash
set -ex
corepack enable;
corepack install;
yarn install;
yarn run playwright install chromium --with-deps;
yarn run build:storybook
exec npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
"npx http-server storybook-static --port 6006 --silent" \
"npx wait-on tcp:6006 && yarn test:storybook"

View File

@@ -1,8 +0,0 @@
#!/usr/bin/env bash
set -ex
corepack enable;
corepack install;
yarn install;
yarn run playwright install chromium --with-deps;
yarn run test:e2e -x --workers=2 --reporter=list "$@";

View File

@@ -6,12 +6,13 @@
:builds
{:main
{:target :esm
{:target :browser
:output-dir "resources/public/js/"
:asset-path "/js"
:devtools {:watch-dir "resources/public"
:reload-strategy :full}
:build-options {:manifest-name "manifest.json"}
:module-loader true
:modules
{:shared
{:entries []}
@@ -19,7 +20,7 @@
:main
{:entries [app.main app.plugins.api]
:depends-on #{:shared}
:exports {init app.main/init}}
:init-fn app.main/init}
:util-highlight
{:entries [app.util.code-highlight]
@@ -49,12 +50,12 @@
:render
{:entries [app.render]
:depends-on #{:shared}
:exports {init app.render/init}}
:init-fn app.render/init}
:rasterizer
{:entries [app.rasterizer]
:depends-on #{:shared}
:exports {init app.rasterizer/init}}}
:init-fn app.rasterizer/init}}
:js-options
{:entry-keys ["module" "browser" "main"]
@@ -74,10 +75,11 @@
:compiler-options
{:fn-invoke-direct true
:optimizations #shadow/env ["PENPOT_BUILD_OPTIMIZATIONS" :as :keyword :default :advanced]
:output-wrapper true
:rename-prefix-namespace "PENPOT"
:source-map true
:elide-asserts true
:anon-fn-naming-policy :off
:cross-chunk-method-motion false
:source-map-detail-level :all}}}
:worker

View File

@@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.common.types.profile :refer [schema:profile]]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -483,7 +484,7 @@
(defn delete-access-token
[{:keys [id] :as params}]
(assert (uuid? id))
(us/assert! ::us/uuid id)
(ptk/reify ::delete-access-token
ptk/WatchEvent
(watch [_ _ _]

View File

@@ -31,28 +31,27 @@
[app.main.ui.static :as static]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[app.util.modules :as mod]
[app.util.theme :as theme]
[beicon.v2.core :as rx]
[rumext.v2 :as mf]))
(def auth-page
(mf/lazy #(mod/load 'app.main.ui.auth/auth-page*)))
(mf/lazy-component app.main.ui.auth/auth))
(def verify-token-page*
(mf/lazy #(mod/load 'app.main.ui.auth.verify-token/verify-token-page*)))
(def verify-token-page
(mf/lazy-component app.main.ui.auth.verify-token/verify-token))
(def viewer-page*
(mf/lazy #(mod/load 'app.main.ui.viewer/viewer-page*)))
(mf/lazy-component app.main.ui.viewer/viewer*))
(def dashboard-page*
(mf/lazy #(mod/load 'app.main.ui.dashboard/dashboard-page*)))
(mf/lazy-component app.main.ui.dashboard/dashboard*))
(def settings-page*
(mf/lazy #(mod/load 'app.main.ui.settings/settings-page*)))
(mf/lazy-component app.main.ui.settings/settings*))
(def workspace-page*
(mf/lazy #(mod/load 'app.main.ui.workspace/workspace-page*)))
(mf/lazy-component app.main.ui.workspace/workspace*))
(mf/defc workspace-legacy-redirect*
{::mf/props :obj
@@ -190,7 +189,7 @@
[:? [:& auth-page {:route route}]]
:auth-verify-token
[:? [:& verify-token-page* {:route route}]]
[:? [:& verify-token-page {:route route}]]
(:settings-profile
:settings-password

View File

@@ -19,7 +19,8 @@
[app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf]))
(mf/defc auth*
(mf/defc auth
{::mf/props :obj}
[{:keys [route]}]
(let [section (dm/get-in route [:data :name])
is-register (or
@@ -68,9 +69,3 @@
(when (= section :auth-register)
[:& terms-register])]]))
(mf/defc auth-page*
{::mf/lazy-load true}
[props]
[:> auth* props])

View File

@@ -61,9 +61,9 @@
(rt/nav :auth-login)
(ntf/warn (tr "errors.unexpected-token"))))
(mf/defc verify-token*
[{:keys [route]}]
(let [token (get-in route [:query-params :token])
(mf/defc verify-token
[{:keys [route] :as props}]
(let [token (get-in route [:query-params :token])
bad-token (mf/use-state false)]
(mf/with-effect []
@@ -99,8 +99,3 @@
[:> static/invalid-token {}]
[:> loader* {:title (tr "labels.loading")
:overlay true}])))
(mf/defc verify-token-page*
{::mf/lazy-load true}
[props]
[:> verify-token* props])

View File

@@ -8,28 +8,24 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.util.modules :as modules]
[cuerdas.core :as str]
[promesa.core :as p]
[rumext.v2 :as mf]))
[rumext.v2 :as mf]
[shadow.lazy :as lazy]))
(def highlight-fn
(delay (modules/load-fn 'app.util.code-highlight/highlight!)))
(lazy/loadable app.util.code-highlight/highlight!))
(mf/defc code-block
{::mf/wrap-props false}
[{:keys [code type]}]
(let [block-ref (mf/use-ref)
code (str/trim code)]
code (str/trim code)]
(mf/with-effect [code type]
(when-let [node (mf/ref-val block-ref)]
(->> @highlight-fn
(p/fmap (fn [f] (f)))
(p/fnly (fn [f cause]
(if cause
(js/console.error cause)
(f node)))))))
(p/let [highlight-fn (lazy/load highlight-fn)]
(highlight-fn node))))
[:pre {:class (dm/str type " " (stl/css :code-display)) :ref block-ref} code]))

View File

@@ -247,6 +247,7 @@
(swap! storage/session dissoc :template))))))
(mf/defc dashboard*
{::mf/props :obj}
[{:keys [profile project-id team-id search-term plugin-url template section]}]
(let [team (mf/deref refs/team)
projects (mf/deref refs/projects)
@@ -312,8 +313,3 @@
:section section
:search-term search-term
:team team}]]]))
(mf/defc dashboard-page*
{::mf/lazy-load true}
[props]
[:> dashboard* props])

View File

@@ -78,9 +78,3 @@
:settings-notifications
[:& notifications-page* {:profile profile}])]]]]))
(mf/defc settings-page*
{::mf/lazy-load true}
[props]
[:> settings* props])

View File

@@ -624,6 +624,7 @@
;; --- Component: Viewer
(mf/defc viewer*
{::mf/props :obj}
[{:keys [file-id share-id page-id] :as props}]
(mf/with-effect [file-id page-id share-id]
(let [params {:file-id file-id
@@ -642,8 +643,3 @@
[:> loader* {:title (tr "labels.loading")
:overlay true}]))
(mf/defc viewer-page*
{::mf/lazy-load true}
[props]
[:> viewer* props])

View File

@@ -165,7 +165,7 @@
(dsh/lookup-page state file-id page-id))))
st/state))
(mf/defc workspace-inner*
(mf/defc workspace-page*
{::mf/private true}
[{:keys [page-id file-id file layout wglobal]}]
(let [page-ref (mf/with-memo [file-id page-id]
@@ -252,16 +252,10 @@
:touch-action "none"}}
[:> context-menu*]
(if (and file-loaded? page-id)
[:> workspace-inner*
[:> workspace-page*
{:page-id page-id
:file-id file-id
:file file
:wglobal wglobal
:layout layout}]
[:> workspace-loader*])]]]]]]))
(mf/defc workspace-page*
{::mf/lazy-load true}
[props]
[:> workspace* props])

View File

@@ -6,7 +6,7 @@
(ns app.main.ui.workspace.shapes.text.editor
(:require
["@penpot/draft-js" :as draft]
["draft-js" :as draft]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]

View File

@@ -33,7 +33,6 @@
[app.util.object :as obj]
[app.util.text.content :as content]
[app.util.text.content.styles :as styles]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn get-contrast-color [background-color]
@@ -269,12 +268,7 @@
"bottom" "flex-end"
nil))
(defn- font-family-from-font-id [font-id]
(if (str/includes? font-id "gfont-noto-sans")
(let [lang (str/replace font-id #"gfont\-noto\-sans\-" "")]
(if (>= (count lang) 3) (str/capital lang) (str/upper lang)))
"Noto Color Emoji"))
;;
;; Text Editor Wrapper
;; This is an SVG element that wraps the HTML editor.
;;
@@ -287,10 +281,6 @@
(let [shape-id (dm/get-prop shape :id)
modifiers (dm/get-in modifiers [shape-id :modifiers])
fallback-fonts (wasm.api/fonts-from-text-content (:content shape) false)
fallback-families (map (fn [font]
(font-family-from-font-id (:font-id font))) fallback-fonts)
clip-id (dm/str "text-edition-clip" shape-id)
text-modifier-ref
@@ -327,7 +317,7 @@
max-height (max height selrect-height)
valign (-> shape :content :vertical-align)
y (:y selrect)
y (if (and valign (> height selrect-height))
y (if (> height selrect-height)
(case valign
"bottom" (- y (- height selrect-height))
"center" (- y (/ (- height selrect-height) 2))
@@ -351,8 +341,7 @@
render-wasm?
(obj/merge!
#js {"--editor-container-width" (dm/str width "px")
"--editor-container-height" (dm/str height "px")
"--fallback-families" (dm/str (str/join ", " fallback-families))})
"--editor-container-height" (dm/str height "px")})
(not render-wasm?)
(obj/merge!

View File

@@ -11,6 +11,7 @@
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.path-names :as cpn]
[app.common.spec :as us]
[app.common.thumbnails :as thc]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
@@ -37,6 +38,7 @@
[app.util.i18n :as i18n :refer [c tr]]
[app.util.strings :refer [matches-search]]
[app.util.timers :as ts]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
@@ -95,6 +97,10 @@
(str (str/slice (:path asset) (count path)))
(cpn/merge-path-item (:name asset))))
(s/def ::asset-name ::us/not-empty-string)
(s/def ::name-group-form
(s/keys :req-un [::asset-name]))
(def initial-context-menu-state
{:open? false :top nil :left nil})

View File

@@ -38,18 +38,30 @@
(features/use-feature "render-wasm/v1")
has-invalid-shapes?
(some (if render-wasm-enabled?
cfh/frame-shape?
#(or (cfh/frame-shape? %) (cfh/text-shape? %)))
shapes-with-children)
(if render-wasm-enabled?
false
(some (fn [shape]
(or (cfh/frame-shape? shape)
(cfh/text-shape? shape)))
shapes-with-children))
head-not-group-like?
(and (= 1 total-selected)
(not is-group?)
(not is-bool?))
disabled-bool-btns (or (zero? total-selected) has-invalid-shapes? head-not-group-like?)
disabled-flatten (or (zero? total-selected) has-invalid-shapes?)
disabled-bool-btns
(if render-wasm-enabled?
false
(or (zero? total-selected)
has-invalid-shapes?
head-not-group-like?))
disabled-flatten
(if render-wasm-enabled?
false
(or (zero? total-selected)
has-invalid-shapes?))
on-change
(mf/use-fn

View File

@@ -32,12 +32,6 @@
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn- token-value-error-fn
[{:keys [value]}]
(when (or (str/empty? value)
(str/blank? value))
(tr "workspace.tokens.empty-input")))
(defn- make-schema
[tokens-tree]
(sm/schema
@@ -50,7 +44,7 @@
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
#(not (cft/token-name-path-exists? % tokens-tree))]]]
[:value [::sm/text {:error/fn token-value-error-fn}]]
[:value ::sm/text]
[:resolved-value ::sm/any]

View File

@@ -32,12 +32,6 @@
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn- token-value-error-fn
[{:keys [value]}]
(when (or (str/empty? value)
(str/blank? value))
(tr "workspace.tokens.empty-input")))
(defn- make-schema
[tokens-tree]
(sm/schema
@@ -50,7 +44,7 @@
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
#(not (cft/token-name-path-exists? % tokens-tree))]]]
[:value [::sm/text {:error/fn token-value-error-fn}]]
[:value ::sm/text]
[:resolved-value ::sm/any]

View File

@@ -32,12 +32,6 @@
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn- token-value-error-fn
[{:keys [value]}]
(when (or (str/empty? value)
(str/blank? value))
(tr "workspace.tokens.empty-input")))
(defn- make-schema
[tokens-tree]
(sm/schema
@@ -50,7 +44,7 @@
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
#(not (cft/token-name-path-exists? % tokens-tree))]]]
[:value [::sm/text {:error/fn token-value-error-fn}]]
[:value ::sm/text]
[:resolved-value ::sm/any]

View File

@@ -205,7 +205,6 @@
;; TODO: Review this value vs default-value
:value (or value "")
:hint-message (:message hint)
:variant "comfortable"
:slot-start swatch
:hint-type (:type hint)})

View File

@@ -83,7 +83,6 @@
(mf/spread-props props {:on-change on-change
:default-value value
:hint-message (:message hint)
:variant "comfortable"
:hint-type (:type hint)})
props

View File

@@ -32,12 +32,6 @@
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn- token-value-error-fn
[{:keys [value]}]
(when (or (str/empty? value)
(str/blank? value))
(tr "workspace.tokens.empty-input")))
(defn- make-schema
[tokens-tree]
(sm/schema
@@ -50,7 +44,7 @@
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
#(not (cft/token-name-path-exists? % tokens-tree))]]]
[:value [::sm/text {:error/fn token-value-error-fn}]]
[:value ::sm/text]
[:resolved-value ::sm/any]

View File

@@ -216,6 +216,18 @@
on-frame-leave (actions/on-frame-leave frame-hover)
on-frame-select (actions/on-frame-select selected read-only?)
;; Text Editor Event Handlers
on-text-keydown (fn [event]
(when (and text-editing? (.-key event))
(.preventDefault event)
(wasm.api/handle-text-keydown (.-key event))))
on-text-mousedown (fn [event]
(when text-editing?
(let [rect (.getBoundingClientRect (.-currentTarget event))
x (- (.-clientX event) (.-left rect))
y (- (.-clientY event) (.-top rect))]
(wasm.api/handle-text-mousedown x y))))
disable-events? (contains? layout :comments)
show-comments? (= drawing-tool :comments)
show-cursor-tooltip? tooltip
@@ -231,8 +243,9 @@
show-pixel-grid? (and (contains? layout :show-pixel-grid)
(>= zoom 8))
show-text-editor? (and editing-shape (= :text (:type editing-shape)))
;; show-text-editor? (and editing-shape (= :text (:type editing-shape)))
show-text-editor? false
hover-grid? (and (some? @hover-top-frame-id)
(ctl/grid-layout? objects @hover-top-frame-id))
@@ -419,7 +432,9 @@
:on-pointer-enter on-pointer-enter
:on-pointer-leave on-pointer-leave
:on-pointer-move on-pointer-move
:on-pointer-up on-pointer-up}
:on-pointer-up on-pointer-up
:on-key-down on-text-keydown
:on-mouse-down on-text-mousedown}
[:defs
;; This clip is so the handlers are not over the rulers

View File

@@ -7,7 +7,7 @@
(ns app.plugins.comments
(:require
[app.common.geom.point :as gpt]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.main.data.comments :as dc]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.comments :as dwc]
@@ -118,8 +118,7 @@
(fn [position]
(let [position (parser/parse-point position)]
(cond
(or (not (sm/valid-safe-number? (:x position)))
(not (sm/valid-safe-number? (:y position))))
(or (not (us/safe-number? (:x position))) (not (us/safe-number? (:y position))))
(u/display-not-valid :position "Not valid point")
(not (r/check-permission plugin-id "comment:write"))

View File

@@ -7,7 +7,7 @@
(ns app.plugins.flex
(:require
[app.common.data :as d]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.transforms :as dwt]
@@ -133,7 +133,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :rowGap value)
(not (r/check-permission plugin-id "content:write"))
@@ -148,7 +148,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :columnGap value)
(not (r/check-permission plugin-id "content:write"))
@@ -163,7 +163,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :verticalPadding value)
(not (r/check-permission plugin-id "content:write"))
@@ -178,7 +178,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :horizontalPadding value)
(not (r/check-permission plugin-id "content:write"))
@@ -194,7 +194,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :topPadding value)
(not (r/check-permission plugin-id "content:write"))
@@ -209,7 +209,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :rightPadding value)
(not (r/check-permission plugin-id "content:write"))
@@ -224,7 +224,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :bottomPadding value)
(not (r/check-permission plugin-id "content:write"))
@@ -239,7 +239,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :leftPadding value)
(not (r/check-permission plugin-id "content:write"))
@@ -296,7 +296,7 @@
:set
(fn [_ value]
(cond
(sm/valid-safe-int? value)
(us/safe-int? value)
(u/display-not-valid :zIndex value)
(not (r/check-permission plugin-id "content:write"))
@@ -359,7 +359,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :verticalMargin value)
(not (r/check-permission plugin-id "content:write"))
@@ -374,7 +374,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :horizontalMargin value)
(not (r/check-permission plugin-id "content:write"))
@@ -389,7 +389,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :topMargin value)
(not (r/check-permission plugin-id "content:write"))
@@ -404,7 +404,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :rightMargin value)
(not (r/check-permission plugin-id "content:write"))
@@ -419,7 +419,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :bottomMargin value)
(not (r/check-permission plugin-id "content:write"))
@@ -434,7 +434,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :leftMargin value)
(not (r/check-permission plugin-id "content:write"))
@@ -449,7 +449,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :maxWidth value)
(not (r/check-permission plugin-id "content:write"))
@@ -464,7 +464,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :minWidth value)
(not (r/check-permission plugin-id "content:write"))
@@ -479,7 +479,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :maxHeight value)
(not (r/check-permission plugin-id "content:write"))
@@ -494,7 +494,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :minHeight value)
(not (r/check-permission plugin-id "content:write"))

View File

@@ -7,7 +7,7 @@
(ns app.plugins.grid
(:require
[app.common.data :as d]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.common.types.shape.layout :as ctl]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.transforms :as dwt]
@@ -126,7 +126,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :rowGap value)
(not (r/check-permission plugin-id "content:write"))
@@ -141,7 +141,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :columnGap value)
(not (r/check-permission plugin-id "content:write"))
@@ -156,7 +156,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :verticalPadding value)
(not (r/check-permission plugin-id "content:write"))
@@ -171,7 +171,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :horizontalPadding value)
(not (r/check-permission plugin-id "content:write"))
@@ -186,7 +186,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :topPadding value)
(not (r/check-permission plugin-id "content:write"))
@@ -201,7 +201,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :rightPadding value)
(not (r/check-permission plugin-id "content:write"))
@@ -216,7 +216,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :bottomPadding value)
(not (r/check-permission plugin-id "content:write"))
@@ -231,7 +231,7 @@
:set
(fn [_ value]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :leftPadding value)
(not (r/check-permission plugin-id "content:write"))
@@ -248,7 +248,7 @@
(u/display-not-valid :addRow-type type)
(and (or (= :percent type) (= :flex type) (= :fixed type))
(not (sm/valid-safe-number? value)))
(not (us/safe-number? value)))
(u/display-not-valid :addRow-value value)
(not (r/check-permission plugin-id "content:write"))
@@ -261,14 +261,14 @@
(fn [index type value]
(let [type (keyword type)]
(cond
(not (sm/valid-safe-int? index))
(not (us/safe-int? index))
(u/display-not-valid :addRowAtIndex-index index)
(not (contains? ctl/grid-track-types type))
(u/display-not-valid :addRowAtIndex-type type)
(and (or (= :percent type) (= :flex type) (= :fixed type))
(not (sm/valid-safe-number? value)))
(not (us/safe-number? value)))
(u/display-not-valid :addRowAtIndex-value value)
(not (r/check-permission plugin-id "content:write"))
@@ -285,7 +285,7 @@
(u/display-not-valid :addColumn-type type)
(and (or (= :percent type) (= :flex type) (= :lex type))
(not (sm/valid-safe-number? value)))
(not (us/safe-number? value)))
(u/display-not-valid :addColumn-value value)
(not (r/check-permission plugin-id "content:write"))
@@ -297,14 +297,14 @@
:addColumnAtIndex
(fn [index type value]
(cond
(not (sm/valid-safe-int? index))
(not (us/safe-int? index))
(u/display-not-valid :addColumnAtIndex-index index)
(not (contains? ctl/grid-track-types type))
(u/display-not-valid :addColumnAtIndex-type type)
(and (or (= :percent type) (= :flex type) (= :fixed type))
(not (sm/valid-safe-number? value)))
(not (us/safe-number? value)))
(u/display-not-valid :addColumnAtIndex-value value)
(not (r/check-permission plugin-id "content:write"))
@@ -317,7 +317,7 @@
:removeRow
(fn [index]
(cond
(not (sm/valid-safe-int? index))
(not (us/safe-int? index))
(u/display-not-valid :removeRow index)
(not (r/check-permission plugin-id "content:write"))
@@ -329,7 +329,7 @@
:removeColumn
(fn [index]
(cond
(not (sm/valid-safe-int? index))
(not (us/safe-int? index))
(u/display-not-valid :removeColumn index)
(not (r/check-permission plugin-id "content:write"))
@@ -342,14 +342,14 @@
(fn [index type value]
(let [type (keyword type)]
(cond
(not (sm/valid-safe-int? index))
(not (us/safe-int? index))
(u/display-not-valid :setColumn-index index)
(not (contains? ctl/grid-track-types type))
(u/display-not-valid :setColumn-type type)
(and (or (= :percent type) (= :flex type) (= :fixed type))
(not (sm/valid-safe-number? value)))
(not (us/safe-number? value)))
(u/display-not-valid :setColumn-value value)
(not (r/check-permission plugin-id "content:write"))
@@ -362,14 +362,14 @@
(fn [index type value]
(let [type (keyword type)]
(cond
(not (sm/valid-safe-int? index))
(not (us/safe-int? index))
(u/display-not-valid :setRow-index index)
(not (contains? ctl/grid-track-types type))
(u/display-not-valid :setRow-type type)
(and (or (= :percent type) (= :flex type) (= :fixed type))
(not (sm/valid-safe-number? value)))
(not (us/safe-number? value)))
(u/display-not-valid :setRow-value value)
(not (r/check-permission plugin-id "content:write"))
@@ -393,10 +393,10 @@
(not (shape-proxy? child))
(u/display-not-valid :appendChild-child child)
(or (< row 0) (not (sm/valid-safe-int? row)))
(or (< row 0) (not (us/safe-int? row)))
(u/display-not-valid :appendChild-row row)
(or (< column 0) (not (sm/valid-safe-int? column)))
(or (< column 0) (not (us/safe-int? column)))
(u/display-not-valid :appendChild-column column)
(not (r/check-permission plugin-id "content:write"))
@@ -431,7 +431,7 @@
(let [cell (locate-cell self)
shape (u/proxy->shape self)]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :row-value value)
(nil? cell)
@@ -451,7 +451,7 @@
(let [shape (u/proxy->shape self)
cell (locate-cell self)]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :rowSpan-value value)
(nil? cell)
@@ -471,7 +471,7 @@
(let [shape (u/proxy->shape self)
cell (locate-cell self)]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :column-value value)
(nil? cell)
@@ -491,7 +491,7 @@
(let [shape (u/proxy->shape self)
cell (locate-cell self)]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :columnSpan-value value)
(nil? cell)

View File

@@ -10,7 +10,7 @@
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.common.types.color :as cc]
[app.common.uuid :as uuid]
[app.main.data.comments :as dc]
@@ -299,7 +299,7 @@
(fn [orientation value board]
(let [shape (u/proxy->shape board)]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :addRulerGuide "Value not a safe number")
(not (contains? #{"vertical" "horizontal"} orientation))
@@ -345,8 +345,8 @@
(or (not (string? content)) (empty? content))
(u/display-not-valid :addCommentThread "Content not valid")
(or (not (sm/valid-safe-number? (:x position)))
(not (sm/valid-safe-number? (:y position))))
(or (not (us/safe-number? (:x position)))
(not (us/safe-number? (:y position))))
(u/display-not-valid :addCommentThread "Position not valid")
(and (some? board) (or (not (shape/shape-proxy? board)) (not (cfh/frame-shape? shape))))

View File

@@ -8,7 +8,7 @@
(:require
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.main.data.workspace.guides :as dwgu]
[app.main.store :as st]
[app.plugins.format :as format]
@@ -77,7 +77,7 @@
:set
(fn [self value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :position "Not valid position")
(not (r/check-permission plugin-id "content:write"))

View File

@@ -14,6 +14,7 @@
[app.common.path-names :as cpn]
[app.common.record :as crc]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.common.svg.path :as svg.path]
[app.common.types.color :as clr]
[app.common.types.component :as ctk]
@@ -324,7 +325,7 @@
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(or (not (sm/valid-safe-int? value)) (< value 0))
(or (not (us/safe-int? value)) (< value 0))
(u/display-not-valid :borderRadius value)
(not (r/check-permission plugin-id "content:write"))
@@ -340,7 +341,7 @@
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :borderRadiusTopLeft value)
(not (r/check-permission plugin-id "content:write"))
@@ -356,7 +357,7 @@
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :borderRadiusTopRight value)
(not (r/check-permission plugin-id "content:write"))
@@ -372,7 +373,7 @@
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :borderRadiusBottomRight value)
(not (r/check-permission plugin-id "content:write"))
@@ -388,7 +389,7 @@
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (sm/valid-safe-int? value))
(not (us/safe-int? value))
(u/display-not-valid :borderRadiusBottomLeft value)
(not (r/check-permission plugin-id "content:write"))
@@ -404,7 +405,7 @@
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(or (not (sm/valid-safe-number? value)) (< value 0) (> value 1))
(or (not (us/safe-number? value)) (< value 0) (> value 1))
(u/display-not-valid :opacity value)
(not (r/check-permission plugin-id "content:write"))
@@ -491,7 +492,7 @@
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :x value)
(not (r/check-permission plugin-id "content:write"))
@@ -509,7 +510,7 @@
(fn [self value]
(let [id (obj/get self "$id")]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :y value)
(not (r/check-permission plugin-id "content:write"))
@@ -554,7 +555,7 @@
:set
(fn [self value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :parentX value)
(not (r/check-permission plugin-id "content:write"))
@@ -581,7 +582,7 @@
:set
(fn [self value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :parentY value)
(not (r/check-permission plugin-id "content:write"))
@@ -608,7 +609,7 @@
:set
(fn [self value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :frameX value)
(not (r/check-permission plugin-id "content:write"))
@@ -635,7 +636,7 @@
:set
(fn [self value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :frameY value)
(not (r/check-permission plugin-id "content:write"))
@@ -794,10 +795,10 @@
:resize
(fn [width height]
(cond
(or (not (sm/valid-safe-number? width)) (<= width 0))
(or (not (us/safe-number? width)) (<= width 0))
(u/display-not-valid :resize width)
(or (not (sm/valid-safe-number? height)) (<= height 0))
(or (not (us/safe-number? height)) (<= height 0))
(u/display-not-valid :resize height)
(not (r/check-permission plugin-id "content:write"))
@@ -1047,10 +1048,10 @@
(not (cfh/text-shape? shape))
(u/display-not-valid :getRange-shape "shape is not text")
(or (not (sm/valid-safe-int? start)) (< start 0) (> start end))
(or (not (us/safe-int? start)) (< start 0) (> start end))
(u/display-not-valid :getRange-start start)
(not (sm/valid-safe-int? end))
(not (us/safe-int? end))
(u/display-not-valid :getRange-end end)
:else
@@ -1077,7 +1078,7 @@
:setParentIndex
(fn [index]
(cond
(not (sm/valid-safe-int? index))
(not (us/safe-int? index))
(u/display-not-valid :setParentIndex index)
(not (r/check-permission plugin-id "content:write"))
@@ -1229,7 +1230,7 @@
(fn [orientation value]
(let [shape (u/locate-shape file-id page-id id)]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :addRulerGuide "Value not a safe number")
(not (contains? #{"vertical" "horizontal"} orientation))

View File

@@ -7,7 +7,7 @@
(ns app.plugins.viewport
(:require
[app.common.data.macros :as dm]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.main.data.workspace.viewport :as dwv]
[app.main.data.workspace.zoom :as dwz]
[app.main.store :as st]
@@ -37,10 +37,10 @@
(let [new-x (obj/get value "x")
new-y (obj/get value "y")]
(cond
(not (sm/valid-safe-number? new-x))
(not (us/safe-number? new-x))
(u/display-not-valid :center-x new-x)
(not (sm/valid-safe-number? new-y))
(not (us/safe-number? new-y))
(u/display-not-valid :center-y new-y)
:else
@@ -62,7 +62,7 @@
:set
(fn [value]
(cond
(not (sm/valid-safe-number? value))
(not (us/safe-number? value))
(u/display-not-valid :zoom value)
:else

View File

@@ -125,40 +125,47 @@
[:embed {:optional true} :boolean]
[:skip-children {:optional true} :boolean]
[:object-id
[:or [::sm/set ::sm/uuid] ::sm/uuid]]])
[:or
::sm/uuid
::sm/coll-of-uuid]]])
(def ^:private coerce-render-objects-params
(sm/coercer schema:render-objects))
(def ^:private render-objects-decoder
(sm/lazy-decoder schema:render-objects
sm/string-transformer))
(def ^:private render-objects-validator
(sm/lazy-validator schema:render-objects))
(defn- render-objects
[params]
(try
(let [{:keys [file-id page-id embed share-id object-id skip-children] :as params}
(coerce-render-objects-params params)]
(st/emit! (fetch-objects-bundle :file-id file-id :page-id page-id :share-id share-id :object-id object-id))
(if (uuid? object-id)
(mf/html
[:& object-svg
{:file-id file-id
:page-id page-id
:share-id share-id
:object-id object-id
:embed embed
:skip-children skip-children}])
(let [{:keys [file-id page-id embed share-id object-id skip-children] :as params} (render-objects-decoder params)]
(if-not (render-objects-validator params)
(do
(js/console.error "invalid arguments")
(sm/pretty-explain schema:render-objects params)
nil)
(mf/html
[:& objects-svg
{:file-id file-id
:page-id page-id
:share-id share-id
:object-ids (into #{} object-id)
:embed embed
:skip-children skip-children}])))
(catch :default cause
(when-let [explain (-> cause ex-data ::sm/explain)]
(js/console.log "Unexpected error")
(js/console.log (sm/humanize-explain explain)))
(mf/html [:span "Unexpected error:" (ex-message cause)]))))
(do
(st/emit! (fetch-objects-bundle :file-id file-id :page-id page-id :share-id share-id :object-id object-id))
(if (uuid? object-id)
(mf/html
[:& object-svg
{:file-id file-id
:page-id page-id
:share-id share-id
:object-id object-id
:embed embed
:skip-children skip-children}])
(mf/html
[:& objects-svg
{:file-id file-id
:page-id page-id
:share-id share-id
:object-ids (into #{} object-id)
:embed embed
:skip-children skip-children}]))))))
;; ---- COMPONENTS SPRITE
@@ -235,37 +242,39 @@
[:embed {:optional true} :boolean]
[:component-id {:optional true} ::sm/uuid]])
(def ^:private coerce-render-components-params
(sm/coercer schema:render-components))
(def ^:private render-components-decoder
(sm/lazy-decoder schema:render-components
sm/string-transformer))
(def ^:private render-components-validator
(sm/lazy-validator schema:render-components))
(defn render-components
[params]
(try
(let [{:keys [file-id component-id embed] :as params}
(coerce-render-components-params params)]
(let [{:keys [file-id component-id embed] :as params} (render-components-decoder params)]
(if-not (render-components-validator params)
(do
(js/console.error "invalid arguments")
(sm/pretty-explain schema:render-components params)
nil)
(st/emit! (ptk/reify ::initialize-render-components
ptk/WatchEvent
(watch [_ _ stream]
(rx/merge
(rx/of (fetch-team :file-id file-id))
(do
(st/emit! (ptk/reify ::initialize-render-components
ptk/WatchEvent
(watch [_ _ stream]
(rx/merge
(rx/of (fetch-team :file-id file-id))
(->> stream
(rx/filter (ptk/type? ::team-fetched))
(rx/observe-on :async)
(rx/map (constantly params))
(rx/map fetch-components-bundle))))))
(->> stream
(rx/filter (ptk/type? ::team-fetched))
(rx/observe-on :async)
(rx/map (constantly params))
(rx/map fetch-components-bundle))))))
(mf/html
[:& components-svg
{:component-id component-id
:embed embed}]))
(catch :default cause
(when-let [explain (-> cause ex-data ::sm/explain)]
(js/console.log "Unexpected error")
(js/console.log (sm/humanize-explain explain)))
(mf/html [:span "Unexpected error:" (ex-message cause)]))))
(mf/html
[:& components-svg
{:component-id component-id
:embed embed}])))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SETUP

View File

@@ -40,7 +40,6 @@
[app.util.debug :as dbg]
[app.util.functions :as fns]
[app.util.globals :as ug]
[app.util.modules :as mod]
[app.util.text.content :as tc]
[beicon.v2.core :as rx]
[promesa.core :as p]
@@ -476,9 +475,9 @@
(dissoc :style)
(merge style)
(select-keys allowed-keys))
fill-rule (-> (or (:fill-rule attrs) (:fillRule attrs)) sr/translate-fill-rule)
stroke-linecap (-> (or (:stroke-linecap attrs) (:strokeLinecap attrs)) sr/translate-stroke-linecap)
stroke-linejoin (-> (or (:stroke-linejoin attrs) (:strokeLinejoin attrs)) sr/translate-stroke-linejoin)
fill-rule (or (-> attrs :fill-rule sr/translate-fill-rule) (-> attrs :fillRule sr/translate-fill-rule))
stroke-linecap (or (-> attrs :stroke-linecap sr/translate-stroke-linecap) (-> attrs :strokeLinecap sr/translate-stroke-linecap))
stroke-linejoin (or (-> attrs :stroke-linejoin sr/translate-stroke-linejoin) (-> attrs :strokeLinejoin sr/translate-stroke-linejoin))
fill-none (= "none" (-> attrs :fill))]
(h/call wasm/internal-module "_set_shape_svg_attrs" fill-rule stroke-linecap stroke-linejoin fill-none)))
@@ -786,10 +785,19 @@
hidden)))
shadows))
(defn fonts-from-text-content [content fallback-fonts-only?]
(defn set-shape-text-content
"This function sets shape text content and returns a stream that loads the needed fonts asynchronously"
[shape-id content]
(h/call wasm/internal-module "_clear_shape_text")
(set-shape-vertical-align (get content :vertical-align))
(let [paragraph-set (first (get content :children))
paragraphs (get paragraph-set :children)
fonts (fonts/get-content-fonts content)
total (count paragraphs)]
(loop [index 0
emoji? false
langs #{}]
@@ -806,36 +814,20 @@
emoji? (if emoji? emoji? (t/contains-emoji? text))
langs (t/collect-used-languages langs text)]
;; FIXME: this should probably be somewhere else
(when fallback-fonts-only? (t/write-shape-text spans paragraph text))
(t/write-shape-text spans paragraph text)
(recur (inc index)
emoji?
langs))))
(let [updated-fonts
(-> #{}
(-> fonts
(cond-> ^boolean emoji? (f/add-emoji-font))
(f/add-noto-fonts langs))
fallback-fonts (filter #(get % :is-fallback) updated-fonts)]
result (f/store-fonts shape-id updated-fonts)]
(if fallback-fonts-only? updated-fonts fallback-fonts))))))
(h/call wasm/internal-module "_update_shape_text_layout")
(defn set-shape-text-content
"This function sets shape text content and returns a stream that loads the needed fonts asynchronously"
[shape-id content]
(h/call wasm/internal-module "_clear_shape_text")
(set-shape-vertical-align (get content :vertical-align))
(let [fonts (fonts/get-content-fonts content)
fallback-fonts (fonts-from-text-content content true)
all-fonts (concat fonts fallback-fonts)
result (f/store-fonts shape-id all-fonts)]
(f/load-fallback-fonts-for-editor! fallback-fonts)
(h/call wasm/internal-module "_update_shape_text_layout")
result))
result)))))
(defn set-shape-grow-type
[grow-type]
@@ -1220,6 +1212,7 @@
(defn init-canvas-context
[canvas]
(let [gl (unchecked-get wasm/internal-module "GL")
flags (debug-flags)
context-id (if (dbg/enabled? :wasm-gl-context-init-error) "fail" "webgl2")
@@ -1266,6 +1259,19 @@
(h/call wasm/internal-module "_hide_grid")
(request-render "clear-grid"))
;; Text Editor Functions
(defn handle-text-keydown
[key]
(when (and wasm/internal-module key)
(h/call wasm/internal-module "_handle_keydown" key)
(request-render "text-editor-keydown")))
(defn handle-text-mousedown
[x y]
(when (and wasm/internal-module x y)
(h/call wasm/internal-module "_handle_mousedown" x y)
(request-render "text-editor-mousedown")))
(defn get-grid-coords
[position]
(let [offset (h/call wasm/internal-module
@@ -1362,7 +1368,7 @@
(delay
(if (exists? js/dynamicImport)
(let [uri (cf/resolve-static-asset "js/render_wasm.js")]
(->> (mod/import uri)
(->> (js/dynamicImport (str uri))
(p/mcat init-wasm-module)
(p/fmap
(fn [default]

View File

@@ -266,12 +266,6 @@
(store-font-id shape-id font-data asset-id emoji? fallback?)))
;; FIXME: This is a temporary function to load the fallback fonts for the editor.
;; Once we render the editor content within wasm, we can remove this function.
(defn load-fallback-fonts-for-editor!
[fonts]
(doseq [font fonts]
(fonts/ensure-loaded! (:font-id font) (:font-variant-id font))))
(defn store-fonts
[shape-id fonts]
@@ -283,8 +277,7 @@
:font-variant-id "regular"
:style 0
:weight 400
:is-emoji true
:is-fallback true}))
:is-emoji true}))
(def noto-fonts
{:japanese {:font-id "gfont-noto-sans-jp" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}

View File

@@ -67,17 +67,15 @@ function filterAllowedTypes(options) {
* @param {string} type
* @returns {boolean}
*/
function filter(type) {
return function filter(type) {
if (
(!("allowHTMLPaste" in options) || !options["allowHTMLPaste"]) &&
type === "text/html"
type === "text/html"
) {
return false;
}
return allowedTypes.includes(type);
};
return filter;
}
/**
@@ -87,22 +85,19 @@ function filterAllowedTypes(options) {
* @returns {Function<AllowedTypesFilterFunction>}
*/
function filterAllowedItems(options) {
/**
* @param {DataTransferItem}
* @returns {boolean}
*/
function filter(item) {
return function filter(item) {
if (
(!("allowHTMLPaste" in options) || !options["allowHTMLPaste"]) &&
item.type === "text/html"
item.type === "text/html"
) {
return false;
}
return allowedTypes.includes(item.type);
};
return filter;
}
/**

View File

@@ -6,11 +6,10 @@
(ns app.util.code-highlight
(:require
["highlight.js$default" :as hljs]
["@penpot/hljs" :as hljs]
[app.util.dom :as dom]))
(defn highlight!
{:lazy-loadable true}
[node]
(dom/set-data! node "highlighted" nil)
(hljs/highlightElement node))

View File

@@ -20,7 +20,7 @@ goog.provide("app.util.globals");
goog.scope(function () {
var self = app.util.globals;
self.global = globalThis;
self.global = goog.global;
function createMockedEventEmitter(k) {
/* Allow mocked objects to be event emitters, so other modules

View File

@@ -92,9 +92,8 @@
is executed in the critical part (application bootstrap) and used in
many parts of the application."
[data]
(set! translations data)
(reset! locale (or (get storage/global ::locale) (autodetect))))
(reset! locale (or (get storage/global ::locale) (autodetect)))
(set! translations data))
(defn set-locale!
[lname]

View File

@@ -1,17 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.util.modules
(:refer-clojure :exclude [load resolve]))
(defmacro load
[thing]
`(-> (shadow.esm/load-by-name ~thing)
(.then (fn [f#] (cljs.core/js-obj "default" (f#))))))
(defmacro load-fn
[thing]
`(shadow.esm/load-by-name ~thing))

View File

@@ -1,16 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.util.modules
(:refer-clojure :exclude [import])
(:require-macros [app.util.modules])
(:require
[shadow.esm :refer [dynamic-import]]))
(defn import
"Dynamic esm module import import"
[path]
(dynamic-import (str path)))

View File

@@ -38,8 +38,7 @@
(str v "px")
(and (= k :font-family) (seq v))
;; pick just first family, avoid quoting twice, and add var(--fallback-families)
(str/concat (str/quote (str/unquote (first (str/split v ",")))) ", var(--fallback-families)")
(str/quote v)
:else
v))
@@ -54,7 +53,7 @@
(str/slice v 0 -2)
(= k :font-family)
(str/unquote (str/replace v ", var(--fallback-families)" ""))
(str/unquote v)
:else
v))

View File

@@ -101,8 +101,8 @@
(rx/push! bus message))))
handle-error
(fn [event]
(on-error worker (.-data event)))]
(fn [error]
(on-error worker (.-data error)))]
(.addEventListener instance "message" handle-message)
(.addEventListener instance "error" handle-error)

View File

@@ -20,12 +20,12 @@
[app.render-wasm.api :as wasm.api]
[app.render-wasm.wasm :as wasm]
[app.util.http :as http]
[app.util.modules :as mod]
[app.worker.impl :as impl]
[beicon.v2.core :as rx]
[okulary.core :as l]
[promesa.core :as p]
[rumext.v2 :as mf]))
[rumext.v2 :as mf]
[shadow.esm :refer (dynamic-import)]))
(log/set-level! :trace)
@@ -101,7 +101,7 @@
(def init-wasm
(delay
(let [uri (cf/resolve-static-asset "js/render_wasm.js")]
(-> (mod/import (str uri))
(-> (dynamic-import (str uri))
(p/then #(wasm.api/init-wasm-module %))
(p/then #(set! wasm/internal-module %))))))

View File

@@ -13,7 +13,6 @@ import {
isLikeParagraph,
} from "./Paragraph.js";
import { isDisplayBlock, normalizeStyles } from "./Style.js";
import { sanitizeFontFamily } from "./Style.js";
const DEFAULT_FONT_SIZE = "14px";
const DEFAULT_FONT_WEIGHT = 400;
@@ -88,16 +87,16 @@ export function mapContentFragmentFromDocument(document, root, styleDefaults) {
styleDefaults?.getPropertyValue("font-size") ?? DEFAULT_FONT_SIZE,
);
}
let fontFamily = textSpan.style.getPropertyValue("font-family");
const fontFamily = textSpan.style.getPropertyValue("font-family");
if (!fontFamily) {
console.warn("font-family", fontFamily);
fontFamily =
const fontFamilyValue =
styleDefaults?.getPropertyValue("font-family") ?? DEFAULT_FONT_FAMILY;
const quotedFontFamily = fontFamilyValue.startsWith('"')
? fontFamilyValue
: `"${fontFamilyValue}"`;
textSpan.style.setProperty("font-family", quotedFontFamily);
}
fontFamily = sanitizeFontFamily(fontFamily);
textSpan.style.setProperty("font-family", fontFamily);
const fontWeight = textSpan.style.getPropertyValue("font-weight");
if (!fontWeight) {
console.warn("font-weight", fontWeight);
@@ -145,29 +144,18 @@ export function htmlToText(html) {
tmp.innerHTML = html;
const blockTags = [
"P",
"DIV",
"SECTION",
"ARTICLE",
"HEADER",
"FOOTER",
"UL",
"OL",
"LI",
"TABLE",
"TR",
"TD",
"TH",
"PRE",
"P", "DIV", "SECTION", "ARTICLE", "HEADER", "FOOTER",
"UL", "OL", "LI", "TABLE", "TR", "TD", "TH", "PRE"
];
function walk(node) {
let text = "";
node.childNodes.forEach((child) => {
node.childNodes.forEach(child => {
if (child.nodeType === Node.TEXT_NODE) {
text += child.textContent;
} else if (child.nodeType === Node.ELEMENT_NODE) {
if (child.tagName === "BR") {
text += "\n";
}
@@ -190,6 +178,7 @@ export function htmlToText(html) {
return result.trim();
}
/**
* Maps any HTML into a valid content DOM element.
*
@@ -198,14 +187,10 @@ export function htmlToText(html) {
* @param {boolean} [allowHTMLPaste=false]
* @returns {DocumentFragment}
*/
export function mapContentFragmentFromHTML(
html,
styleDefaults,
allowHTMLPaste,
) {
export function mapContentFragmentFromHTML(html, styleDefaults, allowHTMLPaste) {
if (allowHTMLPaste) {
try {
const parser = new DOMParser();
const parser = new DOMParser()
const document = parser.parseFromString(html, "text/html");
return mapContentFragmentFromDocument(document, styleDefaults);
} catch (error) {

View File

@@ -19,31 +19,10 @@ const DEFAULT_FONT_WEIGHT = "400";
* @param {string} value
*/
export function sanitizeFontFamily(value) {
// NOTE: This is a fix for a bug introduced earlier that have might modified the font-family in the model
// adding extra double quotes.
if (value && value.startsWith('""')) {
//remove the first and last quotes
value = value.slice(1).replace(/"([^"]*)$/, "$1");
// remove quotes from font-family in 1-word font-families
// and repeated values
value = [
...new Set(
value
.split(", ")
.map((x) => (x.includes(" ") ? x : x.replace(/"/g, ""))),
),
].join(", ");
}
if (!value || value === "") {
return "var(--fallback-families)";
} else if (value.endsWith(" var(--fallback-families)")) {
return value;
} else if (value.startsWith('"')) {
return `${value}, var(--fallback-families)`;
if (value && value.length > 0 && !value.startsWith('"')) {
return `"${value}"`;
} else {
return `"${value}", var(--fallback-families)`;
return value;
}
}

View File

@@ -16,9 +16,7 @@ export const {
RichTextEditorUtil,
SelectionState,
convertFromRaw,
convertToRaw,
EditorBlock,
Editor
convertToRaw
} = pkg;
import DraftPasteProcessor from 'draft-js/lib/DraftPasteProcessor.js';

View File

@@ -8,8 +8,7 @@
"author": "Andrey Antukh",
"license": "MPL-2.0",
"dependencies": {
"draft-js": "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0",
"immutable": "^5.1.4"
"draft-js": "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0"
},
"peerDependencies": {
"react": ">=0.17.0",

View File

@@ -173,13 +173,12 @@ __metadata:
languageName: node
linkType: hard
"@penpot/draft-js@workspace:.":
"@penpot/draft-js-wrapper@workspace:.":
version: 0.0.0-use.local
resolution: "@penpot/draft-js@workspace:."
resolution: "@penpot/draft-js-wrapper@workspace:."
dependencies:
draft-js: "penpot/draft-js.git#4a99b2a6020b2af97f6dc5fa1b4275ec16b559a0"
esbuild: "npm:^0.24.0"
immutable: "npm:^5.1.4"
peerDependencies:
react: ">=0.17.0"
react-dom: ">=0.17.0"
@@ -321,13 +320,6 @@ __metadata:
languageName: node
linkType: hard
"immutable@npm:^5.1.4":
version: 5.1.4
resolution: "immutable@npm:5.1.4"
checksum: 10c0/f1c98382e4cde14a0b218be3b9b2f8441888da8df3b8c064aa756071da55fbed6ad696e5959982508456332419be9fdeaf29b2e58d0eadc45483cc16963c0446
languageName: node
linkType: hard
"immutable@npm:~3.7.4":
version: 3.7.6
resolution: "immutable@npm:3.7.6"

5
frontend/vendor/hljs/index.js vendored Normal file
View File

@@ -0,0 +1,5 @@
import h from "highlight.js";
export function highlightElement(node) {
return h.highlightElement(node);
}

13
frontend/vendor/hljs/package.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"name": "@penpot/hljs",
"version": "1.0.0",
"description": "Penpot Hightlight.js ESM wrapper",
"main": "index.js",
"packageManager": "yarn@4.3.1",
"author": "Andrey Antukh",
"license": "MPL-2.0",
"type": "module",
"dependencies": {
"highlight.js": "^11.10.0"
}
}

21
frontend/vendor/hljs/yarn.lock vendored Normal file
View File

@@ -0,0 +1,21 @@
# This file is generated by running "yarn install" inside your project.
# Manual changes might be lost - proceed with caution!
__metadata:
version: 8
cacheKey: 10c0
"@penpot/hljs@workspace:.":
version: 0.0.0-use.local
resolution: "@penpot/hljs@workspace:."
dependencies:
highlight.js: "npm:^11.10.0"
languageName: unknown
linkType: soft
"highlight.js@npm:^11.10.0":
version: 11.10.0
resolution: "highlight.js@npm:11.10.0"
checksum: 10c0/cd8bf7ef06cbd72ddb83580ecabe769f08f062be8bb82d2eb492d31c17f7480d1f8d14a66fc81deee0601645435f19f04c470510563f847242a41ccff0ab873e
languageName: node
linkType: hard

4
frontend/vendor/tubax/saxjs.cljs vendored Normal file
View File

@@ -0,0 +1,4 @@
(ns tubax.saxjs
(:require ["sax" :as sax]))
(goog/exportSymbol "sax" sax)

Some files were not shown because too many files have changed in this diff Show More