mirror of
https://github.com/penpot/penpot.git
synced 2026-01-06 13:28:57 -05:00
Compare commits
32 Commits
palba-nitr
...
2.11.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
befcca86df | ||
|
|
b7bae3850b | ||
|
|
f248ab5644 | ||
|
|
96d9724516 | ||
|
|
8158f2956f | ||
|
|
e45994e836 | ||
|
|
7de95e108b | ||
|
|
604f6ca024 | ||
|
|
e3cf70d3a8 | ||
|
|
d796dbb572 | ||
|
|
e979476b0e | ||
|
|
097897d8da | ||
|
|
02a1992a0a | ||
|
|
a576c0404a | ||
|
|
7d5c1c9b5f | ||
|
|
cd53d3659c | ||
|
|
9e7ec594ca | ||
|
|
7c529eedd4 | ||
|
|
34363320ae | ||
|
|
092a5139e3 | ||
|
|
4a01121043 | ||
|
|
49721c0bcd | ||
|
|
c214cc1544 | ||
|
|
eaabe54c4b | ||
|
|
37aa59b164 | ||
|
|
cbae3dca34 | ||
|
|
8307b699bf | ||
|
|
cd6865f54b | ||
|
|
88493f6805 | ||
|
|
83cd9c3db6 | ||
|
|
399feec032 | ||
|
|
245190f4f9 |
67
.github/workflows/release.yml
vendored
67
.github/workflows/release.yml
vendored
@@ -37,36 +37,43 @@ jobs:
|
||||
ref: ${{ steps.vars.outputs.gh_ref }}
|
||||
|
||||
# --- Publicly release the docker images ---
|
||||
- name: Login to private registry
|
||||
uses: docker/login-action@v3
|
||||
- name: Configure ECR credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
registry: ${{ secrets.DOCKER_REGISTRY }}
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
aws-access-key-id: ${{ secrets.DOCKER_USERNAME }}
|
||||
aws-secret-access-key: ${{ secrets.DOCKER_PASSWORD }}
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
|
||||
- name: Publish docker images to DockerHub
|
||||
env:
|
||||
TAG: ${{ steps.vars.outputs.gh_ref }}
|
||||
REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
|
||||
HUB: ${{ secrets.PUB_DOCKER_HUB }}
|
||||
- name: Install Skopeo
|
||||
run: |
|
||||
IMAGES=("frontend" "backend" "exporter")
|
||||
EXTRA_TAGS=("main" "latest")
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y skopeo
|
||||
|
||||
- name: Copy images from AWS ECR to Docker Hub
|
||||
env:
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
|
||||
PUB_DOCKER_USERNAME: ${{ secrets.PUB_DOCKER_USERNAME }}
|
||||
PUB_DOCKER_PASSWORD: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
||||
TAG: ${{ steps.vars.outputs.gh_ref }}
|
||||
run: |
|
||||
aws ecr get-login-password --region $AWS_REGION | \
|
||||
skopeo login --username AWS --password-stdin \
|
||||
$DOCKER_REGISTRY
|
||||
|
||||
echo "$PUB_DOCKER_PASSWORD" | skopeo login --username "$PUB_DOCKER_USERNAME" --password-stdin docker.io
|
||||
|
||||
IMAGES=("frontend" "backend" "exporter" "storybook")
|
||||
|
||||
for image in "${IMAGES[@]}"; do
|
||||
docker pull "$REGISTRY/penpotapp/$image:$TAG"
|
||||
docker tag "$REGISTRY/penpotapp/$image:$TAG" "penpotapp/$image:$TAG"
|
||||
docker push "penpotapp/$image:$TAG"
|
||||
skopeo copy --all \
|
||||
docker://$DOCKER_REGISTRY/$image:$TAG \
|
||||
docker://docker.io/$PUB_DOCKER_USERNAME/$image:$TAG
|
||||
|
||||
for tag in "${EXTRA_TAGS[@]}"; do
|
||||
docker tag "$REGISTRY/penpotapp/$image:$TAG" "penpotapp/$image:$tag"
|
||||
docker push "penpotapp/$image:$tag"
|
||||
for alias in main latest; do
|
||||
skopeo copy --all \
|
||||
docker://$DOCKER_REGISTRY/$image:$TAG \
|
||||
docker://docker.io/$PUB_DOCKER_USERNAME/$image:$alias
|
||||
done
|
||||
done
|
||||
|
||||
@@ -93,3 +100,15 @@ jobs:
|
||||
tag_name: ${{ steps.vars.outputs.gh_ref }}
|
||||
name: ${{ steps.vars.outputs.gh_ref }}
|
||||
body: ${{ steps.extract_release_notes.outputs.release_notes }}
|
||||
|
||||
- name: Notify Mattermost
|
||||
if: failure()
|
||||
uses: mattermost/action-mattermost-notify@master
|
||||
with:
|
||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
MATTERMOST_CHANNEL: bot-alerts-cicd
|
||||
TEXT: |
|
||||
❌ 🚀 *[PENPOT] Error releasing penpot.*
|
||||
📄 Triggered from ref: `${{ steps.vars.outputs.gh_ref }}`
|
||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
@infra
|
||||
|
||||
14
CHANGES.md
14
CHANGES.md
@@ -1,6 +1,10 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.11.0 (Unreleased)
|
||||
## 2.11.1
|
||||
|
||||
- Fix WEBP shape export on docker images [Taiga #3838](https://tree.taiga.io/project/penpot/issue/3838)
|
||||
|
||||
## 2.11.0
|
||||
|
||||
### :boom: Breaking changes & Deprecations
|
||||
|
||||
@@ -28,10 +32,6 @@
|
||||
services which use netty internally (redis connection, S3 SDK client). This
|
||||
configuration is not very commonly used so don't expected real impact on any user.
|
||||
|
||||
### :rocket: Epics and highlights
|
||||
|
||||
### :heart: Community contributions (Thank you!)
|
||||
|
||||
### :sparkles: New features & Enhancements
|
||||
|
||||
- New composite token: Typography [Taiga #10200](https://tree.taiga.io/project/penpot/us/10200)
|
||||
@@ -75,6 +75,10 @@
|
||||
- Fix paste without selection sends the new element in the back [Taiga #12382](https://tree.taiga.io/project/penpot/issue/12382)
|
||||
- Fix options button does not work for comments created in the lower part of the screen [Taiga #12422](https://tree.taiga.io/project/penpot/issue/12422)
|
||||
- Fix problem when checking usage with removed teams [Taiga #12442](https://tree.taiga.io/project/penpot/issue/12442)
|
||||
- Fix focus mode persisting across page/file navigation [Taiga #12469](https://tree.taiga.io/project/penpot/issue/12469)
|
||||
- Fix shadow color validation [Github #7705](https://github.com/penpot/penpot/pull/7705)
|
||||
- Fix exception on selection blend-mode using keyboard [Github #7710](https://github.com/penpot/penpot/pull/7710)
|
||||
- Fix crash when using decimal (floating-point) values for X/Y or width/height [Taiga #12543](https://tree.taiga.io/project/penpot/issue/12543)
|
||||
|
||||
## 2.10.1
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
com.google.guava/guava {:mvn/version "33.4.8-jre"}
|
||||
|
||||
funcool/yetti
|
||||
{:git/tag "v11.6"
|
||||
:git/sha "94dc017"
|
||||
{:git/tag "v11.8"
|
||||
:git/sha "1d1b33f"
|
||||
:git/url "https://github.com/funcool/yetti.git"
|
||||
:exclusions [org.slf4j/slf4j-api]}
|
||||
|
||||
|
||||
@@ -550,7 +550,7 @@
|
||||
[cfg data file-id]
|
||||
(let [library-ids (get-libraries cfg [file-id])]
|
||||
(reduce (fn [data library-id]
|
||||
(if-let [library (get-file cfg library-id)]
|
||||
(if-let [library (get-file cfg library-id :include-deleted? true)]
|
||||
(ctf/absorb-assets data (:data library))
|
||||
data))
|
||||
data
|
||||
|
||||
@@ -228,6 +228,7 @@
|
||||
(db/tx-run! cfg (fn [cfg]
|
||||
(cond-> (bfc/get-file cfg file-id
|
||||
{:realize? true
|
||||
:include-deleted? true
|
||||
:lock-for-update? true})
|
||||
detach?
|
||||
(-> (ctf/detach-external-references file-id)
|
||||
@@ -285,14 +286,12 @@
|
||||
|
||||
(let [file (cond-> (select-keys file bfc/file-attrs)
|
||||
(:options data)
|
||||
(assoc :options (:options data))
|
||||
(assoc :options (:options data)))
|
||||
|
||||
:always
|
||||
(dissoc :data))
|
||||
|
||||
file (cond-> file
|
||||
:always
|
||||
(encode-file))
|
||||
file (-> file
|
||||
(dissoc :data)
|
||||
(dissoc :deleted-at)
|
||||
(encode-file))
|
||||
|
||||
path (str "files/" file-id ".json")]
|
||||
(write-entry! output path file))
|
||||
|
||||
@@ -319,5 +319,9 @@
|
||||
([key default]
|
||||
(c/get config key default)))
|
||||
|
||||
(defn logging-context
|
||||
[]
|
||||
{:version/backend (:full version)})
|
||||
|
||||
;; Set value for all new threads bindings.
|
||||
(alter-var-root #'*assert* (constantly (contains? flags :backend-asserts)))
|
||||
|
||||
@@ -25,15 +25,14 @@
|
||||
(let [claims (-> {}
|
||||
(into (::session/token-claims request))
|
||||
(into (::actoken/token-claims request)))]
|
||||
{:request/path (:path request)
|
||||
:request/method (:method request)
|
||||
:request/params (:params request)
|
||||
:request/user-agent (yreq/get-header request "user-agent")
|
||||
:request/ip-addr (inet/parse-request request)
|
||||
:request/profile-id (:uid claims)
|
||||
:version/frontend (or (yreq/get-header request "x-frontend-version") "unknown")
|
||||
:version/backend (:full cf/version)}))
|
||||
|
||||
(-> (cf/logging-context)
|
||||
(assoc :request/path (:path request))
|
||||
(assoc :request/method (:method request))
|
||||
(assoc :request/params (:params request))
|
||||
(assoc :request/user-agent (yreq/get-header request "user-agent"))
|
||||
(assoc :request/ip-addr (inet/parse-request request))
|
||||
(assoc :request/profile-id (:uid claims))
|
||||
(assoc :version/frontend (or (yreq/get-header request "x-frontend-version") "unknown")))))
|
||||
|
||||
(defmulti handle-error
|
||||
(fn [cause _ _]
|
||||
|
||||
@@ -64,9 +64,13 @@
|
||||
(let [mdata (meta result)
|
||||
response (if (fn? result)
|
||||
(result request)
|
||||
(let [result (rph/unwrap result)]
|
||||
{::yres/status (::http/status mdata 200)
|
||||
::yres/headers (::http/headers mdata {})
|
||||
(let [result (rph/unwrap result)
|
||||
status (::http/status mdata 200)
|
||||
headers (cond-> (::http/headers mdata {})
|
||||
(yres/stream-body? result)
|
||||
(assoc "content-type" "application/octet-stream"))]
|
||||
{::yres/status status
|
||||
::yres/headers headers
|
||||
::yres/body result}))]
|
||||
(-> response
|
||||
(handle-response-transformation request mdata)
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
[app.rpc.commands.projects :as projects]
|
||||
[app.rpc.commands.teams :as teams]
|
||||
[app.rpc.doc :as-alias doc]
|
||||
[app.rpc.helpers :as rph]
|
||||
[app.tasks.file-gc]
|
||||
[app.util.services :as sv]
|
||||
[app.worker :as-alias wrk]
|
||||
[yetti.response :as yres]))
|
||||
[app.worker :as-alias wrk]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
(defn stream-export-v1
|
||||
[cfg {:keys [file-id include-libraries embed-assets] :as params}]
|
||||
(yres/stream-body
|
||||
(rph/stream
|
||||
(fn [_ output-stream]
|
||||
(try
|
||||
(-> cfg
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
(defn stream-export-v3
|
||||
[cfg {:keys [file-id include-libraries embed-assets] :as params}]
|
||||
(yres/stream-body
|
||||
(rph/stream
|
||||
(fn [_ output-stream]
|
||||
(try
|
||||
(-> cfg
|
||||
@@ -79,16 +79,11 @@
|
||||
::sm/params schema:export-binfile}
|
||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id version file-id] :as params}]
|
||||
(files/check-read-permissions! pool profile-id file-id)
|
||||
(fn [_]
|
||||
(let [version (or version 1)
|
||||
body (case (int version)
|
||||
1 (stream-export-v1 cfg params)
|
||||
2 (throw (ex-info "not-implemented" {}))
|
||||
3 (stream-export-v3 cfg params))]
|
||||
|
||||
{::yres/status 200
|
||||
::yres/headers {"content-type" "application/octet-stream"}
|
||||
::yres/body body})))
|
||||
(let [version (or version 1)]
|
||||
(case (int version)
|
||||
1 (stream-export-v1 cfg params)
|
||||
2 (throw (ex-info "not-implemented" {}))
|
||||
3 (stream-export-v3 cfg params))))
|
||||
|
||||
;; --- Command: import-binfile
|
||||
|
||||
|
||||
@@ -484,7 +484,6 @@
|
||||
WHERE tpr1.profile_id = ?
|
||||
AND tpr1.is_owner IS true
|
||||
AND tpr2.can_edit IS true
|
||||
AND NOT t.is_default
|
||||
AND t.deleted_at IS NULL")
|
||||
|
||||
(sv/defmethod ::get-subscription-usage
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.http :as-alias http]
|
||||
[app.rpc :as-alias rpc]
|
||||
[yetti.response :as-alias yres]))
|
||||
[yetti.response :as yres]))
|
||||
|
||||
;; A utilty wrapper object for wrap service responses that does not
|
||||
;; implements the IObj interface that make possible attach metadata to
|
||||
@@ -78,3 +78,8 @@
|
||||
(let [exp (if (integer? max-age) max-age (inst-ms max-age))
|
||||
val (dm/fmt "max-age=%" (int (/ exp 1000.0)))]
|
||||
(update response ::yres/headers assoc "cache-control" val)))))
|
||||
|
||||
(defn stream
|
||||
"A convenience allias for yetti.response/stream-body"
|
||||
[f]
|
||||
(yres/stream-body f))
|
||||
|
||||
@@ -218,6 +218,9 @@
|
||||
(when (or (nil? revn) (= revn (:revn file)))
|
||||
file)))
|
||||
|
||||
;; FIXME: we should skip files that does not match the revn on the
|
||||
;; props and add proper schema for this task props
|
||||
|
||||
(defn- process-file!
|
||||
[cfg {:keys [file-id] :as props}]
|
||||
(if-let [file (get-file cfg props)]
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"A maintenance task that is responsible of properly scheduling the
|
||||
file-gc task for all files that matches the eligibility threshold."
|
||||
(:require
|
||||
[app.common.logging :as l]
|
||||
[app.common.time :as ct]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
@@ -21,25 +22,24 @@
|
||||
f.modified_at
|
||||
FROM file AS f
|
||||
WHERE f.has_media_trimmed IS false
|
||||
AND f.modified_at < now() - ?::interval
|
||||
AND f.modified_at < ?
|
||||
AND f.deleted_at IS NULL
|
||||
ORDER BY f.modified_at DESC
|
||||
FOR UPDATE OF f
|
||||
SKIP LOCKED")
|
||||
|
||||
(defn- get-candidates
|
||||
[{:keys [::db/conn ::min-age] :as cfg}]
|
||||
(let [min-age (db/interval min-age)]
|
||||
(db/plan conn [sql:get-candidates min-age] {:fetch-size 10})))
|
||||
|
||||
(defn- schedule!
|
||||
[cfg]
|
||||
[{:keys [::db/conn] :as cfg} threshold]
|
||||
(let [total (reduce (fn [total {:keys [id modified-at revn]}]
|
||||
(let [params {:file-id id :modified-at modified-at :revn revn}]
|
||||
(let [params {:file-id id :revn revn}]
|
||||
(l/trc :hint "schedule"
|
||||
:file-id (str id)
|
||||
:revn revn
|
||||
:modified-at (ct/format-inst modified-at))
|
||||
(wrk/submit! (assoc cfg ::wrk/params params))
|
||||
(inc total)))
|
||||
0
|
||||
(get-candidates cfg))]
|
||||
(db/plan conn [sql:get-candidates threshold] {:fetch-size 10}))]
|
||||
{:processed total}))
|
||||
|
||||
(defmethod ig/assert-key ::handler
|
||||
@@ -53,12 +53,12 @@
|
||||
(defmethod ig/init-key ::handler
|
||||
[_ cfg]
|
||||
(fn [{:keys [props] :as task}]
|
||||
(let [min-age (ct/duration (or (:min-age props) (::min-age cfg)))]
|
||||
(let [threshold (-> (ct/duration (or (:min-age props) (::min-age cfg)))
|
||||
(ct/in-past))]
|
||||
(-> cfg
|
||||
(assoc ::db/rollback (:rollback? props))
|
||||
(assoc ::min-age min-age)
|
||||
(assoc ::wrk/task :file-gc)
|
||||
(assoc ::wrk/priority 10)
|
||||
(assoc ::wrk/mark-retries 0)
|
||||
(assoc ::wrk/delay 1000)
|
||||
(db/tx-run! schedule!)))))
|
||||
(assoc ::wrk/delay 10000)
|
||||
(db/tx-run! schedule! threshold)))))
|
||||
|
||||
@@ -77,8 +77,8 @@
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def ^:private sql:insert-new-task
|
||||
"insert into task (id, name, props, queue, label, priority, max_retries, scheduled_at)
|
||||
values (?, ?, ?, ?, ?, ?, ?, now() + ?)
|
||||
"insert into task (id, name, props, queue, label, priority, max_retries, created_at, modified_at, scheduled_at)
|
||||
values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
returning id")
|
||||
|
||||
(def ^:private
|
||||
@@ -88,7 +88,7 @@
|
||||
AND queue=?
|
||||
AND label=?
|
||||
AND status = 'new'
|
||||
AND scheduled_at > now()")
|
||||
AND scheduled_at > ?")
|
||||
|
||||
(def ^:private schema:options
|
||||
[:map {:title "submit-options"}
|
||||
@@ -111,17 +111,19 @@
|
||||
|
||||
(check-options! options)
|
||||
|
||||
(let [duration (ct/duration delay)
|
||||
interval (db/interval duration)
|
||||
props (db/tjson params)
|
||||
id (uuid/next)
|
||||
tenant (cf/get :tenant)
|
||||
task (d/name task)
|
||||
queue (str/ffmt "%:%" tenant (d/name queue))
|
||||
conn (db/get-connectable options)
|
||||
deleted (when dedupe
|
||||
(-> (db/exec-one! conn [sql:remove-not-started-tasks task queue label])
|
||||
:next.jdbc/update-count))]
|
||||
(let [delay (ct/duration delay)
|
||||
now (ct/now)
|
||||
scheduled-at (-> (ct/plus now delay)
|
||||
(ct/truncate :millisecond))
|
||||
props (db/tjson params)
|
||||
id (uuid/next)
|
||||
tenant (cf/get :tenant)
|
||||
task (d/name task)
|
||||
queue (str/ffmt "%:%" tenant (d/name queue))
|
||||
conn (db/get-connectable options)
|
||||
deleted (when dedupe
|
||||
(-> (db/exec-one! conn [sql:remove-not-started-tasks task queue label now])
|
||||
(db/get-update-count)))]
|
||||
|
||||
(l/trc :hint "submit task"
|
||||
:name task
|
||||
@@ -129,11 +131,13 @@
|
||||
:queue queue
|
||||
:label label
|
||||
:dedupe (boolean dedupe)
|
||||
:delay (ct/format-duration duration)
|
||||
:delay (ct/format-duration delay)
|
||||
:replace (or deleted 0))
|
||||
|
||||
(db/exec-one! conn [sql:insert-new-task id task props queue
|
||||
label priority max-retries interval])
|
||||
label priority max-retries
|
||||
now now scheduled-at])
|
||||
|
||||
id))
|
||||
|
||||
(defn invoke!
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as ct]
|
||||
[app.common.transit :as t]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.metrics :as mtx]
|
||||
[app.redis :as rds]
|
||||
@@ -60,7 +61,8 @@
|
||||
|
||||
(defn get-error-context
|
||||
[_ item]
|
||||
{:params item})
|
||||
(-> (cf/logging-context)
|
||||
(assoc :params item)))
|
||||
|
||||
(defn- get-task
|
||||
[{:keys [::db/pool]} task-id]
|
||||
@@ -131,6 +133,11 @@
|
||||
[{:keys [::id ::timeout] :as cfg} task-id scheduled-at]
|
||||
(loop [task (get-task cfg task-id)]
|
||||
(cond
|
||||
(nil? task)
|
||||
(l/wrn :hint "no task found on the database"
|
||||
:runner-id id
|
||||
:task-id task-id)
|
||||
|
||||
(ex/exception? task)
|
||||
(if (or (db/connection-error? task)
|
||||
(db/serialization-error? task))
|
||||
@@ -151,12 +158,9 @@
|
||||
(inst-ms (:scheduled-at task)))
|
||||
(l/wrn :hint "skiping task, rescheduled"
|
||||
:task-id task-id
|
||||
:runner-id id)
|
||||
|
||||
(nil? task)
|
||||
(l/wrn :hint "no task found on the database"
|
||||
:runner-id id
|
||||
:task-id task-id)
|
||||
:scheduled-at (ct/format-inst (:scheduled-at task))
|
||||
:expected-scheduled-at (ct/format-inst scheduled-at))
|
||||
|
||||
:else
|
||||
(let [result (run-task cfg task)]
|
||||
@@ -177,7 +181,8 @@
|
||||
{:error explain
|
||||
:status "retry"
|
||||
:modified-at now
|
||||
:scheduled-at (ct/plus now delay)
|
||||
:scheduled-at (-> (ct/plus now delay)
|
||||
(ct/truncate :millisecond))
|
||||
:retry-num nretry}
|
||||
{:id (:id task)})
|
||||
nil))
|
||||
@@ -213,6 +218,7 @@
|
||||
:payload payload)))
|
||||
(catch Throwable cause
|
||||
(l/err :hint "unable to decode payload"
|
||||
::l/context (cf/logging-context)
|
||||
:payload payload
|
||||
:length (alength ^String/1 payload)
|
||||
:cause cause))))
|
||||
@@ -224,11 +230,11 @@
|
||||
"failed" (handle-task-failure result)
|
||||
"completed" (handle-task-completion result)
|
||||
(throw (IllegalArgumentException.
|
||||
(str "invalid status received: " status))))))
|
||||
(str "invalid status received: '" status "'"))))))
|
||||
|
||||
(run-task-loop [[task-id scheduled-at]]
|
||||
(loop [result (run-task! cfg task-id scheduled-at)]
|
||||
(when-let [cause (process-result result)]
|
||||
(when-let [cause (some-> result process-result)]
|
||||
(if (or (db/connection-error? cause)
|
||||
(db/serialization-error? cause))
|
||||
(do
|
||||
@@ -236,9 +242,9 @@
|
||||
:cause cause)
|
||||
(px/sleep timeout)
|
||||
(recur result))
|
||||
(do
|
||||
(l/err :hint "unhandled exception on processing task result"
|
||||
:cause cause))))))]
|
||||
(l/err :hint "unhandled exception on processing task result"
|
||||
::l/context (cf/logging-context)
|
||||
:cause cause)))))]
|
||||
|
||||
(try
|
||||
(let [key (str/ffmt "penpot.worker.queue:%" queue)
|
||||
@@ -254,11 +260,14 @@
|
||||
(if (rds/timeout-exception? cause)
|
||||
(do
|
||||
(l/err :hint "redis pop operation timeout, consider increasing redis timeout (will retry in some instants)"
|
||||
::l/context (cf/logging-context)
|
||||
:timeout timeout
|
||||
:cause cause)
|
||||
(px/sleep timeout))
|
||||
|
||||
(l/err :hint "unhandled exception" :cause cause))))))
|
||||
(l/err :hint "unhandled exception"
|
||||
::l/context (cf/logging-context)
|
||||
:cause cause))))))
|
||||
|
||||
(defn- start-thread!
|
||||
[{:keys [::id ::queue ::wrk/tenant] :as cfg}]
|
||||
@@ -284,6 +293,7 @@
|
||||
:queue queue))
|
||||
(catch Throwable cause
|
||||
(l/err :hint "unexpected exception"
|
||||
::l/context (cf/logging-context)
|
||||
:id id
|
||||
:queue queue
|
||||
:cause cause))
|
||||
|
||||
@@ -549,6 +549,44 @@
|
||||
(io/copy r sw)
|
||||
(.toString sw))))
|
||||
|
||||
(defn parse-sse
|
||||
[content]
|
||||
(let [state
|
||||
(reduce (fn [{:keys [events data event id] :as state} line]
|
||||
(cond
|
||||
;; empty line → dispatch event if we have data
|
||||
(str/blank? line)
|
||||
(if (seq data)
|
||||
(-> state
|
||||
(update :events conj {:event (or event "message")
|
||||
:data (-> (str/join "\n" data))})
|
||||
(assoc :data [] :event nil))
|
||||
state)
|
||||
|
||||
;; comment line (starts with :)
|
||||
(str/starts-with? line ":")
|
||||
state
|
||||
|
||||
:else
|
||||
(let [[field raw-value] (str/split line #":" 2)
|
||||
value (some-> raw-value (str/replace #"^ " ""))]
|
||||
(case field
|
||||
"data" (update state :data conj (or value ""))
|
||||
"event" (assoc state :event value)
|
||||
;; ignore retry and unknown fields
|
||||
state))))
|
||||
{:events [] :data [] :event nil}
|
||||
(str/split content #"\r?\n"))
|
||||
|
||||
;; handle unterminated last event (no trailing blank line)
|
||||
state (if (seq (:data state))
|
||||
(update state :events conj
|
||||
{:event (or (:event state) "message")
|
||||
:data (str/join "\n" (:data state))})
|
||||
state)]
|
||||
|
||||
(:events state)))
|
||||
|
||||
(defn consume-sse
|
||||
[callback]
|
||||
(let [{:keys [::yres/status ::yres/body ::yres/headers] :as response} (callback {})
|
||||
@@ -558,12 +596,9 @@
|
||||
(try
|
||||
(px/exec! :virtual #(rcp/write-body-to-stream body nil output))
|
||||
(into []
|
||||
(map (fn [event]
|
||||
(let [[item1 item2] (re-seq #"(.*): (.*)\n?" event)]
|
||||
|
||||
[(keyword (nth item1 2))
|
||||
(tr/decode-str (nth item2 2))])))
|
||||
(-> (slurp' input)
|
||||
(str/split "\n\n")))
|
||||
(map (fn [{:keys [event data]}]
|
||||
[(keyword event)
|
||||
(tr/decode-str data)]))
|
||||
(parse-sse (slurp' input)))
|
||||
(finally
|
||||
(.close input)))))
|
||||
|
||||
@@ -1357,38 +1357,6 @@
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "0004-clean-shadow-color"
|
||||
[data _]
|
||||
(let [decode-color (sm/decoder types.color/schema:color sm/json-transformer)
|
||||
|
||||
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))))
|
||||
|
||||
clean-shadow
|
||||
(fn [shadow]
|
||||
(update shadow :color clean-shadow-color))
|
||||
|
||||
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))))
|
||||
|
||||
(defmethod migrate-data "0005-deprecate-image-type"
|
||||
[data _]
|
||||
(letfn [(update-object [object]
|
||||
@@ -1630,6 +1598,45 @@
|
||||
;; as value; this migration fixes it.
|
||||
(d/update-when data :components d/update-vals d/without-nils))
|
||||
|
||||
(defmethod migrate-data "0015-clean-shadow-color"
|
||||
[data _]
|
||||
(let [decode-shadow-color
|
||||
(sm/decoder ctss/schema:color sm/json-transformer)
|
||||
|
||||
clean-shadow-color
|
||||
(fn [color]
|
||||
(let [ref-id (get color :id)
|
||||
ref-file (get color :file-id)]
|
||||
(-> (d/without-qualified color)
|
||||
(select-keys ctss/color-attrs)
|
||||
(cond-> ref-id
|
||||
(assoc :ref-id ref-id))
|
||||
(cond-> ref-file
|
||||
(assoc :ref-file ref-file))
|
||||
(decode-shadow-color)
|
||||
(d/without-nils))))
|
||||
|
||||
clean-shadow
|
||||
(fn [shadow]
|
||||
(update shadow :color clean-shadow-color))
|
||||
|
||||
clean-xform
|
||||
(comp
|
||||
(keep clean-shadow)
|
||||
(filter ctss/valid-shadow?))
|
||||
|
||||
update-object
|
||||
(fn [object]
|
||||
(d/update-when object :shadow #(into [] clean-xform %)))
|
||||
|
||||
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))))
|
||||
|
||||
(def available-migrations
|
||||
(into (d/ordered-set)
|
||||
["legacy-2"
|
||||
@@ -1689,7 +1696,6 @@
|
||||
"0002-clean-shape-interactions"
|
||||
"0003-fix-root-shape"
|
||||
"0003-convert-path-content-v2"
|
||||
"0004-clean-shadow-color"
|
||||
"0005-deprecate-image-type"
|
||||
"0006-fix-old-texts-fills"
|
||||
"0008-fix-library-colors-v4"
|
||||
@@ -1701,4 +1707,5 @@
|
||||
"0013-fix-component-path"
|
||||
"0013-clear-invalid-strokes-and-fills"
|
||||
"0014-fix-tokens-lib-duplicate-ids"
|
||||
"0014-clear-components-nil-objects"]))
|
||||
"0014-clear-components-nil-objects"
|
||||
"0015-clean-shadow-color"]))
|
||||
|
||||
@@ -466,19 +466,20 @@
|
||||
children (map #(ctst/get-shape page %) shapes)
|
||||
prop-names (cfv/extract-properties-names (first children) (:data file))]
|
||||
(doseq [child children]
|
||||
(if (not (ctk/is-variant? child))
|
||||
(report-error :not-a-variant
|
||||
(str/ffmt "Shape % should be a variant" (:id child))
|
||||
child file page)
|
||||
(do
|
||||
(when (not= (:variant-id child) shape-id)
|
||||
(report-error :invalid-variant-id
|
||||
(str/ffmt "Variant % has invalid variant-id %" (:id child) (:variant-id child))
|
||||
child file page))
|
||||
(when (not= prop-names (cfv/extract-properties-names child (:data file)))
|
||||
(report-error :invalid-variant-properties
|
||||
(str/ffmt "Variant % has invalid properties %" (:id child) (vec prop-names))
|
||||
child file page)))))))
|
||||
(when child
|
||||
(if (not (ctk/is-variant? child))
|
||||
(report-error :not-a-variant
|
||||
(str/ffmt "Shape % should be a variant" (:id child))
|
||||
child file page)
|
||||
(do
|
||||
(when (not= (:variant-id child) shape-id)
|
||||
(report-error :invalid-variant-id
|
||||
(str/ffmt "Variant % has invalid variant-id %" (:id child) (:variant-id child))
|
||||
child file page))
|
||||
(when (not= prop-names (cfv/extract-properties-names child (:data file)))
|
||||
(report-error :invalid-variant-properties
|
||||
(str/ffmt "Variant % has invalid properties %" (:id child) (vec prop-names))
|
||||
child file page))))))))
|
||||
|
||||
(defn- check-variant
|
||||
"Shape is a variant, so
|
||||
@@ -627,7 +628,8 @@
|
||||
main-component (if (:deleted component)
|
||||
(dm/get-in component [:objects (:main-instance-id component)])
|
||||
(ctst/get-shape component-page (:main-instance-id component)))]
|
||||
(when-not (ctk/is-variant? main-component)
|
||||
(when (and main-component
|
||||
(not (ctk/is-variant? main-component)))
|
||||
(report-error :not-a-variant
|
||||
(str/ffmt "Shape % should be a variant" (:id main-component))
|
||||
main-component file component-page))))
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
(defn type
|
||||
[s]
|
||||
(m/-type s))
|
||||
(m/type s default-options))
|
||||
|
||||
(defn properties
|
||||
[s]
|
||||
@@ -46,6 +46,10 @@
|
||||
[s]
|
||||
(m/type-properties s))
|
||||
|
||||
(defn children
|
||||
[s]
|
||||
(m/children s default-options))
|
||||
|
||||
(defn schema
|
||||
[s]
|
||||
(if (schema? s)
|
||||
@@ -127,9 +131,19 @@
|
||||
|
||||
(defn keys
|
||||
"Given a map schema, return all keys as set"
|
||||
[schema]
|
||||
(->> (entries schema)
|
||||
(into #{} xf:map-key)))
|
||||
[schema']
|
||||
(let [schema' (m/schema schema' default-options)]
|
||||
(case (m/type schema')
|
||||
:map
|
||||
(->> (entries schema')
|
||||
(into #{} xf:map-key))
|
||||
|
||||
:merge
|
||||
(->> (m/children schema')
|
||||
(mapcat m/entries)
|
||||
(into #{} xf:map-key))
|
||||
|
||||
(throw (ex-info "not supported schema type" {:type (m/type schema')})))))
|
||||
|
||||
(defn update-properties
|
||||
[s f & args]
|
||||
|
||||
@@ -498,10 +498,10 @@
|
||||
[:map
|
||||
[:x schema:safe-number]
|
||||
[:y schema:safe-number]
|
||||
[:c1x schema:safe-number]
|
||||
[:c1y schema:safe-number]
|
||||
[:c2x schema:safe-number]
|
||||
[:c2y schema:safe-number]]]])
|
||||
[:c1x {:optional true} schema:safe-number]
|
||||
[:c1y {:optional true} schema:safe-number]
|
||||
[:c2x {:optional true} schema:safe-number]
|
||||
[:c2y {:optional true} schema:safe-number]]]])
|
||||
|
||||
(def ^:private schema:segment
|
||||
[:multi {:title "PathSegment"
|
||||
|
||||
@@ -11,6 +11,14 @@
|
||||
|
||||
(def styles #{:drop-shadow :inner-shadow})
|
||||
|
||||
(def schema:color
|
||||
[:merge {:title "ShadowColor"}
|
||||
ctc/schema:color-attrs
|
||||
ctc/schema:plain-color])
|
||||
|
||||
(def color-attrs
|
||||
(sm/keys schema:color))
|
||||
|
||||
(def schema:shadow
|
||||
[:map {:title "Shadow"}
|
||||
[:id [:maybe ::sm/uuid]]
|
||||
@@ -20,7 +28,7 @@
|
||||
[:blur ::sm/safe-number]
|
||||
[:spread ::sm/safe-number]
|
||||
[:hidden :boolean]
|
||||
[:color ctc/schema:color]])
|
||||
[:color schema:color]])
|
||||
|
||||
(def check-shadow
|
||||
(sm/check-fn schema:shadow))
|
||||
|
||||
@@ -1991,13 +1991,19 @@ Will return a value that matches this schema:
|
||||
|
||||
#?(:clj
|
||||
(defn- migrate-to-v1-4
|
||||
"Migrate the TokensLib data structure internals to v1.2 version; it
|
||||
"Migrate the TokensLib data structure internals to v1.4 version; it
|
||||
expects input from v1.3 version"
|
||||
[params]
|
||||
(let [migrate-set-node
|
||||
(fn recurse [node]
|
||||
(if (token-set-legacy? node)
|
||||
(cond
|
||||
(token-set-legacy? node)
|
||||
(make-token-set node)
|
||||
|
||||
(token-set? node)
|
||||
node
|
||||
|
||||
:else
|
||||
(d/update-vals node recurse)))]
|
||||
|
||||
(update params :sets d/update-vals migrate-set-node))))
|
||||
|
||||
@@ -116,6 +116,27 @@
|
||||
(t/is (= sample-content
|
||||
(vec pdata)))))
|
||||
|
||||
|
||||
;; Test the specific case where cuve-to commands comes without the
|
||||
;; optional attrs
|
||||
(t/deftest path-data-plain-to-binary-2
|
||||
(let [plain-content
|
||||
[{:command :move-to :params {:x 480.0 :y 839.0}}
|
||||
{:command :line-to :params {:x 439.0 :y 802.0}}
|
||||
{:command :curve-to :params {:x 264.0 :y 634.0}}
|
||||
{:command :curve-to :params {:x 154.0 :y 508.0}}]
|
||||
|
||||
binary-content
|
||||
(path/content plain-content)]
|
||||
|
||||
#?(:clj
|
||||
(t/is (= "M480.0,839.0L439.0,802.0C264.0,634.0,264.0,634.0,264.0,634.0C154.0,508.0,154.0,508.0,154.0,508.0"
|
||||
(str binary-content)))
|
||||
|
||||
:cljs
|
||||
(t/is (= "M480,839L439,802C264,634,264,634,264,634C154,508,154,508,154,508"
|
||||
(str binary-content))))))
|
||||
|
||||
(t/deftest path-data-from-binary
|
||||
(let [barray #?(:clj (byte-array sample-bytes)
|
||||
:cljs (js/Int8Array.from sample-bytes))
|
||||
|
||||
@@ -149,18 +149,24 @@ FROM base AS setup-rust
|
||||
ENV PATH=/opt/cargo/bin:$PATH \
|
||||
RUSTUP_HOME=/opt/rustup \
|
||||
CARGO_HOME=/opt/cargo \
|
||||
RUSTUP_VERSION=1.27.1 \
|
||||
RUST_VERSION=1.85.0 \
|
||||
RUSTUP_VERSION=1.28.2 \
|
||||
RUST_VERSION=1.91.0 \
|
||||
EMSCRIPTEN_VERSION=4.0.6
|
||||
|
||||
WORKDIR /opt
|
||||
|
||||
RUN set -eux; \
|
||||
# Same steps as in Rust official Docker image https://github.com/rust-lang/docker-rust/blob/9f287282d513a84cb7c7f38f197838f15d37b6a9/1.81.0/bookworm/Dockerfile
|
||||
dpkgArch="$(dpkg --print-architecture)"; \
|
||||
case "${dpkgArch##*-}" in \
|
||||
amd64) rustArch='x86_64-unknown-linux-gnu'; rustupSha256='6aeece6993e902708983b209d04c0d1dbb14ebb405ddb87def578d41f920f56d' ;; \
|
||||
arm64) rustArch='aarch64-unknown-linux-gnu'; rustupSha256='1cffbf51e63e634c746f741de50649bbbcbd9dbe1de363c9ecef64e278dba2b2' ;; \
|
||||
arch="$(dpkg --print-architecture)"; \
|
||||
case "$arch" in \
|
||||
'amd64') \
|
||||
rustArch='x86_64-unknown-linux-gnu'; \
|
||||
rustupSha256='20a06e644b0d9bd2fbdbfd52d42540bdde820ea7df86e92e533c073da0cdd43c'; \
|
||||
;; \
|
||||
'arm64') \
|
||||
rustArch='aarch64-unknown-linux-gnu'; \
|
||||
rustupSha256='e3853c5a252fca15252d07cb23a1bdd9377a8c6f3efa01531109281ae47f841c'; \
|
||||
;; \
|
||||
*) echo >&2 "unsupported architecture: ${dpkgArch}"; exit 1 ;; \
|
||||
esac; \
|
||||
wget "https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${rustArch}/rustup-init"; \
|
||||
|
||||
@@ -5,7 +5,7 @@ ENV LANG=en_US.UTF-8 \
|
||||
LC_ALL=en_US.UTF-8 \
|
||||
NODE_VERSION=v22.21.1 \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
PATH=/opt/node/bin:$PATH
|
||||
PATH=/opt/node/bin:/opt/imagick/bin:$PATH
|
||||
|
||||
RUN set -ex; \
|
||||
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
|
||||
@@ -62,6 +62,22 @@ RUN set -ex; \
|
||||
libxfixes3 \
|
||||
libxkbcommon0 \
|
||||
libxrandr2 \
|
||||
\
|
||||
libgomp1 \
|
||||
libheif1 \
|
||||
libjpeg-turbo8 \
|
||||
liblcms2-2 \
|
||||
libopenexr-3-1-30 \
|
||||
libopenjp2-7 \
|
||||
libpng16-16 \
|
||||
librsvg2-2 \
|
||||
libtiff6 \
|
||||
libwebp7 \
|
||||
libwebpdemux2 \
|
||||
libwebpmux3 \
|
||||
libxml2 \
|
||||
libzip4t64 \
|
||||
libzstd1 \
|
||||
; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
@@ -90,7 +106,8 @@ RUN set -eux; \
|
||||
chown -R penpot:penpot /opt/penpot;
|
||||
|
||||
ARG BUNDLE_PATH="./bundle-exporter/"
|
||||
ADD --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/exporter/
|
||||
COPY --chown=penpot:penpot $BUNDLE_PATH /opt/penpot/exporter/
|
||||
COPY --from=penpotapp/imagemagick:7.1.2-0 /opt/imagick /opt/imagick
|
||||
|
||||
WORKDIR /opt/penpot/exporter
|
||||
USER penpot:penpot
|
||||
|
||||
@@ -1278,7 +1278,7 @@
|
||||
(watch [_ _ stream]
|
||||
(let [stopper-s
|
||||
(->> stream
|
||||
(rx/filter #(or (= ::dw/finalize-page (ptk/type %))
|
||||
(rx/filter #(or (= ::dwpg/finalize-page (ptk/type %))
|
||||
(= ::watch-component-changes (ptk/type %)))))
|
||||
|
||||
workspace-data-s
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.workspace.drawing.common :as dwdc]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
[app.main.data.workspace.pages :as-alias dwpg]
|
||||
[app.main.data.workspace.path.changes :as changes]
|
||||
[app.main.data.workspace.path.common :as common]
|
||||
[app.main.data.workspace.path.helpers :as helpers]
|
||||
@@ -43,7 +44,7 @@
|
||||
(= type :app.main.data.workspace.path.shortcuts/esc-pressed)
|
||||
(= type :app.main.data.workspace.common/clear-edition-mode)
|
||||
(= type :app.main.data.workspace.edition/clear-edition-mode)
|
||||
(= type :app.main.data.workspace/finalize-page)
|
||||
(= type ::dwpg/finalize-page)
|
||||
(= event :interrupt) ;; ESC
|
||||
(and ^boolean (mse/mouse-event? event)
|
||||
^boolean (mse/mouse-double-click-event? event)))))
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
[app.common.data.undo-stack :as u]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.edition :as-alias dwe]
|
||||
[app.main.data.workspace.pages :as-alias dwpg]
|
||||
[app.main.data.workspace.path.changes :as changes]
|
||||
[app.main.data.workspace.path.common :as common]
|
||||
[app.main.data.workspace.path.state :as st]
|
||||
@@ -133,8 +135,8 @@
|
||||
|
||||
(defn- stop-undo? [event]
|
||||
(let [type (ptk/type event)]
|
||||
(or (= :app.main.data.workspace.edition/clear-edition-mode type)
|
||||
(= :app.main.data.workspace/finalize-page type))))
|
||||
(or (= ::dwe/clear-edition-mode type)
|
||||
(= ::dwpg/finalize-page type))))
|
||||
|
||||
(def path-content-ref
|
||||
(letfn [(selector [state]
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
[app.main.data.modal :as md]
|
||||
[app.main.data.workspace.collapse :as dwc]
|
||||
[app.main.data.workspace.edition :as dwe]
|
||||
[app.main.data.workspace.pages :as-alias dwpg]
|
||||
[app.main.data.workspace.specialized-panel :as-alias dwsp]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.data.workspace.zoom :as dwz]
|
||||
@@ -596,21 +597,21 @@
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [stopper (rx/filter #(or (= ::toggle-focus-mode (ptk/type %))
|
||||
(= :app.main.data.workspace/finalize-page (ptk/type %))) stream)]
|
||||
(= ::dwpg/finalize-page (ptk/type %))) stream)]
|
||||
(when (d/not-empty? (:workspace-focus-selected state))
|
||||
(rx/merge
|
||||
(rx/of dwz/zoom-to-selected-shape
|
||||
(deselect-all))
|
||||
(->> (rx/from-atom refs/workspace-page-objects {:emit-current-value? true})
|
||||
(rx/take-until stopper)
|
||||
(rx/map (comp set keys))
|
||||
(rx/buffer 2 1)
|
||||
(rx/merge-map
|
||||
;; While focus is active, update it with any new and deleted shapes
|
||||
(fn [[old-keys new-keys]]
|
||||
(let [removed (set/difference old-keys new-keys)
|
||||
added (set/difference new-keys old-keys)]
|
||||
(->> (rx/merge
|
||||
(rx/of dwz/zoom-to-selected-shape
|
||||
(deselect-all))
|
||||
(->> (rx/from-atom refs/workspace-page-objects {:emit-current-value? true})
|
||||
(rx/map (comp set keys))
|
||||
(rx/buffer 2 1)
|
||||
(rx/merge-map
|
||||
;; While focus is active, update it with any new and deleted shapes
|
||||
(fn [[old-keys new-keys]]
|
||||
(let [removed (set/difference old-keys new-keys)
|
||||
added (set/difference new-keys old-keys)]
|
||||
|
||||
(if (or (d/not-empty? added) (d/not-empty? removed))
|
||||
(rx/of (update-focus-shapes added removed))
|
||||
(rx/empty))))))))))))
|
||||
(if (or (d/not-empty? added) (d/not-empty? removed))
|
||||
(rx/of (update-focus-shapes added removed))
|
||||
(rx/empty)))))))
|
||||
(rx/take-until stopper)))))))
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.persistence :as-alias dps]
|
||||
[app.main.data.workspace.notifications :as-alias wnt]
|
||||
[app.main.data.workspace.pages :as-alias dwpg]
|
||||
[app.main.rasterizer :as thr]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.render :as render]
|
||||
@@ -254,7 +255,7 @@
|
||||
(let [stopper-s (rx/filter
|
||||
(fn [event]
|
||||
(as-> (ptk/type event) type
|
||||
(or (= :app.main.data.workspace/finalize-page type)
|
||||
(or (= ::dwpg/finalize-page type)
|
||||
(= ::watch-state-changes type))))
|
||||
stream)
|
||||
|
||||
|
||||
@@ -89,14 +89,14 @@
|
||||
(let [value (rotate-option-backward options index length)]
|
||||
(swap! state* assoc :current-value value)
|
||||
(when (fn? on-change)
|
||||
(on-change (dm/str value))))
|
||||
(on-change value)))
|
||||
|
||||
(or (kbd/right-arrow? e)
|
||||
(kbd/down-arrow? e))
|
||||
(let [value (rotate-option-forward options index)]
|
||||
(swap! state* assoc :current-value value)
|
||||
(when (fn? on-change)
|
||||
(on-change (dm/str value))))
|
||||
(on-change value)))
|
||||
|
||||
(or (kbd/enter? e)
|
||||
(kbd/space? e))
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.uri :as u]
|
||||
[app.config :as cf]
|
||||
[app.main.data.auth :refer [is-authenticated?]]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
@@ -311,9 +312,10 @@
|
||||
trace (:app.main.errors/trace data)
|
||||
instance (:app.main.errors/instance data)]
|
||||
(with-out-str
|
||||
(println "Hint: " (or (:hint data) (ex-message instance) "--"))
|
||||
(println "Prof ID:" (str (or profile-id "--")))
|
||||
(println "Team ID:" (str (or team-id "--")))
|
||||
(println "Hint: " (or (:hint data) (ex-message instance) "--"))
|
||||
(println "Prof ID: " (str (or profile-id "--")))
|
||||
(println "Team ID: " (str (or team-id "--")))
|
||||
(println "URI: " cf/public-uri)
|
||||
|
||||
(when-let [file-id (:file-id data)]
|
||||
(println "File ID:" (str file-id)))
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
.element-set-content {
|
||||
@include deprecated.flexColumn;
|
||||
grid-column: span 8;
|
||||
margin: deprecated.$s-4 0 deprecated.$s-8 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -303,7 +303,7 @@
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [value attr]
|
||||
(if (or (string? value) (int? value))
|
||||
(if (or (string? value) (number? value))
|
||||
(do
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(binding [cts/*wasm-sync* true]
|
||||
@@ -334,7 +334,7 @@
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [value attr]
|
||||
(if (or (string? value) (int? value))
|
||||
(if (or (string? value) (number? value))
|
||||
(do
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(binding [cts/*wasm-sync* true]
|
||||
@@ -359,7 +359,7 @@
|
||||
(mf/use-fn
|
||||
(mf/deps ids)
|
||||
(fn [value]
|
||||
(if (or (string? value) (int? value))
|
||||
(if (or (string? value) (number? value))
|
||||
(do
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(binding [cts/*wasm-sync* true]
|
||||
|
||||
@@ -157,11 +157,11 @@
|
||||
color-name (dm/get-in src-colors [color-id :name])
|
||||
|
||||
has-multiple-colors (uc/multiple? color)
|
||||
library-color? (and (or (:id color) (:ref-id color)) color-name (not has-multiple-colors))
|
||||
gradient-color? (and (not has-multiple-colors)
|
||||
library-color? (and (or (:id color) (:ref-id color)) color-name (not ^boolean has-multiple-colors))
|
||||
gradient-color? (and (not ^boolean has-multiple-colors)
|
||||
(:gradient color)
|
||||
(dm/get-in color [:gradient :type]))
|
||||
image-color? (and (not has-multiple-colors)
|
||||
image-color? (and (not ^boolean has-multiple-colors)
|
||||
(:image color))
|
||||
|
||||
editing-text* (mf/use-state false)
|
||||
@@ -236,7 +236,7 @@
|
||||
(mf/deps disable-gradient disable-opacity disable-image disable-picker on-change on-close on-open tokens)
|
||||
(fn [color pos tab]
|
||||
(let [color (cond
|
||||
has-multiple-colors
|
||||
^boolean has-multiple-colors
|
||||
{:color default-color
|
||||
:opacity 1}
|
||||
|
||||
@@ -355,6 +355,7 @@
|
||||
[:> color-info-wrapper* {:class (stl/css-case :color-name-wrapper true
|
||||
:library-name-wrapper true)
|
||||
:handle-click-color handle-click-color
|
||||
:opacity false
|
||||
:color color}
|
||||
[:*
|
||||
[:div {:class (stl/css :color-name)
|
||||
@@ -369,11 +370,11 @@
|
||||
|
||||
gradient-color?
|
||||
[:> color-info-wrapper* {:class (stl/css-case :color-name-wrapper true
|
||||
:no-opacity disable-opacity
|
||||
:no-opacity ^boolean disable-opacity
|
||||
:gradient-name-wrapper true)
|
||||
:handle-click-color handle-click-color
|
||||
:color color
|
||||
:opacity true
|
||||
:opacity (not ^boolean disable-opacity)
|
||||
:select-on-focus select-on-focus
|
||||
:on-focus on-focus'
|
||||
:on-blur on-blur'
|
||||
@@ -383,10 +384,10 @@
|
||||
|
||||
image-color?
|
||||
[:> color-info-wrapper* {:class (stl/css-case :color-name-wrapper true
|
||||
:no-opacity disable-opacity)
|
||||
:no-opacity ^boolean disable-opacity)
|
||||
:handle-click-color handle-click-color
|
||||
:color color
|
||||
:opacity true
|
||||
:opacity (not ^boolean disable-opacity)
|
||||
:select-on-focus select-on-focus
|
||||
:on-focus on-focus'
|
||||
:on-blur on-blur'
|
||||
@@ -396,19 +397,20 @@
|
||||
|
||||
:else
|
||||
[:> color-info-wrapper* {:class (stl/css-case :color-name-wrapper true
|
||||
:no-opacity (or disable-opacity
|
||||
has-multiple-colors)
|
||||
:no-opacity (or ^boolean disable-opacity
|
||||
^boolean has-multiple-colors)
|
||||
:editing is-editing-text)
|
||||
:handle-click-color handle-click-color
|
||||
:color color
|
||||
:opacity true
|
||||
:opacity (not (or ^boolean disable-opacity
|
||||
^boolean has-multiple-colors))
|
||||
:select-on-focus select-on-focus
|
||||
:on-focus on-focus'
|
||||
:on-blur on-blur'
|
||||
:on-opacity-change on-opacity-change}
|
||||
|
||||
[:span {:class (stl/css :color-input-wrapper)}
|
||||
[:> color-input* {:value (if has-multiple-colors
|
||||
[:> color-input* {:value (if ^boolean has-multiple-colors
|
||||
""
|
||||
color-without-hash)
|
||||
:placeholder (tr "settings.multiple")
|
||||
|
||||
@@ -186,6 +186,7 @@
|
||||
align-items: center;
|
||||
block-size: $sz-32;
|
||||
inline-size: px2rem(60);
|
||||
padding-inline-start: var(--sp-xs);
|
||||
border-radius: 0 $br-8 $br-8 0;
|
||||
border: $b-1 solid var(--opacity-input-boder-color);
|
||||
background-color: var(--opacity-input-background-color);
|
||||
|
||||
@@ -300,7 +300,7 @@ impl Ord for BezierStart {
|
||||
|
||||
type BM<'a> = BTreeMap<BezierStart, Vec<(BezierSource, Bezier)>>;
|
||||
|
||||
fn init_bm(beziers: &[(BezierSource, Bezier)]) -> BM {
|
||||
fn init_bm(beziers: &[(BezierSource, Bezier)]) -> BM<'_> {
|
||||
let mut bm = BM::default();
|
||||
for entry @ (source, bezier) in beziers.iter() {
|
||||
let value = *entry;
|
||||
|
||||
@@ -83,8 +83,8 @@ fn get_text_stroke_paints(
|
||||
let shader = text_paint.shader();
|
||||
let mut is_opaque = true;
|
||||
|
||||
if shader.is_some() {
|
||||
is_opaque = shader.unwrap().is_opaque();
|
||||
if let Some(shader) = shader {
|
||||
is_opaque = shader.is_opaque();
|
||||
}
|
||||
|
||||
if is_opaque && count_inner_strokes == 1 {
|
||||
|
||||
@@ -504,7 +504,7 @@ impl Shape {
|
||||
(added, removed)
|
||||
}
|
||||
|
||||
pub fn fills(&self) -> std::slice::Iter<Fill> {
|
||||
pub fn fills(&self) -> std::slice::Iter<'_, Fill> {
|
||||
self.fills.iter()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,16 @@
|
||||
use super::Path;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct Bool {
|
||||
pub bool_type: BoolType,
|
||||
pub path: Path,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub enum BoolType {
|
||||
#[default]
|
||||
Union,
|
||||
Difference,
|
||||
Intersection,
|
||||
Exclusion,
|
||||
}
|
||||
|
||||
impl Default for Bool {
|
||||
fn default() -> Self {
|
||||
Bool {
|
||||
bool_type: BoolType::Union,
|
||||
path: Path::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BoolType {
|
||||
fn default() -> Self {
|
||||
Self::Union
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,13 @@ use skia_safe::{self as skia, image_filters, ImageFilter, Paint};
|
||||
use super::Color;
|
||||
use crate::render::filters::compose_filters;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub enum ShadowStyle {
|
||||
#[default]
|
||||
Drop,
|
||||
Inner,
|
||||
}
|
||||
|
||||
impl Default for ShadowStyle {
|
||||
fn default() -> Self {
|
||||
Self::Drop
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Shadow {
|
||||
pub color: Color,
|
||||
|
||||
@@ -180,7 +180,7 @@ pub extern "C" fn set_shape_path_buffer() {
|
||||
let buffer = get_path_upload_buffer();
|
||||
let mut buffer = buffer.lock().unwrap();
|
||||
let chunk_size = size_of::<RawSegmentData>();
|
||||
if buffer.len() % chunk_size != 0 {
|
||||
if !buffer.len().is_multiple_of(chunk_size) {
|
||||
// FIXME
|
||||
println!("Warning: buffer length is not a multiple of chunk size!");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user