Compare commits

..

48 Commits

Author SHA1 Message Date
Alonso Torres
accd5226d7 🐛 Fix sidebar width in localhost (#6732) 2025-07-07 09:28:52 +02:00
Andrey Antukh
16a1fd14e5 🐛 Fix media translation on text nodes on paste (#6845)
Fix incorrect media translation on paste text with fill images
2025-07-07 09:03:35 +02:00
Aitor Moreno
824bb19c7e Merge pull request #6848 from penpot/niwinz-staging-library-referer
 Add referer field to binfile v3
2025-07-07 09:02:13 +02:00
Aitor Moreno
d0f3e0f0b0 Merge pull request #6853 from penpot/niwinz-staging-path-bool-fixes
🐛 Fix exception on a corner case of creating bool shape
2025-07-07 09:00:57 +02:00
Andrey Antukh
43ba2b05e8 📎 Change current config values for error report explain 2025-07-04 14:51:08 +02:00
Andrey Antukh
d5ccb704b2 🐛 Fix unexpected exception on creating bool shapes 2025-07-04 14:42:09 +02:00
David Barragán Merino
6d21fcc9de 🔧 Fix condition for automatic events (#6849) 2025-07-04 11:57:57 +02:00
Andrey Antukh
77741b49a7 Add tracking for referer on the import-binfile 2025-07-04 11:02:36 +02:00
Andrey Antukh
a7e0cfc609 🎉 Bump 1.0.7 release of the penpot library
Includes the ability to pass referer
2025-07-04 11:02:36 +02:00
Andrey Antukh
50a6355537 🎉 Add options for creating library build context
With the ability to pass referer.
2025-07-04 11:02:36 +02:00
Alejandro Alonso
264aef277d Merge pull request #6847 from penpot/niwinz-staging-error-reports-2
 Add minor improvements to error report on calc bool content
2025-07-04 10:53:02 +02:00
Andrey Antukh
78d0e6d059 Add minor improvements to error report on calc bool content 2025-07-04 10:13:24 +02:00
Alonso Torres
6d41d36b3a 🐛 Fix problem when double click on hidden shapes (#6833) 2025-07-04 09:01:20 +02:00
David Barragán Merino
bb97df373e 🔧 Add Github Action to build and upload artifact (#6840)
Co-authored-by: Francis Santiago <francis.santiago@kaleidos.net>
2025-07-04 08:41:46 +02:00
Alejandro Alonso
a41af032cd Merge pull request #6844 from penpot/niwinz-staging-enhancements
 Improve error reporting
2025-07-03 15:31:20 +02:00
Andrey Antukh
86ee4f55c5 📚 Update docstring 2025-07-03 14:54:00 +02:00
Andrey Antukh
63cd3ae025 Add better error handling for bool creation 2025-07-03 14:54:00 +02:00
Andrey Antukh
cafb7abb53 🎉 Add better syntax facility for ex/try! macro 2025-07-03 14:54:00 +02:00
Andrey Antukh
e5b6c4a9e0 Add minor improvement to error reporter logger 2025-07-03 14:54:00 +02:00
Andrey Antukh
1d5bad5523 💄 Report file-id on file changes exception 2025-07-03 14:54:00 +02:00
Andrey Antukh
96d6868b45 🐛 Add missing fields on get-team-shared-files query 2025-07-03 14:54:00 +02:00
Andrey Antukh
b739d8bd0c 💄 Change default depth on params for error reports 2025-07-03 14:54:00 +02:00
Alejandro Alonso
dd803dc1de Merge pull request #6839 from penpot/niwinz-staging-fix-broken-path
🐛 Add migration for fix undecoded path content
2025-07-03 13:43:59 +02:00
Andrey Antukh
b627c10737 🔥 Remove duplicated check-fn 2025-07-03 10:50:09 +02:00
María Valderrama
95f4a9bd29 Add missing start-plugin event (#6809)
*  Add missing start-plugin event

* 📎 Correct event origin
2025-07-03 10:29:43 +02:00
Andrey Antukh
a2b8f19ff3 🐛 Add migration for fix undecoded path content 2025-07-03 08:40:23 +02:00
Andrey Antukh
898182e3d5 Add minor events props normalization (#6836) 2025-07-02 14:41:48 +02:00
Miguel de Benito Delgado
e03c822b51 🐛 Fix internal error on missing theme setting in profile (#6822) 2025-07-02 10:17:22 +02:00
Eva Marco
a3aabf3b7d 🐛 Fix tooltip position after click (#6830) 2025-07-02 09:56:14 +02:00
Andrés Moya
5c4fd97541 🐛 Allow importing file without any token but with themes or sets (#6796) 2025-06-27 11:32:14 +02:00
Alejandro Alonso
3010abbf64 Merge pull request #6793 from penpot/alotor-fix-plugins-system-theme
🐛 Fix problem with plugins on system theme
2025-06-26 12:30:51 +02:00
alonso.torres
e6a7eed7a9 🐛 Fix problem with plugins on system theme 2025-06-26 12:15:06 +02:00
Alejandro Alonso
daf3b5caa8 🐛 Fix slow color picker (#6780) 2025-06-26 11:07:35 +02:00
Andrey Antukh
34d65ed1c8 Merge pull request #6775 from penpot/superalex-fix-entering-long-project-name
🐛 Fix entering long project name
2025-06-26 10:48:54 +02:00
Alejandro Alonso
27c624ae0f Merge pull request #6787 from penpot/niwinz-staging-hotfix-4
🐛 Several fixes
2025-06-26 09:13:49 +02:00
Andrey Antukh
3831b3034e 🐛 Fix boolean shape migration that causes issues on import 2025-06-26 08:55:09 +02:00
Andrey Antukh
00390a1349 🐛 Add correct is-text-node? predicate to text processing methods 2025-06-26 08:32:11 +02:00
Andrey Antukh
17bfed137c 📎 Add better defaults for text processing on old migrations 2025-06-26 08:32:11 +02:00
Andrey Antukh
77ef26b207 📎 Add srepl script for validate file schema 2025-06-26 08:32:11 +02:00
Andrey Antukh
26239a15f2 📎 Add missing changes on lost-colors fix script 2025-06-25 20:13:35 +02:00
Andrey Antukh
207974fe6c Add minor improvement to color cleaning migration 2025-06-25 19:26:43 +02:00
Andrey Antukh
b52e2fa681 🐛 Add missing version field on get-team-shared-files internal query 2025-06-25 19:24:18 +02:00
Andrey Antukh
bf719b587f Add better shadow cleaning migration 2025-06-25 19:17:58 +02:00
Alejandro Alonso
61109c91e3 Merge pull request #6784 from penpot/niwinz-staging-hotfix-3
🐛 Fix incorrect library color cleaning mechanism
2025-06-25 16:21:58 +02:00
Andrey Antukh
4915a97c2c 📎 Add script for restoring lost colors 2025-06-25 16:10:35 +02:00
Andrey Antukh
903aba5642 🐛 Fix incorrect library color cleaning mechanism 2025-06-25 14:36:33 +02:00
Alejandro Alonso
82583f5079 🐛 Fix entering long project name 2025-06-25 14:21:52 +02:00
Alejandro Alonso
4561392791 🐛 Fix shortcut error pressing G+W from the View Mode (#6772) 2025-06-25 14:14:44 +02:00
49 changed files with 777 additions and 313 deletions

129
.github/workflows/build-bundles.yml vendored Normal file
View File

@@ -0,0 +1,129 @@
name: Build and Upload Penpot Bundles non-prod
on:
# Create bundler for every tag
push:
tags:
- '**' # Pattern matched against refs/tags
# Create bundler every hour between 5:00 and 20:00 on working days
schedule:
- cron: '0 5-20 * * 1-5'
# Create bundler from manual action
workflow_dispatch:
inputs:
zip_mode:
# zip_mode defines how the build artifacts are packaged:
# - 'individual': creates one ZIP file per component (frontend, backend, exporter)
# - 'all': creates a single ZIP containing all components
# - null: for the rest of cases (non-manual events)
description: 'Bundle packaging mode'
required: false
default: 'individual'
type: choice
options:
- individual
- all
jobs:
build-bundles:
name: Build and Upload Penpot Bundles
runs-on: ubuntu-24.04
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Extract somer useful variables
id: vars
run: |
echo "commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "gh_branch=${{ github.base_ref || github.ref_name }}" >> $GITHUB_OUTPUT
# Set up Docker Buildx for multi-arch build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Run manage.sh build-bundle from host
run: ./manage.sh build-bundle
- name: Prepare directories for zipping
run: |
mkdir zips
mv bundles penpot
- name: Create zip bundles for zip_mode == 'all'
if: ${{ github.event.inputs.zip_mode == 'all' }}
run: |
echo "📦 Packaging Penpot 'all' bundles..."
zip -r zips/penpot-all-bundles.zip penpot
- name: Create zip bundles for zip_mode != 'all'
if: ${{ github.event.inputs.zip_mode != 'all' }}
run: |
echo "📦 Packaging Penpot 'individual' bundles..."
zip -r zips/penpot-frontend.zip penpot/frontend
zip -r zips/penpot-backend.zip penpot/backend
zip -r zips/penpot-exporter.zip penpot/exporter
- name: Upload unified 'all' bundle
if: ${{ github.event.inputs.zip_mode == 'all' }}
uses: actions/upload-artifact@v4
with:
name: penpot-all-bundles
path: zips/penpot-all-bundles.zip
- name: Upload individual bundles
if: ${{ github.event.inputs.zip_mode != 'all' }}
uses: actions/upload-artifact@v4
with:
name: penpot-individual-bundles
path: |
zips/penpot-frontend.zip
zips/penpot-backend.zip
zips/penpot-exporter.zip
- name: Upload unified 'all' bundle to S3
if: ${{ github.event.inputs.zip_mode == 'all' }}
run: |
aws s3 cp zips/penpot-all-bundles.zip s3://${{ secrets.S3_BUCKET }}/penpot-all-bundles-${{ steps.vars.outputs.gh_branch}}.zip
aws s3 cp zips/penpot-all-bundles.zip s3://${{ secrets.S3_BUCKET }}/penpot-all-bundles-${{ steps.vars.outputs.commit_hash }}.zip
- name: Upload 'individual' bundles to S3
if: ${{ github.event.inputs.zip_mode != 'all' }}
run: |
for name in penpot-frontend penpot-backend penpot-exporter; do
aws s3 cp zips/${name}.zip s3://${{ secrets.S3_BUCKET }}/${name}-${{ steps.vars.outputs.gh_branch }}-latest.zip
aws s3 cp zips/${name}.zip s3://${{ secrets.S3_BUCKET }}/${name}-${{ steps.vars.outputs.commit_hash }}.zip
done
- name: Notify Mattermost about automatic bundles
if: github.event_name == 'pull_request'
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
TEXT: |
📦 *Penpot bundle automatically generated*
📄 PR: ${{ github.event.pull_request.title }}
🔁 From: \`${{ github.head_ref }}\` to \`{{ github.base_ref }}\`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: Notify Mattermost about manual bundles
if: github.event_name == 'workflow_dispatch'
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
TEXT: |
📦 *Penpot bundle manually generated*
📄 Triggered from branch: `${{ github.ref_name}}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: Print artifact summary URL
run: |
echo "📦 Artifacts available at:"
echo "🔗 https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"

View File

@@ -39,6 +39,7 @@ on-premises instances** that want to keep up to date.
- Update google fonts (at 2025/05/19) [Taiga 10792](https://tree.taiga.io/project/penpot/us/10792)
- Add tooltip component to DS [Taiga 9220](https://tree.taiga.io/project/penpot/us/9220)
- Allow multi file token export [Taiga #10144](https://tree.taiga.io/project/penpot/us/10144)
- Fix problem when double click on hidden shapes [Taiga #11314](https://tree.taiga.io/project/penpot/issue/11314)
### :bug: Bugs fixed
@@ -57,7 +58,12 @@ on-premises instances** that want to keep up to date.
- Fix button width [Taiga #11394](https://tree.taiga.io/project/penpot/issue/11394)
- Fix mixed letter spacing and line height [Taiga #11178](https://tree.taiga.io/project/penpot/issue/11178)
- Fix snap nodes shortcut [Taiga #11054](https://tree.taiga.io/project/penpot/issue/11054)
- Fix changing a text property in a text layer does not unapply the previously applied token in the same property [Taiga #11337}(https://tree.taiga.io/project/penpot/issue/11337)
- Fix changing a text property in a text layer does not unapply the previously applied token in the same property [Taiga #11337](https://tree.taiga.io/project/penpot/issue/11337)
- Fix shortcut error pressing G+W from the View Mode [Taiga #11061](https://tree.taiga.io/project/penpot/issue/11061)
- Fix entering long project name [Taiga #11417](https://tree.taiga.io/project/penpot/issue/11417)
- Fix slow color picker [Taiga #11019](https://tree.taiga.io/project/penpot/issue/11019)
- Fix tooltip position after click [Taiga #11405](https://tree.taiga.io/project/penpot/issue/11405)
- Fix incorrect media translation on paste text with fill images [Github #6845](https://github.com/penpot/penpot/pull/6845)
## 2.7.2

View File

@@ -12,7 +12,6 @@
[app.binfile.common :as bfc]
[app.binfile.migrations :as bfm]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.features :as cfeat]
[app.common.files.migrations :as-alias fmg]
@@ -54,7 +53,7 @@
[:map {:title "Manifest"}
[:version ::sm/int]
[:type :string]
[:referer {:optional true} :string]
[:generated-by {:optional true} :string]
[:files
@@ -373,6 +372,7 @@
params {:type "penpot/export-files"
:version 1
:generated-by (str "penpot/" (:full cf/version))
:refer "penpot"
:files (vec (vals files))
:relations rels}]
(write-entry! output "manifest.json" params))))
@@ -878,13 +878,8 @@
(defn- import-files
[{:keys [::bfc/timestamp ::bfc/input ::bfc/name] :or {timestamp (dt/now)} :as cfg}]
(dm/assert!
"expected zip file"
(instance? ZipFile input))
(dm/assert!
"expected valid instant"
(dt/instant? timestamp))
(assert (instance? ZipFile input) "expected zip file")
(assert (dt/instant? timestamp) "expected valid instant")
(let [manifest (-> (read-manifest input)
(validate-manifest))
@@ -896,6 +891,7 @@
:hint "unexpected type on manifest"
:manifest manifest))
;; Check if all files referenced on manifest are present
(doseq [{file-id :id features :features} (:files manifest)]
(let [path (str "files/" file-id ".json")]
@@ -956,14 +952,13 @@
[{:keys [::bfc/ids] :as cfg} output]
(dm/assert!
"expected a set of uuid's for `::bfc/ids` parameter"
(and (set? ids)
(every? uuid? ids)))
(assert
(and (set? ids) (every? uuid? ids))
"expected a set of uuid's for `::bfc/ids` parameter")
(dm/assert!
"expected instance of jio/IOFactory for `input`"
(satisfies? jio/IOFactory output))
(assert
(satisfies? jio/IOFactory output)
"expected instance of jio/IOFactory for `input`")
(let [id (uuid/next)
tp (dt/tpoint)
@@ -1002,14 +997,14 @@
(defn import-files!
[{:keys [::bfc/input] :as cfg}]
(dm/assert!
"expected valid profile-id and project-id on `cfg`"
(assert
(and (uuid? (::bfc/profile-id cfg))
(uuid? (::bfc/project-id cfg))))
(uuid? (::bfc/project-id cfg)))
"expected valid profile-id and project-id on `cfg`")
(dm/assert!
"expected instance of jio/IOFactory for `input`"
(io/coercible? input))
(assert
(io/coercible? input)
"expected instance of jio/IOFactory for `input`")
(let [id (uuid/next)
tp (dt/tpoint)
@@ -1029,3 +1024,9 @@
:id (str id)
:elapsed (dt/format-duration (tp))
:error? (some? @cs))))))
(defn get-manifest
[path]
(with-open [input (ZipFile. (fs/file path))]
(-> (read-manifest input)
(validate-manifest))))

View File

@@ -41,7 +41,7 @@
(if (or (instance? java.util.concurrent.CompletionException cause)
(instance? java.util.concurrent.ExecutionException cause))
(-> record
(assoc ::trace (ex/format-throwable cause :data? false :explain? false :header? false :summary? false))
(assoc ::trace (ex/format-throwable cause :data? true :explain? false :header? false :summary? false))
(assoc ::l/cause (ex-cause cause))
(record->report))
@@ -64,18 +64,18 @@
message))
@message)
:trace (or (::trace record)
(some-> cause (ex/format-throwable :data? false :explain? false :header? false :summary? false)))}
(some-> cause (ex/format-throwable :data? true :explain? false :header? false :summary? false)))}
(when-let [params (or (:request/params context) (:params context))]
{:params (pp/pprint-str params :length 30 :level 13)})
{:params (pp/pprint-str params :length 20 :level 20)})
(when-let [value (:value context)]
{:value (pp/pprint-str value :length 30 :level 12)})
{:value (pp/pprint-str value :length 30 :level 13)})
(when-let [data (some-> data (dissoc ::s/problems ::s/value ::s/spec ::sm/explain :hint))]
{:data (pp/pprint-str data :length 30 :level 12)})
{:data (pp/pprint-str data :length 30 :level 13)})
(when-let [explain (ex/explain data :length 30 :level 12)]
(when-let [explain (ex/explain data :length 30 :level 13)]
{:explain explain})))))
(defn error-record?

View File

@@ -134,11 +134,18 @@
::webhooks/event? true
::sse/stream? true
::sm/params schema:import-binfile}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id project-id version] :as params}]
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id project-id version file] :as params}]
(projects/check-edition-permissions! pool profile-id project-id)
(let [params (-> params
(assoc :profile-id profile-id)
(assoc :version (or version 1)))]
(let [version (or version 1)
params (-> params
(assoc :profile-id profile-id)
(assoc :version version))
manifest (case (int version)
1 nil
3 (bf.v3/get-manifest (:path file)))]
(with-meta
(sse/response (partial import-binfile cfg params))
{::audit/props {:file nil}})))
{::audit/props {:file nil
:generated-by (:generated-by manifest)
:referer (:referer manifest)}})))

View File

@@ -559,7 +559,10 @@
f.project_id,
f.created_at,
f.modified_at,
f.data_backend,
f.data_ref_id,
f.name,
f.version,
f.is_shared,
ft.media_id,
p.team_id

View File

@@ -0,0 +1,88 @@
;; 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.srepl.fixes.lost-colors
"A collection of adhoc fixes scripts."
(:require
[app.binfile.common :as bfc]
[app.common.logging :as l]
[app.common.types.color :as types.color]
[app.db :as db]
[app.srepl.helpers :as h]))
(def sql:get-affected-files
"SELECT fm.file_id AS id FROM file_migration AS fm WHERE fm.name = '0008-fix-library-colors-v2'")
(def sql:get-matching-snapshot
"SELECT * FROM file_change
WHERE file_id = ?
AND created_at <= ?
AND label IS NOT NULL
AND data IS NOT NULL
ORDER BY created_at DESC
LIMIT 2")
(defn get-affected-migration
[conn file-id]
(db/get* conn :file-migration
{:name "0008-fix-library-colors-v2"
:file-id file-id}))
(defn get-last-valid-snapshot
[conn migration]
(let [[snapshot] (db/exec! conn [sql:get-matching-snapshot
(:file-id migration)
(:created-at migration)])]
(when snapshot
(let [snapshot (assoc snapshot :id (:file-id snapshot))]
(bfc/decode-file h/*system* snapshot)))))
(defn restore-color
[{:keys [data] :as snapshot} color]
(when-let [scolor (get-in data [:colors (:id color)])]
(-> (select-keys scolor types.color/library-color-attrs)
(types.color/check-library-color))))
(defn restore-missing-colors
[{:keys [id] :as file} & _opts]
(l/inf :hint "process file" :file-id (str id) :name (:name file) :has-colors (-> file :data :colors not-empty boolean))
(if-let [colors (-> file :data :colors not-empty)]
(let [migration (get-affected-migration h/*system* id)]
(if-let [snapshot (get-last-valid-snapshot h/*system* migration)]
(do
(l/inf :hint "using snapshot" :snapshot (:label snapshot))
(let [colors (reduce-kv (fn [colors color-id color]
(if-let [result (restore-color snapshot color)]
(do
(l/inf :hint "restored color" :file-id (str id) :color-id (str color-id))
(assoc colors color-id result))
(do
(l/wrn :hint "ignoring color" :file-id (str id) :color (pr-str color))
colors)))
colors
colors)
file (-> file
(update :data assoc :colors colors)
(update :migrations disj "0008-fix-library-colors-v2"))]
(db/delete! h/*system* :file-migration
{:name "0008-fix-library-colors-v2"
:file-id (:id file)})
file))
(do
(db/delete! h/*system* :file-migration
{:name "0008-fix-library-colors-v2"
:file-id (:id file)})
nil)))
(do
(db/delete! h/*system* :file-migration
{:name "0008-fix-library-colors-v2"
:file-id (:id file)})
nil)))

View File

@@ -17,6 +17,7 @@
[app.common.files.validate :as cfv]
[app.common.logging :as l]
[app.common.pprint :as p]
[app.common.schema :as sm]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
@@ -395,6 +396,22 @@
libs (bfc/get-resolved-file-libraries system file)]
(cfv/validate-file file libs))))))
(defn validate-file-schema
"Validate structure, referencial integrity and semantic coherence of
all contents of a file. Returns a list of errors."
[file-id]
(let [file-id (h/parse-uuid file-id)]
(db/tx-run! (assoc main/system ::db/rollback true)
(fn [system]
(try
(let [file (bfc/get-file system file-id)]
(cfv/validate-file-schema! file)
(println "OK"))
(catch Exception cause
(if-let [explain (-> cause ex-data ::sm/explain)]
(println (sm/humanize-explain explain))
(ex/print-throwable cause))))))))
(defn repair-file!
"Repair the list of errors detected by validation."
[file-id & {:keys [rollback?] :or {rollback? true} :as opts}]
@@ -474,7 +491,8 @@
:index idx)
(let [system (assoc main/system ::db/rollback rollback?)]
(db/tx-run! system (fn [system]
(binding [h/*system* system]
(binding [h/*system* system
db/*conn* (db/get-connection system)]
(h/process-file! system file-id update-fn opts)))))
(catch Throwable cause

View File

@@ -47,10 +47,26 @@
`(try ~@exprs (catch Throwable e# nil))))
(defmacro try!
[& exprs]
(if (:ns &env)
`(try ~@exprs (catch :default e# e#))
`(try ~@exprs (catch Throwable e# e#))))
[expr & {:keys [reraise-with on-exception]}]
(let [ex-sym
(gensym "exc")
generate-catch
(fn []
(cond
(map? reraise-with)
`(ex/raise ~@(mapcat identity reraise-with) :cause ~ex-sym)
on-exception
`(let [handler# ~on-exception]
(handler# ~ex-sym))
:else
ex-sym))]
(if (:ns &env)
`(try ~expr (catch :default ~ex-sym ~(generate-catch)))
`(try ~expr (catch Throwable ~ex-sym ~(generate-catch))))))
(defn ex-info?
[v]

View File

@@ -487,7 +487,9 @@
(cts/shape? shape-new))
(ex/raise :type :assertion
:code :data-validation
:hint "invalid shape found after applying changes"
:hint (str "invalid shape found after applying changes on file "
(:id data-new))
:file-id (:id data-new)
::sm/explain (cts/explain-shape shape-new))))))]
(->> (into #{} (map :page-id) items)

View File

@@ -855,7 +855,7 @@
(update-object [object]
(if (cfh/text-shape? object)
(update object :content #(txt/transform-nodes identity update-text-node %))
(update object :content #(txt/transform-nodes txt/is-content-node? update-text-node %))
object))
(update-container [container]
@@ -1105,7 +1105,7 @@
;; The text shape also can has fills on the text
;; fragments so we need to fix fills there
(cond-> (cfh/text-shape? object)
(update :content (partial txt/transform-nodes identity fix-fills)))))
(update :content (partial txt/transform-nodes txt/is-content-node? fix-fills)))))
(update-container [container]
(d/update-when container :objects d/update-vals update-object))]
@@ -1277,22 +1277,21 @@
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0002-normalize-bool-content"
(defmethod migrate-data "0002-normalize-bool-content-v2"
[data _]
(letfn [(update-object [object]
;; NOTE: we still preserve the previous value for possible
;; rollback, we still need to perform an other migration
;; for properly delete the bool-content prop from shapes
;; once the know the migration was OK
(if (and (cfh/bool-shape? object)
(not (contains? object :content)))
(if-let [content (:bool-content object)]
(assoc object :content content)
object)
(if (cfh/bool-shape? object)
(if (contains? object :content)
(dissoc object :bool-content)
(let [content (:bool-content object)]
(-> object
(assoc :content content)
(dissoc :bool-content))))
(dissoc object :bool-content :bool-type)))
(update-container [container]
(d/update-when container :objects update-vals update-object))]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index d/update-vals update-container)
@@ -1320,49 +1319,71 @@
(d/update-when :components d/update-vals update-container)
(d/without-nils))))
(defmethod migrate-data "0003-convert-path-content"
(defmethod migrate-data "0003-convert-path-content-v2"
[data _]
(some-> cfeat/*new* (swap! conj "fdata/path-data"))
(letfn [(update-object [object]
(if (or (cfh/bool-shape? object)
(cfh/path-shape? object))
(update object :content path/content)
object))
(let [decode-segments
(sm/decoder path/schema:segments sm/json-transformer)
(update-container [container]
(d/update-when container :objects update-vals update-object))]
update-object
(fn [object]
(if (or (cfh/bool-shape? object)
(cfh/path-shape? object))
(let [content (get object :content)
content (cond
(path/content? content)
content
(nil? content)
(path/content [])
:else
(-> content
(decode-segments)
(path/content)))]
(assoc object :content content))
object))
update-container
(fn [container]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0004-clean-shadow-and-colors"
(defmethod migrate-data "0004-clean-shadow-color"
[data _]
(letfn [(clean-shadow [shadow]
(update shadow :color (fn [color]
(let [ref-id (get color :id)
ref-file (get color :file-id)]
(-> (d/without-qualified color)
(select-keys [:opacity :color :gradient :image :ref-id :ref-file])
(cond-> ref-id
(assoc :ref-id ref-id))
(cond-> ref-file
(assoc :ref-file ref-file)))))))
(let [decode-color (sm/decoder types.color/schema:color sm/json-transformer)
(update-object [object]
(d/update-when object :shadow #(mapv clean-shadow %)))
clean-shadow-color
(fn [color]
(let [ref-id (get color :id)
ref-file (get color :file-id)]
(-> (d/without-qualified color)
(select-keys [:opacity :color :gradient :image :ref-id :ref-file])
(cond-> ref-id
(assoc :ref-id ref-id))
(cond-> ref-file
(assoc :ref-file ref-file))
(decode-color))))
(update-container [container]
(d/update-when container :objects d/update-vals update-object))
clean-shadow
(fn [shadow]
(update shadow :color clean-shadow-color))
(clean-library-color [color]
(dissoc color :file-id))]
update-object
(fn [object]
(d/update-when object :shadow #(mapv clean-shadow %)))
update-container
(fn [container]
(d/update-when container :objects d/update-vals update-object))]
(-> data
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container)
(d/update-when :colors d/update-vals clean-library-color))))
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0005-deprecate-image-type"
[data _]
@@ -1402,7 +1423,7 @@
(update-object [object]
(if (cfh/text-shape? object)
(update object :content (partial txt/transform-nodes identity fix-fills))
(update object :content (partial txt/transform-nodes txt/is-content-node? fix-fills))
object))
(update-container [container]
@@ -1480,7 +1501,7 @@
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0008-fix-library-colors-v2"
(defmethod migrate-data "0008-fix-library-colors-v4"
[data _]
(letfn [(clear-color-opacity [color]
(if (and (contains? color :opacity)
@@ -1491,9 +1512,8 @@
(clear-color [color]
(-> color
(select-keys types.color/library-color-attrs)
(d/without-nils)
(d/without-qualified)
(clear-color-opacity)))]
(clear-color-opacity)
(d/without-nils)))]
(d/update-when data :colors d/update-vals clear-color)))
@@ -1552,12 +1572,12 @@
"legacy-66"
"legacy-67"
"0001-remove-tokens-from-groups"
"0002-normalize-bool-content"
"0002-normalize-bool-content-v2"
"0002-clean-shape-interactions"
"0003-fix-root-shape"
"0003-convert-path-content"
"0004-clean-shadow-and-colors"
"0003-convert-path-content-v2"
"0004-clean-shadow-color"
"0005-deprecate-image-type"
"0006-fix-old-texts-fills"
"0007-clear-invalid-strokes-and-fills-v2"
"0008-fix-library-colors-v2"]))
"0008-fix-library-colors-v4"]))

View File

@@ -130,8 +130,8 @@
(defn is-text-node?
[node]
(and (string? (:text node))
(not= (:text node) "")))
(and (nil? (:type node))
(string? (:text node))))
(defn is-paragraph-set-node?
[node]
@@ -170,19 +170,6 @@
item))
root)))
(defn xform-nodes
"The same as transform but instead of receiving a funcion, receives
a transducer."
[xf root]
(let [rf (fn [_ v] v)]
(walk/postwalk
(fn [item]
(let [rf (xf rf)]
(if (is-node? item)
(d/nilv (rf nil item) item)
item)))
root)))
(defn update-text-content
[shape pred-fn update-fn attrs]
(let [update-attrs-fn #(update-fn % attrs)

View File

@@ -151,7 +151,7 @@
[:fn has-valid-color-attrs?]])
(def library-color-attrs
(sm/keys schema:library-color-attrs))
(into required-color-attrs (sm/keys schema:library-color-attrs)))
(def valid-color?
(sm/lazy-validator schema:color))

View File

@@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.files.helpers :as cpf]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
@@ -26,6 +27,9 @@
(def ^:const bool-style-properties bool/style-properties)
(def ^:const default-bool-fills bool/default-fills)
(def schema:content impl/schema:content)
(def schema:segments impl/schema:segments)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TRANSFORMATIONS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -48,9 +52,9 @@
[data]
(impl/from-string data))
(defn check-path-content
(defn check-content
[content]
(impl/check-content-like content))
(impl/check-content content))
(defn get-byte-size
"Get byte size of a path content"
@@ -76,7 +80,7 @@
(defn apply-content-modifiers
"Apply delta modifiers over the path content"
[content modifiers]
(assert (impl/check-content-like content))
(assert (impl/check-content content))
(letfn [(apply-to-index [content [index params]]
(if (contains? content index)
@@ -196,7 +200,18 @@
contents
(sequence extract-content-xf (:shapes shape))]
(bool/calculate-content (:bool-type shape) contents)))
(ex/try!
(bool/calculate-content (:bool-type shape) contents)
:on-exception
(fn [cause]
(ex/raise :type :internal
:code :invalid-path-content
:hint (str "unable to calculate bool content for shape " (:id shape))
:shapes (:shapes shape)
:type (:bool-type shape)
:content (vec contents)
:cause cause)))))
(defn calc-bool-content
"Calculate the boolean content from shape and objects. Returns a

View File

@@ -412,7 +412,7 @@
(defn calculate-content
"Create a bool content from a collection of contents and specified
type."
type. Returns plain segments"
[bool-type contents]
;; We apply the boolean operation in to each pair and the result to the next
;; element

View File

@@ -27,13 +27,11 @@
(defn make-move-to [to]
{:command :move-to
:relative false
:params {:x (:x to)
:y (:y to)}})
(defn make-line-to [to]
{:command :line-to
:relative false
:params {:x (:x to)
:y (:y to)}})
@@ -65,7 +63,6 @@
(defn make-curve-to
[to h1 h2]
{:command :curve-to
:relative false
:params (make-curve-params to h1 h2)})
(defn prefix->coords [prefix]
@@ -98,7 +95,7 @@
(defn segment->point
([segment] (segment->point segment :x))
([segment coord]
(when-let [params (get segment :params)]
(when-let [params (not-empty (get segment :params))]
(case coord
:c1 (gpt/point (get params :c1x)
(get params :c1y))

View File

@@ -507,18 +507,6 @@
(= (:command e1) :move-to))))}
schema:segment])
(def schema:content-like
[:sequential schema:segment])
(def check-content-like
(sm/check-fn schema:content-like))
(def check-segment
(sm/check-fn schema:segment))
(def ^:private check-segments
(sm/check-fn schema:segments))
(defn path-data?
[o]
(instance? PathData o))
@@ -526,37 +514,40 @@
(declare from-string)
(declare from-plain)
;; Mainly used on backend: features/components_v2.clj
(sm/register! ::path/segment schema:segment)
(sm/register! ::path/segments schema:segments)
(def schema:content
(sm/type-schema
{:type ::path/content
:compile
(fn [_ _ _]
(let [decoder (delay (sm/decoder schema:segments sm/json-transformer))
generator (->> (sg/generator schema:segments)
(sg/filter not-empty)
(sg/fmap from-plain))]
{:pred path-data?
:type-properties
{:gen/gen generator
:encode/json identity
:decode/json (fn [s]
(cond
(string? s)
(from-string s)
(sm/register!
{:type ::path/content
:compile
(fn [_ _ _]
(let [decoder (delay (sm/decoder schema:segments sm/json-transformer))
generator (->> (sg/generator schema:segments)
(sg/filter not-empty)
(sg/fmap from-plain))]
{:pred path-data?
:type-properties
{:gen/gen generator
:encode/json identity
:decode/json (fn [s]
(cond
(string? s)
(from-string s)
(vector? s)
(let [decode-fn (deref decoder)]
(-> (decode-fn s)
(from-plain)))
(vector? s)
(let [decode-fn (deref decoder)]
(-> (decode-fn s)
(from-plain)))
:else
s))}}))}))
:else
s))}}))})
(def check-plain-content
(sm/check-fn schema:segments))
(def check-path-content
(sm/check-fn ::path/content))
(def check-segment
(sm/check-fn schema:segment))
(def check-content
(sm/check-fn schema:content))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; CONSTRUCTORS & PREDICATES
@@ -617,7 +608,7 @@
(defn from-plain
"Create a PathData instance from plain data structures"
[segments]
(assert (check-segments segments))
(assert (check-plain-content segments))
(let [total (count segments)
buffer (buf/allocate (* total SEGMENT-BYTE-SIZE))]

View File

@@ -246,7 +246,7 @@
[:map {:title "BoolAttrs"}
[:shapes [:vector {:gen/max 10 :gen/min 1} ::sm/uuid]]
[:bool-type [::sm/one-of bool-types]]
[:content ::path/content]])
[:content path/schema:content]])
(def ^:private schema:rect-attrs
[:map {:title "RectAttrs"}])
@@ -271,7 +271,7 @@
(def ^:private schema:path-attrs
[:map {:title "PathAttrs"}
[:content ::path/content]])
[:content path/schema:content]])
(def ^:private schema:text-attrs
[:map {:title "TextAttrs"}

View File

@@ -1212,7 +1212,8 @@ Will return a value that matches this schema:
"Searches through decoded token file and returns:
- `:json-format/legacy` when first node satisfies `legacy-node?` predicate
- `:json-format/dtcg` when first node satisfies `dtcg-node?` predicate
- `nil` if neither combination is found"
- If neither combination is found, return dtcg format by default (we assume that
the file does not contain any token, so the format is irrelevan)."
([decoded-json]
(get-json-format decoded-json legacy-node? dtcg-node?))
([decoded-json legacy-node? dtcg-node?]
@@ -1230,9 +1231,10 @@ Will return a value that matches this schema:
(check-node node)
(when (branch? node)
(mapcat walk (children node))))))]
(->> (walk decoded-json)
(filter some?)
first)))) ;; TODO: throw error if format cannot be determined
(d/nilv (->> (walk decoded-json)
(filter some?)
first)
:json-format/dtcg))))
(defn- legacy-json->dtcg-json
"Converts a decoded json file in legacy format into DTCG format."
@@ -1291,9 +1293,17 @@ Will return a value that matches this schema:
[set-name decoded-json-tokens]
(assert (map? decoded-json-tokens) "expected a plain clojure map for `decoded-json-tokens`")
(assert (= (get-json-format decoded-json-tokens) :json-format/dtcg) "expected a dtcg format for `decoded-json-tokens`")
(-> (make-tokens-lib)
(add-set (make-token-set :name (normalize-set-name set-name)
:tokens (flatten-nested-tokens-json decoded-json-tokens "")))))
(let [set-name (normalize-set-name set-name)
tokens (flatten-nested-tokens-json decoded-json-tokens "")]
(when (empty? tokens)
(throw (ex-info "the file doesn't contain any tokens"
{:error/code :error.import/invalid-json-data})))
(-> (make-tokens-lib)
(add-set (make-token-set :name set-name
:tokens tokens)))))
(defn- parse-single-set-legacy-json
"Parse a decoded json file with a single set of tokens in legacy format into a TokensLib."
@@ -1385,6 +1395,10 @@ Will return a value that matches this schema:
library
active-theme-names)]
(when (and (empty? sets) (empty? themes))
(throw (ex-info "the file doesn't contain any tokens"
{:error/code :error.import/invalid-json-data})))
library))
(defn- parse-multi-set-legacy-json

View File

@@ -14,6 +14,7 @@
[app.common.pprint :as pp]
[app.common.transit :as trans]
[app.common.types.path :as path]
[app.common.types.path.bool :as path.bool]
[app.common.types.path.helpers :as path.helpers]
[app.common.types.path.impl :as path.impl]
[app.common.types.path.segment :as path.segment]
@@ -414,3 +415,89 @@
result1 (get-handlers sample-content-large)
result2 (path.segment/get-handlers content)]
(t/is (= result1 result2))))
(def contents-for-bool
[[{:command :move-to, :params {:x 1682.9000244140625, :y 48.0}}
{:command :line-to, :params {:x 1682.9000244140625, :y 44.0}}
{:command :curve-to, :params {:x 1683.9000244140625, :y 43.0, :c1x 1682.9000244140625, :c1y 43.400001525878906, :c2x 1683.300048828125, :c2y 43.0}}
{:command :line-to, :params {:x 1687.9000244140625, :y 43.0}}
{:command :curve-to, :params {:x 1688.9000244140625, :y 44.0, :c1x 1688.5, :c1y 43.0, :c2x 1688.9000244140625, :c2y 43.400001525878906}}
{:command :line-to, :params {:x 1688.9000244140625, :y 48.0}}
{:command :curve-to, :params {:x 1687.9000244140625, :y 49.0, :c1x 1688.9000244140625, :c1y 48.599998474121094, :c2x 1688.5, :c2y 49.0}}
{:command :line-to, :params {:x 1683.9000244140625, :y 49.0}}
{:command :curve-to, :params {:x 1682.9000244140625, :y 48.0, :c1x 1683.300048828125, :c1y 49.0, :c2x 1682.9000244140625, :c2y 48.599998474121094}}
{:command :close-path, :params {}}
{:command :close-path, :params {}}
{:command :move-to, :params {:x 1684.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1684.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1686.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1686.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1684.9000244140625, :y 45.0}}
{:command :close-path, :params {}}
{:command :close-path, :params {}}]
[{:command :move-to, :params {:x 1672.9000244140625, :y 48.0}}
{:command :line-to, :params {:x 1672.9000244140625, :y 44.0}}
{:command :curve-to, :params {:x 1673.9000244140625, :y 43.0, :c1x 1672.9000244140625, :c1y 43.400001525878906, :c2x 1673.300048828125, :c2y 43.0}}
{:command :line-to, :params {:x 1677.9000244140625, :y 43.0}}
{:command :curve-to, :params {:x 1678.9000244140625, :y 44.0, :c1x 1678.5, :c1y 43.0, :c2x 1678.9000244140625, :c2y 43.400001525878906}}
{:command :line-to, :params {:x 1678.9000244140625, :y 48.0}}
{:command :curve-to, :params {:x 1677.9000244140625, :y 49.0, :c1x 1678.9000244140625, :c1y 48.599998474121094, :c2x 1678.5, :c2y 49.0}}
{:command :line-to, :params {:x 1673.9000244140625, :y 49.0}}
{:command :curve-to, :params {:x 1672.9000244140625, :y 48.0, :c1x 1673.300048828125, :c1y 49.0, :c2x 1672.9000244140625, :c2y 48.599998474121094}}
{:command :close-path, :params {}}
{:command :close-path, :params {}}
{:command :move-to, :params {:x 1674.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1674.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1676.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1676.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1674.9000244140625, :y 45.0}}
{:command :close-path, :params {}}
{:command :close-path, :params {}}]])
(def bool-result
[{:command :move-to, :params {:x 1682.9000244140625, :y 48.0}}
{:command :line-to, :params {:x 1682.9000244140625, :y 44.0}}
{:command :curve-to,
:params
{:x 1683.9000244140625, :y 43.0, :c1x 1682.9000244140625, :c1y 43.400001525878906, :c2x 1683.300048828125, :c2y 43.0}}
{:command :line-to, :params {:x 1687.9000244140625, :y 43.0}}
{:command :curve-to,
:params {:x 1688.9000244140625, :y 44.0, :c1x 1688.5, :c1y 43.0, :c2x 1688.9000244140625, :c2y 43.400001525878906}}
{:command :line-to, :params {:x 1688.9000244140625, :y 48.0}}
{:command :curve-to,
:params {:x 1687.9000244140625, :y 49.0, :c1x 1688.9000244140625, :c1y 48.599998474121094, :c2x 1688.5, :c2y 49.0}}
{:command :line-to, :params {:x 1683.9000244140625, :y 49.0}}
{:command :curve-to,
:params
{:x 1682.9000244140625, :y 48.0, :c1x 1683.300048828125, :c1y 49.0, :c2x 1682.9000244140625, :c2y 48.599998474121094}}
{:command :move-to, :params {:x 1684.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1684.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1686.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1686.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1684.9000244140625, :y 45.0}}
{:command :move-to, :params {:x 1672.9000244140625, :y 48.0}}
{:command :line-to, :params {:x 1672.9000244140625, :y 44.0}}
{:command :curve-to,
:params
{:x 1673.9000244140625, :y 43.0, :c1x 1672.9000244140625, :c1y 43.400001525878906, :c2x 1673.300048828125, :c2y 43.0}}
{:command :line-to, :params {:x 1677.9000244140625, :y 43.0}}
{:command :curve-to,
:params {:x 1678.9000244140625, :y 44.0, :c1x 1678.5, :c1y 43.0, :c2x 1678.9000244140625, :c2y 43.400001525878906}}
{:command :line-to, :params {:x 1678.9000244140625, :y 48.0}}
{:command :curve-to,
:params {:x 1677.9000244140625, :y 49.0, :c1x 1678.9000244140625, :c1y 48.599998474121094, :c2x 1678.5, :c2y 49.0}}
{:command :line-to, :params {:x 1673.9000244140625, :y 49.0}}
{:command :curve-to,
:params
{:x 1672.9000244140625, :y 48.0, :c1x 1673.300048828125, :c1y 49.0, :c2x 1672.9000244140625, :c2y 48.599998474121094}}
{:command :move-to, :params {:x 1674.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1674.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1676.9000244140625, :y 47.0}}
{:command :line-to, :params {:x 1676.9000244140625, :y 45.0}}
{:command :line-to, :params {:x 1674.9000244140625, :y 45.0}}])
(t/deftest calculate-bool-content
(let [result (path.bool/calculate-content :union contents-for-bool)]
(t/is (= result bool-result))))

View File

@@ -113,10 +113,10 @@
{:num 500})))
(t/deftest shape-path-content-json-roundtrip
(let [encode (sm/encoder ::path/content (sm/json-transformer))
decode (sm/decoder ::path/content (sm/json-transformer))]
(let [encode (sm/encoder path/schema:content (sm/json-transformer))
decode (sm/decoder path/schema:content (sm/json-transformer))]
(smt/check!
(smt/for [path-content (sg/generator ::path/content)]
(smt/for [path-content (sg/generator path/schema:content)]
(let [path-content-1 (encode path-content)
path-content-2 (json-roundtrip path-content-1)
path-content-3 (decode path-content-2)]

View File

@@ -404,4 +404,4 @@ COPY files/entrypoint.sh /home/entrypoint.sh
COPY files/init.sh /home/init.sh
ENTRYPOINT ["/home/entrypoint.sh"]
CMD ["/home/init.sh"]
CMD ["/home/init.sh"]

View File

@@ -15,6 +15,9 @@
(def grid-x-axis 10)
(def grid-y-axis 10)
(def sidebar-default-width 318)
(def sidebar-default-max-width 768)
(def page-metadata
"Default data for page metadata."
{:grid-x-axis grid-x-axis

View File

@@ -9,6 +9,7 @@
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.main.data.changes :as dch]
[app.main.data.event :as ev]
[app.main.data.modal :as modal]
[app.main.data.notifications :as ntf]
[app.main.store :as st]
@@ -144,8 +145,13 @@
(watch [_ state _]
(let [user-can-edit? (dm/get-in state [:permissions :can-edit])]
(when-let [pid (::open-plugin state)]
(open-plugin! (preg/get-plugin pid) user-can-edit?)
(rx/of #(dissoc % ::open-plugin)))))))
(let [plugin (preg/get-plugin pid)]
(open-plugin! plugin user-can-edit?)
(rx/of (ev/event {::ev/name "start-plugin"
::ev/origin "workspace"
:name (:name plugin)
:host (:host plugin)})
#(dissoc % ::open-plugin))))))))
(defn- update-plugin-permissions-peek
[{:keys [plugin-id url]}]

View File

@@ -172,7 +172,9 @@
current)]
(case current
"dark" "light"
"light" "dark")))))
"light" "dark"
; Failsafe for missing data
"dark")))))
ptk/WatchEvent
(watch [it state _]

View File

@@ -673,41 +673,35 @@
(defn paste-shapes
[{in-viewport? :in-viewport :as pdata}]
(letfn [(translate-media [mdata media-idx attr-path]
(let [id (-> (get-in mdata attr-path)
(:id))
(letfn [(translate-media [mdata media-idx attr]
(let [id (-> (get mdata attr) :id)
mobj (get media-idx id)]
(if mobj
(if (empty? attr-path)
(assoc mdata :id (:id mobj))
(update-in mdata attr-path assoc :id (:id mobj)))
(update mdata attr assoc :id (:id mobj))
mdata)))
(add-obj? [chg]
(= (:type chg) :add-obj))
(process-rchange-shape [obj media-idx]
(let [translate-fill-image #(translate-media % media-idx :fill-image)
translate-stroke-image #(translate-media % media-idx :stroke-image)
translate-fills #(mapv translate-fill-image %)
translate-strokes #(mapv translate-stroke-image %)
process-text-node #(d/update-when % :fills translate-fills)]
(-> obj
(update :fills translate-fills)
(update :strokes translate-strokes)
(d/update-when :content #(txt/transform-nodes process-text-node %))
(d/update-when :position-data #(mapv process-text-node %)))))
;; Analyze the rchange and replace staled media and
;; references to the new uploaded media-objects.
(process-rchange [media-idx change]
(let [;; Texts can have different fills for pieces of the text
tr-fill-xf (map #(translate-media % media-idx [:fill-image]))
tr-stroke-xf (map #(translate-media % media-idx [:stroke-image]))]
(if (add-obj? change)
(update change :obj (fn [obj]
(-> obj
(update :fills #(into [] tr-fill-xf %))
(update :strokes #(into [] tr-stroke-xf %))
(d/update-when :metadata translate-media media-idx [])
(d/update-when :fill-image translate-media media-idx [])
(d/update-when :content
(fn [content]
(txt/xform-nodes tr-fill-xf content)))
(d/update-when :position-data
(fn [position-data]
(mapv (fn [pos-data]
(update pos-data :fills #(into [] tr-fill-xf %)))
position-data))))))
change)))
(if (add-obj? change)
(update change :obj process-rchange-shape media-idx)
change))
(calculate-paste-position [state pobjects selected position]
(let [page-objects (dsh/lookup-page-objects state)
@@ -909,16 +903,17 @@
(ev/event {::ev/name "use-library-component"
::ev/origin origin
:is-external-library external-lib?
:parent-shape-type parent-type})
:type (get shape :type)
:parent-type parent-type})
(if (cfh/has-layout? objects (:parent-id shape))
(ev/event {::ev/name "layout-add-element"
::ev/origin origin
:element-type (get shape :type)
:type (get shape :type)
:parent-type parent-type})
(ev/event {::ev/name "create-shape"
::ev/origin origin
:shape-type (get shape :type)
:parent-shape-type parent-type})))))))
:type (get shape :type)
:parent-type parent-type})))))))
(rx/of (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)

View File

@@ -148,13 +148,14 @@
"Given the current layout flags, and updates them with the data
stored in Storage."
[layout]
(reduce (fn [layout [flag key]]
(condp = (get storage/user key ::none)
::none layout
false (disj layout flag)
true (conj layout flag)))
layout
layout-flags-persistence-mapping))
(let [layout (set (or layout #{}))]
(reduce-kv (fn [layout flag key]
(condp = (get storage/user key ::none)
::none layout
false (disj layout flag)
true (conj layout flag)))
layout
layout-flags-persistence-mapping)))
(defn persist-layout-flags!
"Given a set of layout flags, and persist a subset of them to the Storage."

View File

@@ -19,8 +19,8 @@
"Generates changes to update the new content of the shape"
[it objects page-id shape old-content new-content]
(assert (path/check-path-content old-content))
(assert (path/check-path-content new-content))
(assert (path/content? old-content))
(assert (path/content? new-content))
(let [shape-id (:id shape)

View File

@@ -305,7 +305,7 @@
ptk/UpdateEvent
(update [_ state]
(let [content (some-> (dm/get-in state [:workspace-drawing :object :content])
(path/check-path-content))]
(path/check-content))]
(if (> (count content) 1)
(assoc-in state [:workspace-drawing :object :initialized?] true)
state)))

View File

@@ -493,16 +493,17 @@
(ev/event {::ev/name "use-library-component"
::ev/origin origin
:is-external-library external-lib?
:parent-shape-type parent-type})
:type (get shape :type)
:parent-type parent-type})
(if (cfh/has-layout? objects (:parent-id shape))
(ev/event {::ev/name "layout-add-element"
::ev/origin origin
:element-type (get shape :type)
:type (get shape :type)
:parent-type parent-type})
(ev/event {::ev/name "create-shape"
::ev/origin origin
:shape-type (get shape :type)
:parent-shape-type parent-type})))))))
:type (get shape :type)
:parent-type parent-type})))))))
;; Warning: This order is important for the focus mode.
(->> (rx/of

View File

@@ -129,7 +129,10 @@
(pcb/set-undo-group (:id shape)))
undo-id
(js/Symbol)]
(js/Symbol)
parent-type
(cfh/get-shape-type objects (:parent-id shape))]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id)
@@ -146,12 +149,13 @@
(rx/of (ev/event {::ev/name "create-shape"
::ev/origin "workspace:add-shape"
:type (get shape :type)
:parent-type (cfh/get-shape-type objects (:parent-id shape))}))
:parent-type parent-type}))
(when (cfh/has-layout? objects (:parent-id shape))
(rx/of (ev/event {::ev/name "layout-add-element"
::ev/origin "workspace:add-shape"
:element-type (get shape :type)})))))))))
:type (get shape :type)
:parent-type parent-type})))))))))
(defn move-shapes-into-frame
[frame-id shapes]

View File

@@ -86,7 +86,8 @@
(when-not (str/empty? name)
(st/emit! (-> (dd/rename-project (assoc project :name name))
(with-meta {::ev/origin "project"}))))
(swap! local assoc :edition false)))}]
(swap! local assoc :edition false)))
:max-length 250}]
[:div {:class (stl/css :dashboard-title)}
[:h1 {:on-double-click on-edit
:data-testid "project-title"

View File

@@ -414,7 +414,8 @@
[:div {:class (stl/css :item-info)}
(if (and (= file-id (:file-id state)) (:edition state))
[:& inline-edition {:content (:name file)
:on-end edit}]
:on-end edit
:max-length 250}]
[:h3 (:name file)])
[:& grid-item-metadata {:modified-at (:modified-at file)}]]

View File

@@ -13,7 +13,7 @@
[rumext.v2 :as mf]))
(mf/defc inline-edition
[{:keys [content on-end] :as props}]
[{:keys [content on-end max-length] :as props}]
(let [name (mf/use-state content)
input-ref (mf/use-ref)
@@ -61,13 +61,14 @@
(dom/select-text! node))))
[:div {:class (stl/css :edit-wrapper)}
[:input {:class (stl/css :element-title)
:value @name
:ref input-ref
:on-click on-click
:on-change on-input
[:input {:class (stl/css :element-title)
:value @name
:ref input-ref
:on-click on-click
:on-change on-input
:on-key-down on-keyup
:on-blur on-blur}]
:on-blur on-blur
:max-length max-length}]
[:span {:class (stl/css :close)
:on-click on-cancel} i/close]]))

View File

@@ -220,7 +220,8 @@
[:div {:class (stl/css :project-name-wrapper)}
(if (:edition @local)
[:& inline-edition {:content (:name project)
:on-end on-edit}]
:on-end on-edit
:max-length 250}]
[:h2 {:on-click on-nav
:style {:max-width (str title-width "%")}
:class (stl/css :project-name)

View File

@@ -36,8 +36,6 @@
(defn- hide-popover
[node]
(dom/unset-css-property! node "block-size")
(dom/unset-css-property! node "display")
(.hidePopover ^js node))
(defn- calculate-placement-bounding-rect
@@ -159,11 +157,9 @@
(let [tooltip-brect (dom/get-bounding-rect tooltip)
window-size (dom/get-window-size)]
(when-let [[placement placement-rect] (find-matching-placement placement tooltip-brect origin-brect window-size offset)]
(let [height (if (or (= placement "right") (= placement "left"))
(- (:height placement-rect) arrow-height)
(:height placement-rect))]
(dom/set-css-property! tooltip "display" "grid")
(dom/set-css-property! tooltip "block-size" (dm/str height "px"))
(dom/set-css-property! tooltip "inset-block-start" (dm/str (:top placement-rect) "px"))
(dom/set-css-property! tooltip "inset-inline-start" (dm/str (:left placement-rect) "px")))
@@ -212,8 +208,9 @@
update-position
(fn []
(let [placement (update-tooltip-position tooltip placement origin-brect offset)]
(reset! placement* placement)))]
(let [new-placement (update-tooltip-position tooltip placement origin-brect offset)]
(when (not= new-placement placement)
(reset! placement* new-placement))))]
(add-schedule schedule-ref delay update-position)))))
@@ -234,7 +231,7 @@
tooltip-class
(stl/css-case
:tooltip true
:tooltip-content-wrapper true
:tooltip-top (identical? placement "top")
:tooltip-bottom (identical? placement "bottom")
:tooltip-left (identical? placement "left")
@@ -260,10 +257,11 @@
[:> :div props
children
[:div {:class [class tooltip-class]
[:div {:class [class (stl/css :tooltip)]
:id id
:popover "auto"
:role "tooltip"}
[:div {:class (stl/css :tooltip-content)} content]
[:div {:class (stl/css :tooltip-arrow)
:id "tooltip-arrow"}]]]))
[:div {:class tooltip-class}
[:div {:class (stl/css :tooltip-content)} content]
[:div {:class (stl/css :tooltip-arrow)
:id "tooltip-arrow"}]]]]))

View File

@@ -19,6 +19,12 @@ $arrow-side: 12px;
block-size: fit-content;
}
.tooltip-content-wrapper {
display: grid;
inline-size: fit-content;
block-size: fit-content;
}
.tooltip-arrow {
background-color: var(--color-background-primary);
border-radius: var(--sp-xs);

View File

@@ -8,6 +8,7 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.main.constants :refer [sidebar-default-width sidebar-default-max-width]]
[app.main.data.common :as dcm]
[app.main.data.event :as ev]
[app.main.data.workspace :as dw]
@@ -210,7 +211,7 @@
(= current-section :code)))
{:keys [on-pointer-down on-lost-pointer-capture on-pointer-move set-size size]}
(use-resize-hook :code 276 276 768 :x true :right)
(use-resize-hook :code sidebar-default-width sidebar-default-width sidebar-default-max-width :x true :right)
on-change-section
(mf/use-fn
@@ -224,7 +225,7 @@
(mf/use-fn
(mf/deps size)
(fn []
(set-size (if (> size 276) 276 768))))
(set-size (if (> size sidebar-default-width) sidebar-default-width sidebar-default-max-width))))
props
(mf/spread-props props
@@ -235,12 +236,12 @@
[:aside
{:class (stl/css-case :right-settings-bar true
:not-expand (not can-be-expanded?)
:expanded (> size 276))
:expanded (> size sidebar-default-width))
:id "right-sidebar-aside"
:data-testid "right-sidebar"
:data-size (str size)
:style {"--width" (if can-be-expanded? (dm/str size "px") "276px")}}
:style {"--width" (if can-be-expanded? (dm/str size "px") (dm/str sidebar-default-width "px"))}}
(when can-be-expanded?
[:div {:class (stl/css :resize-area)

View File

@@ -286,7 +286,8 @@
(fn [mod? ids]
(let [sorted-ids
(into (d/ordered-set)
(comp (remove #(dm/get-in objects [% :blocked]))
(comp (remove (partial cfh/hidden-parent? objects))
(remove #(dm/get-in objects [% :blocked]))
(remove (partial cfh/svg-raw-shape? objects)))
(ctt/sort-z-index objects ids {:bottom-frames? mod?}))]
(mf/set-ref-val! sorted-ids-cache (assoc cached-ids [mod? ids] sorted-ids))
@@ -355,7 +356,6 @@
hover-shape
(->> ids
(remove remove-hover?)
(remove (partial cfh/hidden-parent? objects))
(remove #(and mod? (no-fill-nested-frames? %)))
(filter #(or (empty? focus) (cpf/is-in-focus? objects focus %)))
(first)
@@ -366,7 +366,6 @@
(when show-measures?
(->> ids
(remove remove-measure?)
(remove (partial cfh/hidden-parent? objects))
(remove #(and mod? (no-fill-nested-frames? %)))
(filter #(or (empty? focus) (cpf/is-in-focus? objects focus %)))
(first)

View File

@@ -50,6 +50,41 @@
(obj/set! internal-state "canvas" new-canvas)
new-canvas))))))))
(defn process-pointer-move [viewport-node canvas canvas-image-data zoom-view-context client-x client-y]
(when-let [image-data (mf/ref-val canvas-image-data)]
(when-let [zoom-view-node (dom/get-element "picker-detail")]
(when-not (mf/ref-val zoom-view-context)
(mf/set-ref-val! zoom-view-context (.getContext zoom-view-node "2d")))
(let [canvas-width 260
canvas-height 140
{brx :left bry :top} (dom/get-bounding-rect viewport-node)
x (mth/floor (- client-x brx))
y (mth/floor (- client-y bry))
zoom-context (mf/ref-val zoom-view-context)
offset (* (+ (* y (unchecked-get image-data "width")) x) 4)
rgba (unchecked-get image-data "data")
r (obj/get rgba (+ 0 offset))
g (obj/get rgba (+ 1 offset))
b (obj/get rgba (+ 2 offset))
a (obj/get rgba (+ 3 offset))
sx (- x 32)
sy (if (cfg/check-browser? :safari) y (- y 17))
sw 65
sh 35
dx 0
dy 0
dw canvas-width
dh canvas-height]
(when (obj/get zoom-context "imageSmoothingEnabled")
(obj/set! zoom-context "imageSmoothingEnabled" false))
(.clearRect zoom-context 0 0 canvas-width canvas-height)
(.drawImage zoom-context canvas sx sy sw sh dx dy dw dh)
(st/emit! (dwc/pick-color [r g b a]))))))
(mf/defc pixel-overlay
{::mf/wrap-props false}
[props]
@@ -62,7 +97,8 @@
canvas-context (.getContext canvas "2d" #js {:willReadFrequently true})
canvas-image-data (mf/use-ref nil)
zoom-view-context (mf/use-ref nil)
canvas-ready (mf/use-state false)
initial-mouse-pos (mf/use-state {:x 0 :y 0})
update-str (rx/subject)
handle-keydown
@@ -74,49 +110,6 @@
(st/emit! (dwc/stop-picker))
(modal/disallow-click-outside!))))
handle-pointer-move-picker
(mf/use-callback
(mf/deps viewport-node)
(fn [event]
(when-let [image-data (mf/ref-val canvas-image-data)]
(when-let [zoom-view-node (dom/get-element "picker-detail")]
(when-not (mf/ref-val zoom-view-context)
(mf/set-ref-val! zoom-view-context (.getContext zoom-view-node "2d")))
(let [canvas-width 260
canvas-height 140
{brx :left bry :top} (dom/get-bounding-rect viewport-node)
x (mth/floor (- (.-clientX event) brx))
y (mth/floor (- (.-clientY event) bry))
zoom-context (mf/ref-val zoom-view-context)
offset (* (+ (* y (unchecked-get image-data "width")) x) 4)
rgba (unchecked-get image-data "data")
r (obj/get rgba (+ 0 offset))
g (obj/get rgba (+ 1 offset))
b (obj/get rgba (+ 2 offset))
a (obj/get rgba (+ 3 offset))
;; I don't know why, but the zoom view is offset by 24px
;; instead of 25.
sx (- x 32)
;; Safari has a different offset fro the y coord
sy (if (cfg/check-browser? :safari) y (- y 17))
sw 65
sh 35
dx 0
dy 0
dw canvas-width
dh canvas-height]
(when (obj/get zoom-context "imageSmoothingEnabled")
(obj/set! zoom-context "imageSmoothingEnabled" false))
(.clearRect zoom-context 0 0 canvas-width canvas-height)
(.drawImage zoom-context canvas sx sy sw sh dx dy dw dh)
(st/emit! (dwc/pick-color [r g b a])))))))
handle-pointer-down-picker
(mf/use-callback
(fn [event]
@@ -152,12 +145,27 @@
(let [width (unchecked-get canvas "width")
height (unchecked-get canvas "height")
image-data (.getImageData canvas-context 0 0 width height)]
(mf/set-ref-val! canvas-image-data image-data))))))))
(mf/set-ref-val! canvas-image-data image-data)
(reset! canvas-ready true))))))))
handle-svg-change
(mf/use-callback
(fn []
(rx/push! update-str :update)))]
(rx/push! update-str :update)))
handle-mouse-enter
(mf/use-callback
(mf/deps viewport-node)
(fn [event]
(let [x (.-clientX event)
y (.-clientY event)]
(reset! initial-mouse-pos {:x x
:y y}))))
handle-pointer-move-picker
(mf/use-callback
(mf/deps viewport-node)
(fn [event]
(process-pointer-move viewport-node canvas canvas-image-data zoom-view-context (.-clientX event) (.-clientY event))))]
(when (obj/get canvas-context "imageSmoothingEnabled")
(obj/set! canvas-context "imageSmoothingEnabled" false))
@@ -188,9 +196,17 @@
;; Disconnect on unmount
#(.disconnect observer))))
(mf/use-effect
(mf/deps viewport-node @canvas-ready)
(fn []
(when canvas-ready
(let [{:keys [x y]} @initial-mouse-pos]
(process-pointer-move viewport-node canvas canvas-image-data zoom-view-context x y)))))
[:div {:id "pixel-overlay"
:tab-index 0
:class (dm/str (cur/get-static "picker") " " (stl/css :pixel-overlay))
:on-pointer-down handle-pointer-down-picker
:on-pointer-up handle-pointer-up-picker
:on-pointer-move handle-pointer-move-picker}]))
:on-pointer-move handle-pointer-move-picker
:on-mouse-enter handle-mouse-enter}]))

View File

@@ -44,6 +44,7 @@
[app.plugins.viewport :as viewport]
[app.util.code-gen :as cg]
[app.util.object :as obj]
[app.util.theme :as theme]
[beicon.v2.core :as rx]
[cuerdas.core :as str]))
@@ -216,9 +217,15 @@
:getTheme
(fn []
(let [theme (get-in @st/state [:profile :theme])]
(if (or (not theme) (= theme "default"))
(cond
(or (not theme) (= theme "system"))
(theme/get-system-theme)
(= theme "default")
"dark"
(get-in @st/state [:profile :theme]))))
:else
theme)))
:getCurrentUser
(fn []

View File

@@ -14,6 +14,7 @@
[app.plugins.parser :as parser]
[app.plugins.shape :as shape]
[app.util.object :as obj]
[app.util.theme :as theme]
[goog.functions :as gf]))
(defmulti handle-state-change (fn [type _] type))
@@ -50,10 +51,23 @@
::not-changed
(apply array (map str new-selection)))))
(defn- get-theme
[state]
(let [theme (get-in state [:profile :theme])]
(cond
(or (not theme) (= theme "system"))
(theme/get-system-theme)
(= theme "default")
"dark"
:else
theme)))
(defmethod handle-state-change "themechange"
[_ _ old-val new-val _]
(let [old-theme (get-in old-val [:profile :theme])
new-theme (get-in new-val [:profile :theme])]
(let [old-theme (get-theme old-val)
new-theme (get-theme new-val)]
(if (identical? old-theme new-theme)
::not-changed
(if (= new-theme "default")

View File

@@ -1,5 +1,16 @@
# CHANGELOG
## 1.0.7
- Add the ability to provide refereron creating build context
```js
const context = penpot.createBuildContext({referer:"my-referer"});
```
The referer will be added as an additional field on the manifest.json
## 1.0.6
- Fix unexpected issue on library color decoding

View File

@@ -21,11 +21,10 @@
:dev
{:extra-paths ["dev"]
:extra-deps
{thheller/shadow-cljs {:mvn/version "3.1.4"}
{thheller/shadow-cljs {:mvn/version "3.1.7"}
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "RELEASE"}
cider/cider-nrepl {:mvn/version "0.48.0"}}}
criterium/criterium {:mvn/version "RELEASE"}}}
:shadow-cljs
{:main-opts ["-m" "shadow.cljs.devtools.cli"]

View File

@@ -1,6 +1,6 @@
{
"name": "@penpot/library",
"version": "1.0.6",
"version": "1.0.7",
"license": "MPL-2.0",
"author": "Kaleidos INC",
"packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538",
@@ -40,8 +40,7 @@
"@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",
"concurrently": "^9.1.2",
"luxon": "^3.6.1",
"nodemon": "^3.1.9",
"shadow-cljs": "3.1.4"
"nodemon": "^3.1.9"
},
"dependencies": {
"source-map-support": "^0.5.21"

View File

@@ -6,7 +6,7 @@ import { Writable } from "stream";
// console.log(penpot);
(async function () {
const context = penpot.createBuildContext();
const context = penpot.createBuildContext({referer:"playground"});
{
context.addFile({ name: "Test File 1" });

View File

@@ -271,11 +271,19 @@
(fn []
(json/->js @state))))
(def ^:private schema:context-options
[:map {:title "ContextOptions"}
[:referer {:optional true} ::sm/text]])
(def ^:private decode-context-options
(sm/decoder schema:context-options sm/json-transformer))
(defn create-build-context
"Create an empty builder state context."
[]
(let [state (atom {})
api (create-builder-api state)]
[options]
(let [options (some-> options decode-params decode-context-options)
state (atom {:options options})
api (create-builder-api state)]
(specify! api
cljs.core/IDeref

View File

@@ -183,17 +183,22 @@
(defn- generate-manifest-procs
[state]
(let [files (->> (get state ::fb/files)
(mapv (fn [[file-id file]]
{:id file-id
:name (:name file)
:features (:features file)})))
(let [opts (get state :options)
files (->> (get state ::fb/files)
(mapv (fn [[file-id file]]
{:id file-id
:name (:name file)
:features (:features file)})))
params {:type "penpot/export-files"
:version 1
:generated-by "penpot-library/%version%"
:referer (get opts :referer)
:files files
:relations []}]
["manifest.json" (delay (json/encode params))]))
:relations []}
params (d/without-nils params)]
["manifest.json"
(delay (json/encode params))]))
(defn- generate-procs
[state]

View File

@@ -7,6 +7,10 @@ export DEVENV_PNAME="penpotdev";
export CURRENT_USER_ID=$(id -u);
export CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD);
# Safe directory to avoid ownership errors with Git
git config --global --add safe.directory /home/penpot/penpot || true
# Set default java options
export JAVA_OPTS=${JAVA_OPTS:-"-Xmx1000m -Xms50m"};
@@ -356,4 +360,4 @@ case $1 in
*)
usage
;;
esac
esac