mirror of
https://github.com/penpot/penpot.git
synced 2026-01-27 15:51:32 -05:00
Compare commits
1 Commits
staging-re
...
alotor-was
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc918d39c3 |
16
.github/workflows/plugins-deploy-api-doc.yml
vendored
16
.github/workflows/plugins-deploy-api-doc.yml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
- "plugins/libs/plugin-types/REAME.md"
|
||||
- "plugins/tools/typedoc.css"
|
||||
- "plugins/CHANGELOG.md"
|
||||
- "plugins/wrangler-penpot-plugins-api-doc.toml"
|
||||
- "plugins/wrangle-penpot-plugins-api-doc.toml"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
gh_ref:
|
||||
@@ -98,16 +98,4 @@ jobs:
|
||||
workingDirectory: plugins
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: deploy --config wrangler-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }}
|
||||
|
||||
- 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 PLUGINS] Error deploying API documentation.*
|
||||
📄 Triggered from ref: `${{ inputs.gh_ref }}`
|
||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
@infra
|
||||
command: deploy --config wrangle-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }}
|
||||
|
||||
127
.github/workflows/plugins-deploy-package.yml
vendored
127
.github/workflows/plugins-deploy-package.yml
vendored
@@ -1,127 +0,0 @@
|
||||
name: Plugins/package deployer
|
||||
|
||||
on:
|
||||
# Deploy package from manual action
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
gh_ref:
|
||||
description: 'Name of the branch'
|
||||
type: choice
|
||||
required: true
|
||||
default: 'develop'
|
||||
options:
|
||||
- develop
|
||||
- staging
|
||||
- main
|
||||
plugin_name:
|
||||
description: 'Pluging name (like plugins/apps/<plugin_name>-plugin)'
|
||||
type: string
|
||||
required: true
|
||||
workflow_call:
|
||||
inputs:
|
||||
gh_ref:
|
||||
description: 'Name of the branch'
|
||||
type: string
|
||||
required: true
|
||||
default: 'develop'
|
||||
plugin_name:
|
||||
description: 'Publig name (from plugins/apps/<plugin_name>-plugin)'
|
||||
type: string
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: penpot-runner-01
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ inputs.gh_ref }}
|
||||
|
||||
# START: Setup Node and PNPM enabling cache
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Enable PNPM
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
|
||||
- name: Get pnpm store path
|
||||
id: pnpm-store
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
# END: Setup Node and PNPM enabling cache
|
||||
|
||||
- name: Install deps
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: |
|
||||
pnpm install --no-frozen-lockfile;
|
||||
pnpm add -D -w wrangler@latest;
|
||||
|
||||
- name: "Build package for ${{ inputs.plugin_name }}-plugin"
|
||||
working-directory: plugins
|
||||
shell: bash
|
||||
run: npx nx build ${{ inputs.plugin_name }}-plugin
|
||||
|
||||
- name: Select Worker name
|
||||
run: |
|
||||
REF="${{ inputs.gh_ref }}"
|
||||
case "$REF" in
|
||||
main)
|
||||
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-pro" >> $GITHUB_ENV
|
||||
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.penpot.app" >> $GITHUB_ENV ;;
|
||||
staging)
|
||||
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-pre" >> $GITHUB_ENV
|
||||
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.penpot.dev" >> $GITHUB_ENV ;;
|
||||
develop)
|
||||
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-hourly" >> $GITHUB_ENV
|
||||
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.hourly.penpot.dev" >> $GITHUB_ENV ;;
|
||||
*) echo "Unsupported branch ${REF}" && exit 1 ;;
|
||||
esac
|
||||
|
||||
- name: Set the custom url
|
||||
working-directory: plugins
|
||||
shell: bash
|
||||
run: |
|
||||
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" apps/${{ inputs.plugin_name }}-plugin/wrangler.toml
|
||||
|
||||
- name: Deploy to Cloudflare Workers
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
workingDirectory: plugins
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: deploy --config apps/${{ inputs.plugin_name }}-plugin/wrangler.toml --name ${{ env.WORKER_NAME }}
|
||||
|
||||
- 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 PLUGINS] Error deploying ${{ env.WORKER_NAME }}.*
|
||||
📄 Triggered from ref: `${{ inputs.gh_ref }}`
|
||||
Plugin name: `${{ inputs.plugin_name }}-plugin`
|
||||
Cloudflare worker name: `${{ env.WORKER_NAME }}`
|
||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
@infra
|
||||
143
.github/workflows/plugins-deploy-packages.yml
vendored
143
.github/workflows/plugins-deploy-packages.yml
vendored
@@ -1,143 +0,0 @@
|
||||
name: Plugins/packages deployer
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- staging
|
||||
- main
|
||||
paths:
|
||||
- 'plugins/apps/*-plugin/**'
|
||||
- 'libs/plugins-styles/**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
gh_ref:
|
||||
description: 'Name of the branch'
|
||||
type: choice
|
||||
required: true
|
||||
default: 'develop'
|
||||
options:
|
||||
- develop
|
||||
- staging
|
||||
- main
|
||||
|
||||
jobs:
|
||||
detect-changes:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
colors_to_tokens: ${{ steps.filter.outputs.colors_to_tokens }}
|
||||
create_palette: ${{ steps.filter.outputs.create_palette }}
|
||||
lorem_ipsum: ${{ steps.filter.outputs.lorem_ipsum }}
|
||||
rename_layers: ${{ steps.filter.outputs.rename_layers }}
|
||||
contrast: ${{ steps.filter.outputs.contrast }}
|
||||
icons: ${{ steps.filter.outputs.icons }}
|
||||
poc_state: ${{ steps.filter.outputs.poc_state }}
|
||||
table: ${{ steps.filter.outputs.table }}
|
||||
# [For new plugins]
|
||||
# Add more outputs here
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: filter
|
||||
uses: dorny/paths-filter@v3
|
||||
with:
|
||||
filters: |
|
||||
colors_to_tokens:
|
||||
- 'plugins/apps/colors-to-tokens-plugin/**'
|
||||
- 'libs/plugins-styles/**'
|
||||
contrast:
|
||||
- 'plugins/apps/contrast-plugin/**'
|
||||
- 'libs/plugins-styles/**'
|
||||
create_palette:
|
||||
- 'plugins/apps/create-palette-plugin/**'
|
||||
- 'libs/plugins-styles/**'
|
||||
icons:
|
||||
- 'plugins/apps/icons-plugin/**'
|
||||
- 'libs/plugins-styles/**'
|
||||
lorem_ipsum:
|
||||
- 'plugins/apps/lorem-ipsum-plugin/**'
|
||||
- 'libs/plugins-styles/**'
|
||||
rename_layers:
|
||||
- 'plugins/apps/rename-layers-plugin/**'
|
||||
- 'libs/plugins-styles/**'
|
||||
table:
|
||||
- 'plugins/apps/table-plugin/**'
|
||||
- 'libs/plugins-styles/**'
|
||||
# [For new plugins]
|
||||
# Add more plugin filters here
|
||||
# another_plugin:
|
||||
# - 'plugins/apps/another-plugin/**'
|
||||
# - 'libs/plugins-styles/**'
|
||||
|
||||
colors-to-tokens-plugin:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.colors_to_tokens == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: colors-to-tokens
|
||||
|
||||
contrast-plugin:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.contrast == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: contrast
|
||||
|
||||
create-palette-plugin:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.create_palette == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: create-palette
|
||||
|
||||
icons-plugin:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.icons == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: icons
|
||||
|
||||
lorem-ipsum-plugin:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.lorem_ipsum == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: lorem-ipsum
|
||||
|
||||
rename-layers-plugin:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.rename_layers == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: rename-layers
|
||||
|
||||
table-plugin:
|
||||
needs: detect-changes
|
||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.table == 'true'
|
||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
plugin_name: table
|
||||
|
||||
# [For new plugins]
|
||||
# Add more jobs for other plugins below, following the same pattern
|
||||
# another-plugin:
|
||||
# needs: detect-changes
|
||||
# if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.another_plugin == 'true'
|
||||
# uses: ./.github/workflows/plugins-deploy-package.yml
|
||||
# secrets: inherit
|
||||
# with:
|
||||
# gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
||||
# plugin_name: another
|
||||
@@ -37,8 +37,7 @@
|
||||
- Fix exception on uploading large fonts [Github #8135](https://github.com/penpot/penpot/pull/8135)
|
||||
- Fix unhandled exception on open-new-window helper [Github #7787](https://github.com/penpot/penpot/issues/7787)
|
||||
- Fix incorrect handling of input values on layout gap and padding inputs [Github #8113](https://github.com/penpot/penpot/issues/8113)
|
||||
- Fix several race conditions on path editor [Github #8187](https://github.com/penpot/penpot/pull/8187)
|
||||
- Fix app freeze when introducing an error on a very long token name [Taiga #13214](https://tree.taiga.io/project/penpot/issue/13214)
|
||||
|
||||
|
||||
## 2.12.1
|
||||
|
||||
|
||||
@@ -124,51 +124,33 @@
|
||||
|
||||
(defn adjust-to-viewport
|
||||
([viewport srect] (adjust-to-viewport viewport srect nil))
|
||||
([viewport srect {:keys [padding min-zoom] :or {padding 0 min-zoom nil}}]
|
||||
([viewport srect {:keys [padding] :or {padding 0}}]
|
||||
(let [gprop (/ (:width viewport)
|
||||
(:height viewport))
|
||||
srect-padded (-> srect
|
||||
(update :x #(- % padding))
|
||||
(update :y #(- % padding))
|
||||
(update :width #(+ % padding padding))
|
||||
(update :height #(+ % padding padding)))
|
||||
width (:width srect-padded)
|
||||
height (:height srect-padded)
|
||||
lprop (/ width height)
|
||||
adjusted-rect
|
||||
(cond
|
||||
(> gprop lprop)
|
||||
(let [width' (* (/ width lprop) gprop)
|
||||
padding (/ (- width' width) 2)]
|
||||
(-> srect-padded
|
||||
(update :x #(- % padding))
|
||||
(assoc :width width')
|
||||
(grc/update-rect :position)))
|
||||
|
||||
(< gprop lprop)
|
||||
(let [height' (/ (* height lprop) gprop)
|
||||
padding (/ (- height' height) 2)]
|
||||
(-> srect-padded
|
||||
(update :y #(- % padding))
|
||||
(assoc :height height')
|
||||
(grc/update-rect :position)))
|
||||
|
||||
:else
|
||||
(grc/update-rect srect-padded :position))]
|
||||
;; If min-zoom is specified and the resulting zoom would be below it,
|
||||
;; return a rect with the original top-left corner centered in the viewport
|
||||
;; instead of using the aspect-ratio-adjusted rect (which can push coords
|
||||
;; extremely far with extreme aspect ratios).
|
||||
(if (and (some? min-zoom)
|
||||
(< (/ (:width viewport) (:width adjusted-rect)) min-zoom))
|
||||
(let [anchor-x (:x srect)
|
||||
anchor-y (:y srect)
|
||||
vbox-width (/ (:width viewport) min-zoom)
|
||||
vbox-height (/ (:height viewport) min-zoom)]
|
||||
(-> adjusted-rect
|
||||
(assoc :x (- anchor-x (/ vbox-width 2))
|
||||
:y (- anchor-y (/ vbox-height 2))
|
||||
:width vbox-width
|
||||
:height vbox-height)
|
||||
srect (-> srect
|
||||
(update :x #(- % padding))
|
||||
(update :y #(- % padding))
|
||||
(update :width #(+ % padding padding))
|
||||
(update :height #(+ % padding padding)))
|
||||
width (:width srect)
|
||||
height (:height srect)
|
||||
lprop (/ width height)]
|
||||
(cond
|
||||
(> gprop lprop)
|
||||
(let [width' (* (/ width lprop) gprop)
|
||||
padding (/ (- width' width) 2)]
|
||||
(-> srect
|
||||
(update :x #(- % padding))
|
||||
(assoc :width width')
|
||||
(grc/update-rect :position)))
|
||||
adjusted-rect))))
|
||||
|
||||
(< gprop lprop)
|
||||
(let [height' (/ (* height lprop) gprop)
|
||||
padding (/ (- height' height) 2)]
|
||||
(-> srect
|
||||
(update :y #(- % padding))
|
||||
(assoc :height height')
|
||||
(grc/update-rect :position)))
|
||||
|
||||
:else
|
||||
(grc/update-rect srect :position)))))
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
|
||||
(def token-name-ref
|
||||
[:re {:title "TokenNameRef" :gen/gen sg/text}
|
||||
#"^[a-zA-Z0-9_-][a-zA-Z0-9$_-]*(\.[a-zA-Z0-9$_-]+)*$"])
|
||||
#"^(?!\$)([a-zA-Z0-9-$_]+\.?)*(?<!\.)$"])
|
||||
|
||||
(def ^:private schema:color
|
||||
[:map
|
||||
|
||||
@@ -214,8 +214,8 @@
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [change-fn
|
||||
(fn [node attrs]
|
||||
(update node :fills types.fills/prepend attrs))
|
||||
(fn [shape attrs]
|
||||
(update shape :fills types.fills/prepend attrs))
|
||||
undo-id
|
||||
(js/Symbol)]
|
||||
(rx/concat
|
||||
|
||||
@@ -105,9 +105,15 @@
|
||||
(if (dsh/lookup-page state file-id page-id)
|
||||
(rx/concat
|
||||
(rx/of (initialize-page* file-id page-id)
|
||||
(fdf/fix-deleted-fonts-for-page file-id page-id)
|
||||
(dwth/watch-state-changes file-id page-id)
|
||||
(dwl/watch-component-changes))
|
||||
(fdf/fix-deleted-fonts-for-page file-id page-id))
|
||||
|
||||
;; Disable thumbnail generation in wasm renderer
|
||||
(if (features/active-feature? state "render-wasm/v1")
|
||||
(rx/empty)
|
||||
(rx/of (dwth/watch-state-changes file-id page-id)))
|
||||
|
||||
(rx/of (dwl/watch-component-changes))
|
||||
|
||||
(let [profile (:profile state)
|
||||
props (get profile :props)]
|
||||
(when (not (:workspace-visited props))
|
||||
|
||||
@@ -70,22 +70,20 @@
|
||||
(= (-> content last :command) :move-to))
|
||||
(into [] (take (dec (count content)) content))
|
||||
content)]
|
||||
(st/set-content state content)))
|
||||
(-> state
|
||||
(st/set-content content))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page-id (:current-page-id state)
|
||||
local (get state :workspace-local)
|
||||
id (get local :edition)
|
||||
objects (dsh/lookup-page-objects state page-id)]
|
||||
objects (dsh/lookup-page-objects state page-id)
|
||||
id (dm/get-in state [:workspace-local :edition])
|
||||
old-content (dm/get-in state [:workspace-local :edit-path id :old-content])
|
||||
shape (st/get-path state)]
|
||||
|
||||
;; NOTE: we proceed only if the shape is present on the
|
||||
;; objects, if shape is a ephimeral drawing shape, we should
|
||||
;; do nothing
|
||||
(when-let [shape (get objects id)]
|
||||
(when-let [old-content (dm/get-in local [:edit-path id :old-content])]
|
||||
(let [new-content (get shape :content)
|
||||
changes (generate-path-changes it objects page-id shape old-content new-content)]
|
||||
(rx/of (dch/commit-changes changes))))))))))
|
||||
(if (and (some? old-content) (some? (:id shape)))
|
||||
(let [changes (generate-path-changes it objects page-id shape old-content (:content shape))]
|
||||
(rx/of (dch/commit-changes changes)))
|
||||
(rx/empty)))))))
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.types.path :as path]
|
||||
[app.common.types.path.helpers :as path.helpers]
|
||||
@@ -288,34 +289,34 @@
|
||||
|
||||
(declare stop-path-edit)
|
||||
|
||||
|
||||
(defn start-path-edit
|
||||
[id]
|
||||
(ptk/reify ::start-path-edit
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [objects (dsh/lookup-page-objects state)
|
||||
shape (get objects id)]
|
||||
edit-path (dm/get-in state [:workspace-local :edit-path id])
|
||||
content (st/get-path state :content)
|
||||
state (cond-> state
|
||||
(cfh/path-shape? objects id)
|
||||
(st/set-content (path/close-subpaths content)))]
|
||||
|
||||
(-> state
|
||||
(st/set-content (path/close-subpaths (:content shape)))
|
||||
(update-in [:workspace-local :edit-path id]
|
||||
(fn [state]
|
||||
(let [state (if state
|
||||
(if (= :move (:edit-mode state))
|
||||
(assoc state :edit-mode :draw)
|
||||
state)
|
||||
{:edit-mode :move
|
||||
:selected #{}
|
||||
:snap-toggled false})]
|
||||
(assoc state :old-content (:content shape))))))))
|
||||
(cond-> state
|
||||
(or (not edit-path)
|
||||
(= :draw (:edit-mode edit-path)))
|
||||
(assoc-in [:workspace-local :edit-path id] {:edit-mode :move
|
||||
:selected #{}
|
||||
:snap-toggled false})
|
||||
(and (some? edit-path)
|
||||
(= :move (:edit-mode edit-path)))
|
||||
(assoc-in [:workspace-local :edit-path id :edit-mode] :draw))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ stream]
|
||||
(let [stopper (rx/filter #(let [type (ptk/type %)]
|
||||
(= type ::dwe/clear-edition-mode)
|
||||
(= type ::start-path-edit))
|
||||
stream)]
|
||||
(let [stopper (->> stream
|
||||
(rx/filter #(let [type (ptk/type %)]
|
||||
(= type ::dwe/clear-edition-mode)
|
||||
(= type ::start-path-edit))))]
|
||||
(rx/concat
|
||||
(rx/of (undo/start-path-undo))
|
||||
(->> stream
|
||||
@@ -324,8 +325,7 @@
|
||||
(rx/map #(stop-path-edit id))
|
||||
(rx/take-until stopper)))))))
|
||||
|
||||
(defn stop-path-edit
|
||||
[id]
|
||||
(defn stop-path-edit [id]
|
||||
(ptk/reify ::stop-path-edit
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
@@ -335,12 +335,13 @@
|
||||
(watch [_ _ _]
|
||||
(rx/of (ptk/data-event :layout/update {:ids [id]})))))
|
||||
|
||||
(defn- split-segments
|
||||
[id {:keys [from-p to-p t]}]
|
||||
(defn split-segments
|
||||
[{:keys [from-p to-p t]}]
|
||||
(ptk/reify ::split-segments
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [content (st/get-path state :content)]
|
||||
(let [id (st/get-path-id state)
|
||||
content (st/get-path state :content)]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :edit-path id :old-content] content)
|
||||
(st/set-content (-> content
|
||||
@@ -352,10 +353,10 @@
|
||||
(rx/of (changes/save-path-content {:preserve-move-to true})))))
|
||||
|
||||
(defn create-node-at-position
|
||||
[params]
|
||||
[event]
|
||||
(ptk/reify ::create-node-at-position
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [id (st/get-path-id state)]
|
||||
(rx/of (dwsh/update-shapes [id] path/convert-to-path)
|
||||
(split-segments id params))))))
|
||||
(split-segments event))))))
|
||||
|
||||
@@ -191,59 +191,63 @@
|
||||
[page-id [event [old-data new-data]]]
|
||||
|
||||
(let [changes (:changes event)
|
||||
lookup-data-objects
|
||||
(fn [data page-id]
|
||||
(dm/get-in data [:pages-index page-id :objects]))
|
||||
;; cache for the get-frame-ids function
|
||||
frame-id-cache (atom {})]
|
||||
|
||||
(letfn [(lookup-data-objects [data page-id]
|
||||
(dm/get-in data [:pages-index page-id :objects]))
|
||||
|
||||
extract-ids
|
||||
(fn [{:keys [page-id type] :as change}]
|
||||
(case type
|
||||
:add-obj [[page-id (:id change)]]
|
||||
:mod-obj [[page-id (:id change)]]
|
||||
:del-obj [[page-id (:id change)]]
|
||||
:mov-objects (->> (:shapes change) (map #(vector page-id %)))
|
||||
[]))
|
||||
(extract-ids [{:keys [page-id type] :as change}]
|
||||
(case type
|
||||
:add-obj [[page-id (:id change)]]
|
||||
:mod-obj [[page-id (:id change)]]
|
||||
:del-obj [[page-id (:id change)]]
|
||||
:mov-objects (->> (:shapes change) (map #(vector page-id %)))
|
||||
[]))
|
||||
|
||||
get-frame-ids
|
||||
(fn get-frame-ids [id]
|
||||
(let [old-objects (lookup-data-objects old-data page-id)
|
||||
new-objects (lookup-data-objects new-data page-id)
|
||||
(get-frame-ids [id]
|
||||
(let [old-objects (lookup-data-objects old-data page-id)
|
||||
new-objects (lookup-data-objects new-data page-id)
|
||||
|
||||
new-shape (get new-objects id)
|
||||
old-shape (get old-objects id)
|
||||
new-shape (get new-objects id)
|
||||
old-shape (get old-objects id)
|
||||
|
||||
old-frame-id (if (cfh/frame-shape? old-shape) id (:frame-id old-shape))
|
||||
new-frame-id (if (cfh/frame-shape? new-shape) id (:frame-id new-shape))
|
||||
old-frame-id (if (cfh/frame-shape? old-shape) id (:frame-id old-shape))
|
||||
new-frame-id (if (cfh/frame-shape? new-shape) id (:frame-id new-shape))
|
||||
|
||||
root-frame-old? (cfh/root-frame? old-objects old-frame-id)
|
||||
root-frame-new? (cfh/root-frame? new-objects new-frame-id)
|
||||
instance-root? (ctc/instance-root? new-shape)]
|
||||
root-frame-old? (cfh/root-frame? old-objects old-frame-id)
|
||||
root-frame-new? (cfh/root-frame? new-objects new-frame-id)
|
||||
instance-root? (ctc/instance-root? new-shape)]
|
||||
|
||||
(cond-> #{}
|
||||
root-frame-old?
|
||||
(conj ["frame" old-frame-id])
|
||||
(cond-> #{}
|
||||
root-frame-old?
|
||||
(conj ["frame" old-frame-id])
|
||||
|
||||
root-frame-new?
|
||||
(conj ["frame" new-frame-id])
|
||||
root-frame-new?
|
||||
(conj ["frame" new-frame-id])
|
||||
|
||||
instance-root?
|
||||
(conj ["component" id])
|
||||
instance-root?
|
||||
(conj ["component" id])
|
||||
|
||||
(and (uuid? (:frame-id old-shape))
|
||||
(not= uuid/zero (:frame-id old-shape)))
|
||||
(into (get-frame-ids (:frame-id old-shape)))
|
||||
(and (uuid? (:frame-id old-shape))
|
||||
(not= uuid/zero (:frame-id old-shape)))
|
||||
(into (get-frame-ids (:frame-id old-shape)))
|
||||
|
||||
(and (uuid? (:frame-id new-shape))
|
||||
(not= uuid/zero (:frame-id new-shape)))
|
||||
(into (get-frame-ids (:frame-id new-shape))))))]
|
||||
(and (uuid? (:frame-id new-shape))
|
||||
(not= uuid/zero (:frame-id new-shape)))
|
||||
(into (get-frame-ids (:frame-id new-shape))))))
|
||||
|
||||
(into #{}
|
||||
(comp (mapcat extract-ids)
|
||||
(filter (fn [[page-id']] (= page-id page-id')))
|
||||
(map (fn [[_ id]] id))
|
||||
(mapcat get-frame-ids))
|
||||
changes)))
|
||||
(get-frame-ids-cached [id]
|
||||
(or (get @frame-id-cache id)
|
||||
(let [result (get-frame-ids id)]
|
||||
(swap! frame-id-cache assoc id result)
|
||||
result)))]
|
||||
(into #{}
|
||||
(comp (mapcat extract-ids)
|
||||
(filter (fn [[page-id']] (= page-id page-id')))
|
||||
(map (fn [[_ id]] id))
|
||||
(mapcat get-frame-ids-cached))
|
||||
changes))))
|
||||
|
||||
(defn watch-state-changes
|
||||
"Watch the state for changes inside frames. If a change is detected will force a rendering
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
(or (> (:width srect) width)
|
||||
(> (:height srect) height))
|
||||
(let [srect (gal/adjust-to-viewport size srect {:padding 40 :min-zoom 0.01})
|
||||
(let [srect (gal/adjust-to-viewport size srect {:padding 40})
|
||||
zoom (/ (:width size) (:width srect))]
|
||||
|
||||
(-> local
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
state
|
||||
(update state :workspace-local
|
||||
(fn [{:keys [vport] :as local}]
|
||||
(let [srect (gal/adjust-to-viewport vport srect {:padding 160 :min-zoom 0.01})
|
||||
(let [srect (gal/adjust-to-viewport vport srect {:padding 160})
|
||||
zoom (/ (:width vport) (:width srect))]
|
||||
(-> local
|
||||
(assoc :zoom zoom)
|
||||
@@ -118,7 +118,7 @@
|
||||
(gsh/shapes->rect))]
|
||||
(update state :workspace-local
|
||||
(fn [{:keys [vport] :as local}]
|
||||
(let [srect (gal/adjust-to-viewport vport srect {:padding 40 :min-zoom 0.01})
|
||||
(let [srect (gal/adjust-to-viewport vport srect {:padding 40})
|
||||
zoom (/ (:width vport) (:width srect))]
|
||||
(-> local
|
||||
(assoc :zoom zoom)
|
||||
@@ -142,7 +142,7 @@
|
||||
(fn [{:keys [vport] :as local}]
|
||||
(let [srect (gal/adjust-to-viewport
|
||||
vport srect
|
||||
{:padding 40 :min-zoom 0.01})
|
||||
{:padding 40})
|
||||
zoom (/ (:width vport)
|
||||
(:width srect))]
|
||||
(-> local
|
||||
|
||||
@@ -238,8 +238,7 @@ export class SelectionController extends EventTarget {
|
||||
#applyStylesFromElementToCurrentStyle(element) {
|
||||
for (let index = 0; index < element.style.length; index++) {
|
||||
const styleName = element.style.item(index);
|
||||
// Only merge fill styles from text spans.
|
||||
if (!isTextSpan(element) && styleName === "--fills") {
|
||||
if (styleName === "--fills") {
|
||||
continue;
|
||||
}
|
||||
let styleValue = element.style.getPropertyValue(styleName);
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
name = "color-to-tokens-plugin"
|
||||
compatibility_date = "2025-01-01"
|
||||
|
||||
assets = { directory = "../../dist/apps/colors-to-tokens-plugin/browser" }
|
||||
|
||||
[[routes]]
|
||||
pattern = "WORKER_URI"
|
||||
custom_domain = true
|
||||
@@ -1,8 +0,0 @@
|
||||
name = "contrast-plugin"
|
||||
compatibility_date = "2025-01-01"
|
||||
|
||||
assets = { directory = "../../dist/apps/contrast-plugin/browser" }
|
||||
|
||||
[[routes]]
|
||||
pattern = "WORKER_URI"
|
||||
custom_domain = true
|
||||
@@ -1,8 +0,0 @@
|
||||
name = "create-palette-plugin"
|
||||
compatibility_date = "2025-01-01"
|
||||
|
||||
assets = { directory = "../../dist/apps/create-palette-plugin" }
|
||||
|
||||
[[routes]]
|
||||
pattern = "WORKER_URI"
|
||||
custom_domain = true
|
||||
@@ -1,8 +0,0 @@
|
||||
name = "icons-plugin"
|
||||
compatibility_date = "2025-01-01"
|
||||
|
||||
assets = { directory = "../../dist/apps/icons-plugin/browser" }
|
||||
|
||||
[[routes]]
|
||||
pattern = "WORKER_URI"
|
||||
custom_domain = true
|
||||
@@ -1,8 +0,0 @@
|
||||
name = "lorem-ipsum-plugin"
|
||||
compatibility_date = "2025-01-01"
|
||||
|
||||
assets = { directory = "../../dist/apps/lorem-ipsum-plugin/browser" }
|
||||
|
||||
[[routes]]
|
||||
pattern = "WORKER_URI"
|
||||
custom_domain = true
|
||||
@@ -1,8 +0,0 @@
|
||||
name = "rename-layers-plugin"
|
||||
compatibility_date = "2025-01-01"
|
||||
|
||||
assets = { directory = "../../dist/apps/rename-layers-plugin/browser" }
|
||||
|
||||
[[routes]]
|
||||
pattern = "WORKER_URI"
|
||||
custom_domain = true
|
||||
@@ -1,8 +0,0 @@
|
||||
name = "table-plugin"
|
||||
compatibility_date = "2025-01-01"
|
||||
|
||||
assets = { directory = "../../dist/apps/table-plugin/browser" }
|
||||
|
||||
[[routes]]
|
||||
pattern = "WORKER_URI"
|
||||
custom_domain = true
|
||||
@@ -275,26 +275,29 @@ pub extern "C" fn set_view_end() {
|
||||
state.render_state.options.set_fast_mode(false);
|
||||
state.render_state.cancel_animation_frame();
|
||||
|
||||
// Update tile_viewbox first so that get_tiles_for_shape uses the correct interest area
|
||||
// This is critical because we limit tiles to the interest area for optimization
|
||||
let scale = state.render_state.get_scale();
|
||||
state
|
||||
.render_state
|
||||
.tile_viewbox
|
||||
.update(state.render_state.viewbox, scale);
|
||||
|
||||
// We rebuild the tile index on both pan and zoom because `get_tiles_for_shape`
|
||||
// clips each shape to the current `TileViewbox::interest_rect` (viewport-dependent).
|
||||
let _rebuild_start = performance::begin_timed_log!("rebuild_tiles");
|
||||
performance::begin_measure!("set_view_end::rebuild_tiles");
|
||||
if state.render_state.options.is_profile_rebuild_tiles() {
|
||||
state.rebuild_tiles();
|
||||
let zoom_changed = state.render_state.zoom_changed();
|
||||
// Only rebuild tile indices when zoom has changed.
|
||||
// During pan-only operations, shapes stay in the same tiles
|
||||
// because tile_size = 1/scale * TILE_SIZE (depends only on zoom).
|
||||
if zoom_changed {
|
||||
let _rebuild_start = performance::begin_timed_log!("rebuild_tiles");
|
||||
performance::begin_measure!("set_view_end::rebuild_tiles");
|
||||
if state.render_state.options.is_profile_rebuild_tiles() {
|
||||
state.rebuild_tiles();
|
||||
} else {
|
||||
state.rebuild_tiles_shallow();
|
||||
}
|
||||
performance::end_measure!("set_view_end::rebuild_tiles");
|
||||
performance::end_timed_log!("rebuild_tiles", _rebuild_start);
|
||||
} else {
|
||||
state.rebuild_tiles_shallow();
|
||||
// During pan, we only clear the tile index without
|
||||
// invalidating cached textures, which is more efficient.
|
||||
let _clear_start = performance::begin_timed_log!("clear_tile_index");
|
||||
performance::begin_measure!("set_view_end::clear_tile_index");
|
||||
state.clear_tile_index();
|
||||
performance::end_measure!("set_view_end::clear_tile_index");
|
||||
performance::end_timed_log!("clear_tile_index", _clear_start);
|
||||
}
|
||||
performance::end_measure!("set_view_end::rebuild_tiles");
|
||||
performance::end_timed_log!("rebuild_tiles", _rebuild_start);
|
||||
|
||||
state.render_state.sync_cached_viewbox();
|
||||
performance::end_measure!("set_view_end");
|
||||
performance::end_timed_log!("set_view_end", _end_start);
|
||||
|
||||
@@ -1168,6 +1168,7 @@ impl RenderState {
|
||||
let scale = self.get_scale();
|
||||
|
||||
self.tile_viewbox.update(self.viewbox, scale);
|
||||
|
||||
self.focus_mode.reset();
|
||||
|
||||
performance::begin_measure!("render");
|
||||
@@ -2110,44 +2111,13 @@ impl RenderState {
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a shape returns the TileRect with the range of tiles that the shape is in.
|
||||
* This is always limited to the interest area to optimize performance and prevent
|
||||
* processing unnecessary tiles outside the viewport. The interest area already
|
||||
* includes a margin (VIEWPORT_INTEREST_AREA_THRESHOLD) calculated via
|
||||
* get_tiles_for_viewbox_with_interest, ensuring smooth pan/zoom interactions.
|
||||
*
|
||||
* When the viewport changes (pan/zoom), the interest area is updated and shapes
|
||||
* are dynamically added to the tile index via the fallback mechanism in
|
||||
* render_shape_tree_partial_uncached, ensuring all shapes render correctly.
|
||||
* Given a shape returns the TileRect with the range of tiles that the shape is in
|
||||
*/
|
||||
pub fn get_tiles_for_shape(&mut self, shape: &Shape, tree: ShapesPoolRef) -> TileRect {
|
||||
let scale = self.get_scale();
|
||||
let extrect = self.get_cached_extrect(shape, tree, scale);
|
||||
let tile_size = tiles::get_tile_size(scale);
|
||||
let shape_tiles = tiles::get_tiles_for_rect(extrect, tile_size);
|
||||
let interest_rect = &self.tile_viewbox.interest_rect;
|
||||
// Calculate the intersection of shape_tiles with interest_rect
|
||||
// This returns only the tiles that are both in the shape and in the interest area
|
||||
let intersection_x1 = shape_tiles.x1().max(interest_rect.x1());
|
||||
let intersection_y1 = shape_tiles.y1().max(interest_rect.y1());
|
||||
let intersection_x2 = shape_tiles.x2().min(interest_rect.x2());
|
||||
let intersection_y2 = shape_tiles.y2().min(interest_rect.y2());
|
||||
|
||||
// Return the intersection if valid (there is overlap), otherwise return empty rect
|
||||
if intersection_x1 <= intersection_x2 && intersection_y1 <= intersection_y2 {
|
||||
// Valid intersection: return the tiles that are in both shape_tiles and interest_rect
|
||||
TileRect(
|
||||
intersection_x1,
|
||||
intersection_y1,
|
||||
intersection_x2,
|
||||
intersection_y2,
|
||||
)
|
||||
} else {
|
||||
// No intersection: shape is completely outside interest area
|
||||
// The shape will be added dynamically via add_shape_tiles when it enters
|
||||
// the interest area during pan/zoom operations
|
||||
TileRect(0, 0, -1, -1)
|
||||
}
|
||||
tiles::get_tiles_for_rect(extrect, tile_size)
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2228,6 +2198,17 @@ impl RenderState {
|
||||
performance::end_measure!("rebuild_tiles_shallow");
|
||||
}
|
||||
|
||||
/// Clears the tile index without invalidating cached tile textures.
|
||||
/// This is useful when tile positions don't change (e.g., during pan operations)
|
||||
/// but the tile index needs to be synchronized. The cached tile textures remain
|
||||
/// valid since they don't depend on the current view position, only on zoom level.
|
||||
/// This is much more efficient than clearing the entire cache surface.
|
||||
pub fn clear_tile_index(&mut self) {
|
||||
performance::begin_measure!("clear_tile_index");
|
||||
self.surfaces.clear_tiles();
|
||||
performance::end_measure!("clear_tile_index");
|
||||
}
|
||||
|
||||
pub fn rebuild_tiles_from(&mut self, tree: ShapesPoolRef, base_id: Option<&Uuid>) {
|
||||
performance::begin_measure!("rebuild_tiles");
|
||||
|
||||
|
||||
@@ -207,6 +207,10 @@ impl State {
|
||||
self.render_state.rebuild_tiles_shallow(&self.shapes);
|
||||
}
|
||||
|
||||
pub fn clear_tile_index(&mut self) {
|
||||
self.render_state.clear_tile_index();
|
||||
}
|
||||
|
||||
pub fn rebuild_tiles(&mut self) {
|
||||
self.render_state.rebuild_tiles_from(&self.shapes, None);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user