diff --git a/frontend/src/app/main/data/plugins.cljs b/frontend/src/app/main/data/plugins.cljs index a075e54a12..905908fa2b 100644 --- a/frontend/src/app/main/data/plugins.cljs +++ b/frontend/src/app/main/data/plugins.cljs @@ -44,6 +44,20 @@ (update [_ state] (update-in state [:workspace-local :open-plugins] (fnil conj #{}) id)))) +(defn reset-plugin-flags + [id] + (ptk/reify ::reset-plugin-flags + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-local :plugin-flags] assoc id {})))) + +(defn set-plugin-flag + [id key value] + (ptk/reify ::reset-plugin-flags + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-local :plugin-flags id] assoc key value)))) + (defn remove-current-plugin [id] (ptk/reify ::remove-current-plugin @@ -54,7 +68,9 @@ (defn- load-plugin! [{:keys [plugin-id name description host code icon permissions]}] (try - (st/emit! (save-current-plugin plugin-id)) + (st/emit! (save-current-plugin plugin-id) + (reset-plugin-flags plugin-id)) + (.ɵloadPlugin ^js ug/global #js {:pluginId plugin-id diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index be97f52a78..cd4cfff542 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -32,6 +32,7 @@ [app.main.ui.shapes.text.fontfaces :refer [shapes->fonts]] [app.plugins.events :as events] [app.plugins.file :as file] + [app.plugins.flags :as flags] [app.plugins.fonts :as fonts] [app.plugins.format :as format] [app.plugins.history :as history] @@ -124,6 +125,9 @@ :fonts {:get (fn [] (fonts/fonts-subcontext plugin-id))} + :flags + {:get (fn [] (flags/flags-proxy plugin-id))} + :library {:get (fn [] (library/library-subcontext plugin-id))} diff --git a/frontend/src/app/plugins/flags.cljs b/frontend/src/app/plugins/flags.cljs new file mode 100644 index 0000000000..0e8a10a5da --- /dev/null +++ b/frontend/src/app/plugins/flags.cljs @@ -0,0 +1,33 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.plugins.flags + (:require + [app.main.data.plugins :as dp] + [app.main.store :as st] + [app.plugins.utils :as u] + [app.util.object :as obj])) + +(defn flags-proxy + [plugin-id] + (obj/reify {:name "FlagProxy"} + :naturalChildOrdering + {:this false + :get + (fn [] + (boolean + (get-in + @st/state + [:workspace-local :plugin-flags plugin-id :natural-child-ordering]))) + + :set + (fn [value] + (cond + (not (boolean? value)) + (u/display-not-valid :naturalChildOrdering value) + + :else + (st/emit! (dp/set-plugin-flag plugin-id :natural-child-ordering value))))})) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index ec39712420..e85efe6115 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -52,6 +52,7 @@ [app.plugins.parser :as parser] [app.plugins.register :as r] [app.plugins.ruler-guides :as rg] + [app.plugins.state :refer [natural-child-ordering?]] [app.plugins.text :as text] [app.plugins.utils :as u] [app.util.http :as http] @@ -921,7 +922,6 @@ (fn [] (let [shape (u/locate-shape file-id page-id id)] (cond - (and (not (cfh/frame-shape? shape)) (not (cfh/group-shape? shape)) (not (cfh/svg-raw-shape? shape)) @@ -929,9 +929,14 @@ (u/display-not-valid :getChildren (:type shape)) :else - (->> (u/locate-shape file-id page-id id) - (:shapes) - (format/format-array #(shape-proxy plugin-id file-id page-id %)))))) + (let [is-reversed? (ctl/flex-layout? shape) + reverse-fn + (if (and (natural-child-ordering? plugin-id) is-reversed?) + reverse identity)] + (->> (u/locate-shape file-id page-id id) + (:shapes) + (reverse-fn) + (format/format-array #(shape-proxy plugin-id file-id page-id %))))))) :appendChild (fn [child] @@ -950,8 +955,10 @@ (u/display-not-valid :appendChild "Plugin doesn't have 'content:write' permission") :else - (let [child-id (obj/get child "$id")] - (st/emit! (dwsh/relocate-shapes #{child-id} id 0)))))) + (let [child-id (obj/get child "$id") + is-reversed? (ctl/flex-layout? shape) + index (if (and (natural-child-ordering? plugin-id) is-reversed?) 0 (count (:shapes shape)))] + (st/emit! (dwsh/relocate-shapes #{child-id} id index)))))) :insertChild (fn [index child] @@ -970,7 +977,12 @@ (u/display-not-valid :insertChild "Plugin doesn't have 'content:write' permission") :else - (let [child-id (obj/get child "$id")] + (let [child-id (obj/get child "$id") + is-reversed? (ctl/flex-layout? shape) + index + (if (and (natural-child-ordering? plugin-id) is-reversed?) + (- (count (:shapes shape)) index) + index)] (st/emit! (dwsh/relocate-shapes #{child-id} id index)))))) ;; Only for frames @@ -1360,7 +1372,8 @@ (let [shape (u/proxy->shape self) file-id (obj/get self "$file") page-id (obj/get self "$page") - ids (->> children (map #(obj/get % "$id")))] + reverse-fn (if (natural-child-ordering? plugin-id) reverse identity) + ids (->> children reverse-fn (map #(obj/get % "$id")))] (cond (not= (set ids) (set (:shapes shape))) diff --git a/frontend/src/app/plugins/state.cljs b/frontend/src/app/plugins/state.cljs new file mode 100644 index 0000000000..25c931571f --- /dev/null +++ b/frontend/src/app/plugins/state.cljs @@ -0,0 +1,16 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.plugins.state + (:require + [app.main.store :as st])) + +(defn natural-child-ordering? + [plugin-id] + (boolean + (get-in + @st/state + [:workspace-local :plugin-flags plugin-id :natural-child-ordering]))) diff --git a/plugins/CHANGELOG.md b/plugins/CHANGELOG.md index e15c253923..a5d5faff84 100644 --- a/plugins/CHANGELOG.md +++ b/plugins/CHANGELOG.md @@ -1,6 +1,10 @@ +## 1.5.0 (Unreleased) + +- **plugin-types**: Added a flags subcontexts with the flag `naturalChildrenOrdering` + ## 1.4.2 (2026-01-21) -- **plugin-types:** fix atob/btoa functions +- **plugin-runtime:** fix atob/btoa functions ## 1.4.0 (2026-01-21) diff --git a/plugins/libs/plugin-types/index.d.ts b/plugins/libs/plugin-types/index.d.ts index 9f85bc864a..ff56cb2490 100644 --- a/plugins/libs/plugin-types/index.d.ts +++ b/plugins/libs/plugin-types/index.d.ts @@ -804,6 +804,11 @@ export interface Context { */ readonly viewport: Viewport; + /** + * Provides flags to customize the API behavior. + */ + readonly flags: Flags; + /** * Context encapsulating the history operations * @@ -1679,6 +1684,19 @@ export interface Fill { fillImage?: ImageData; } +/** + * This subcontext allows the API o change certain defaults + */ +export interface Flags { + /** + * If `true` the .children property will be always sorted in the z-index ordering. + * Also, appendChild method will be append the children in the top-most position. + * The insertchild method is changed acordingly to respect this ordering. + * Defaults to false + */ + naturalChildOrdering: boolean; +} + /** * Represents a flexible layout configuration in Penpot. * This interface extends `CommonLayout` and includes properties for defining the direction, diff --git a/plugins/libs/plugins-runtime/src/lib/api/index.ts b/plugins/libs/plugins-runtime/src/lib/api/index.ts index 7ee49458f4..53cc8eceac 100644 --- a/plugins/libs/plugins-runtime/src/lib/api/index.ts +++ b/plugins/libs/plugins-runtime/src/lib/api/index.ts @@ -1,31 +1,32 @@ import type { - Penpot, - EventsMap, - Page, - Shape, - Rectangle, - Board, - Group, - Viewport, - Text, - File, - Theme, - LibraryContext, - Ellipse, - Path, - BooleanType, - Boolean, - User, ActiveUser, - FontsContext, - SvgRaw, + Board, + Boolean, + BooleanType, Color, ColorShapeInfo, + Ellipse, + EventsMap, + File, + Flags, + FontsContext, + Group, HistoryContext, - LocalStorage, - VariantContainer, LibraryComponent, + LibraryContext, LibraryVariantComponent, + LocalStorage, + Page, + Path, + Penpot, + Rectangle, + Shape, + SvgRaw, + Text, + Theme, + User, + VariantContainer, + Viewport, } from '@penpot/plugin-types'; import { Permissions } from '../models/manifest.model.js'; @@ -193,6 +194,10 @@ export function createApi( return plugin.context.fonts; }, + get flags(): Flags { + return plugin.context.flags; + }, + get currentUser(): User { checkPermission('user:read'); return plugin.context.currentUser;