diff --git a/.github/workflows/plugins-deploy-api-doc.yml b/.github/workflows/plugins-deploy-api-doc.yml index 62a87745bb..f8451e9816 100644 --- a/.github/workflows/plugins-deploy-api-doc.yml +++ b/.github/workflows/plugins-deploy-api-doc.yml @@ -11,7 +11,7 @@ on: - "plugins/libs/plugin-types/REAME.md" - "plugins/tools/typedoc.css" - "plugins/CHANGELOG.md" - - "plugins/wrangle-penpot-plugins-api-doc.toml" + - "plugins/wrangler-penpot-plugins-api-doc.toml" workflow_dispatch: inputs: gh_ref: @@ -98,4 +98,16 @@ jobs: workingDirectory: plugins apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - command: deploy --config wrangle-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }} + 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 diff --git a/.github/workflows/plugins-deploy-package.yml b/.github/workflows/plugins-deploy-package.yml new file mode 100644 index 0000000000..cad4b1524f --- /dev/null +++ b/.github/workflows/plugins-deploy-package.yml @@ -0,0 +1,127 @@ +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)' + 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)' + 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 diff --git a/.github/workflows/plugins-deploy-packages.yml b/.github/workflows/plugins-deploy-packages.yml new file mode 100644 index 0000000000..3223bc52a6 --- /dev/null +++ b/.github/workflows/plugins-deploy-packages.yml @@ -0,0 +1,143 @@ +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 diff --git a/CHANGES.md b/CHANGES.md index 3cefb829b5..6936a46f31 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -37,7 +37,8 @@ - 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 diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index 5ee3661a91..6a9d830a3a 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -99,7 +99,7 @@ (def token-name-ref [:re {:title "TokenNameRef" :gen/gen sg/text} - #"^(?!\$)([a-zA-Z0-9-$_]+\.?)*(? content last :command) :move-to)) (into [] (take (dec (count content)) content)) content)] - (-> state - (st/set-content content)))) + (st/set-content state content))) ptk/WatchEvent (watch [it state _] (let [page-id (:current-page-id state) - 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)] + local (get state :workspace-local) + id (get local :edition) + objects (dsh/lookup-page-objects state page-id)] - (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))))))) + ;; 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)))))))))) diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index 1210111f71..a97947ea05 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -8,7 +8,6 @@ (: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] @@ -289,34 +288,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) - 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)))] + shape (get objects id)] - (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)))) + (-> 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)))))))) ptk/WatchEvent (watch [_ _ stream] - (let [stopper (->> stream - (rx/filter #(let [type (ptk/type %)] - (= type ::dwe/clear-edition-mode) - (= type ::start-path-edit))))] + (let [stopper (rx/filter #(let [type (ptk/type %)] + (= type ::dwe/clear-edition-mode) + (= type ::start-path-edit)) + stream)] (rx/concat (rx/of (undo/start-path-undo)) (->> stream @@ -325,7 +324,8 @@ (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,13 +335,12 @@ (watch [_ _ _] (rx/of (ptk/data-event :layout/update {:ids [id]}))))) -(defn split-segments - [{:keys [from-p to-p t]}] +(defn- split-segments + [id {:keys [from-p to-p t]}] (ptk/reify ::split-segments ptk/UpdateEvent (update [_ state] - (let [id (st/get-path-id state) - content (st/get-path state :content)] + (let [content (st/get-path state :content)] (-> state (assoc-in [:workspace-local :edit-path id :old-content] content) (st/set-content (-> content @@ -353,10 +352,10 @@ (rx/of (changes/save-path-content {:preserve-move-to true}))))) (defn create-node-at-position - [event] + [params] (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 event)))))) + (split-segments id params)))))) diff --git a/plugins/apps/colors-to-tokens-plugin/wrangler.toml b/plugins/apps/colors-to-tokens-plugin/wrangler.toml new file mode 100644 index 0000000000..7f48730a36 --- /dev/null +++ b/plugins/apps/colors-to-tokens-plugin/wrangler.toml @@ -0,0 +1,8 @@ +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 diff --git a/plugins/apps/contrast-plugin/wrangler.toml b/plugins/apps/contrast-plugin/wrangler.toml new file mode 100644 index 0000000000..86f456ec95 --- /dev/null +++ b/plugins/apps/contrast-plugin/wrangler.toml @@ -0,0 +1,8 @@ +name = "contrast-plugin" +compatibility_date = "2025-01-01" + +assets = { directory = "../../dist/apps/contrast-plugin/browser" } + +[[routes]] +pattern = "WORKER_URI" +custom_domain = true diff --git a/plugins/apps/create-palette-plugin/wrangler.toml b/plugins/apps/create-palette-plugin/wrangler.toml new file mode 100644 index 0000000000..40f4f67a38 --- /dev/null +++ b/plugins/apps/create-palette-plugin/wrangler.toml @@ -0,0 +1,8 @@ +name = "create-palette-plugin" +compatibility_date = "2025-01-01" + +assets = { directory = "../../dist/apps/create-palette-plugin" } + +[[routes]] +pattern = "WORKER_URI" +custom_domain = true diff --git a/plugins/apps/icons-plugin/wrangler.toml b/plugins/apps/icons-plugin/wrangler.toml new file mode 100644 index 0000000000..0a690dac57 --- /dev/null +++ b/plugins/apps/icons-plugin/wrangler.toml @@ -0,0 +1,8 @@ +name = "icons-plugin" +compatibility_date = "2025-01-01" + +assets = { directory = "../../dist/apps/icons-plugin/browser" } + +[[routes]] +pattern = "WORKER_URI" +custom_domain = true diff --git a/plugins/apps/lorem-ipsum-plugin/wrangler.toml b/plugins/apps/lorem-ipsum-plugin/wrangler.toml new file mode 100644 index 0000000000..398691c3ba --- /dev/null +++ b/plugins/apps/lorem-ipsum-plugin/wrangler.toml @@ -0,0 +1,8 @@ +name = "lorem-ipsum-plugin" +compatibility_date = "2025-01-01" + +assets = { directory = "../../dist/apps/lorem-ipsum-plugin/browser" } + +[[routes]] +pattern = "WORKER_URI" +custom_domain = true diff --git a/plugins/apps/rename-layers-plugin/wrangler.toml b/plugins/apps/rename-layers-plugin/wrangler.toml new file mode 100644 index 0000000000..4fdc18597d --- /dev/null +++ b/plugins/apps/rename-layers-plugin/wrangler.toml @@ -0,0 +1,8 @@ +name = "rename-layers-plugin" +compatibility_date = "2025-01-01" + +assets = { directory = "../../dist/apps/rename-layers-plugin/browser" } + +[[routes]] +pattern = "WORKER_URI" +custom_domain = true diff --git a/plugins/apps/table-plugin/wrangler.toml b/plugins/apps/table-plugin/wrangler.toml new file mode 100644 index 0000000000..9c95160a01 --- /dev/null +++ b/plugins/apps/table-plugin/wrangler.toml @@ -0,0 +1,8 @@ +name = "table-plugin" +compatibility_date = "2025-01-01" + +assets = { directory = "../../dist/apps/table-plugin/browser" } + +[[routes]] +pattern = "WORKER_URI" +custom_domain = true diff --git a/plugins/wrangle-penpot-plugins-api-doc.toml b/plugins/wrangler-penpot-plugins-api-doc.toml similarity index 100% rename from plugins/wrangle-penpot-plugins-api-doc.toml rename to plugins/wrangler-penpot-plugins-api-doc.toml