diff --git a/frontend/src/app/main/data/workspace/variants.cljs b/frontend/src/app/main/data/workspace/variants.cljs index 28f1a30963..b21afdc8b7 100644 --- a/frontend/src/app/main/data/workspace/variants.cljs +++ b/frontend/src/app/main/data/workspace/variants.cljs @@ -613,7 +613,7 @@ vec)) (defn combine-as-variants - [ids {:keys [page-id trigger]}] + [ids {:keys [page-id trigger variant-id]}] (ptk/reify ::combine-as-variants ptk/WatchEvent (watch [_ state stream] @@ -647,7 +647,7 @@ :shapes count inc) - variant-id (uuid/next) + variant-id (or variant-id (uuid/next)) undo-id (js/Symbol)] (rx/concat diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index 14effac100..16706eacbd 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -14,6 +14,7 @@ [app.common.geom.point :as gpt] [app.common.schema :as sm] [app.common.types.color :as ctc] + [app.common.types.component :as ctk] [app.common.types.shape :as cts] [app.common.types.text :as txt] [app.common.uuid :as uuid] @@ -26,6 +27,7 @@ [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.media :as dwm] [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.variants :as dwv] [app.main.data.workspace.wasm-text :as dwwt] [app.main.features :as features] [app.main.fonts :refer [fetch-font-css]] @@ -608,4 +610,35 @@ :else (let [ids (into #{} (map #(obj/get % "$id")) shapes)] - (st/emit! (dw/convert-selected-to-path ids))))))) + (st/emit! (dw/convert-selected-to-path ids))))) + + :createVariantFromComponents + (fn [shapes] + (cond + (or (not (seq shapes)) + (not (every? u/is-main-component-proxy? shapes))) + (u/display-not-valid :shapes shapes) + + :else + (let [file-id (obj/get (first shapes) "$file") + page-id (obj/get (first shapes) "$page") + ids (->> shapes + (map #(obj/get % "$id")) + (into #{})) + + ;; Check that every component is: + ;; - in the same page + ;; - not already a variant + valid? + (every? + (fn [id] + (let [shape (u/locate-shape file-id page-id id) + component (u/locate-library-component file-id (:component-id shape))] + (not (ctk/is-variant? component)))) + ids)] + (when valid? + (let [variant-id (uuid/next)] + (st/emit! (dwv/combine-as-variants + ids + {:trigger "plugin:combine-as-variants" :variant-id variant-id})) + (library/variant-proxy plugin-id file-id variant-id)))))))) diff --git a/frontend/src/app/plugins/shape.cljs b/frontend/src/app/plugins/shape.cljs index 5a0c8f6634..eba2c2cfcf 100644 --- a/frontend/src/app/plugins/shape.cljs +++ b/frontend/src/app/plugins/shape.cljs @@ -1351,16 +1351,22 @@ :combineAsVariants (fn [ids] - (if (or (not (seq ids)) (not (every? uuid/parse* ids))) + (cond + (or (not (seq ids)) (not (every? uuid/parse* ids))) (u/display-not-valid :ids ids) + + :else (let [shape (u/locate-shape file-id page-id id) component (u/locate-library-component file-id (:component-id shape)) ids (->> ids (map uuid/uuid) (into #{id}))] - (when (and component (not (ctk/is-variant? component))) - (st/emit! - (dwv/combine-as-variants ids {:trigger "plugin:combine-as-variants"}))))))) + (when (and component (not (ctk/is-variant? component))) + (let [variant-id (uuid/next)] + (st/emit! (dwv/combine-as-variants + ids + {:trigger "plugin:combine-as-variants" :variant-id variant-id})) + (variant-proxy plugin-id file-id variant-id))))))) (cond-> (or (cfh/frame-shape? data) (cfh/group-shape? data) (cfh/svg-raw-shape? data) (cfh/bool-shape? data)) (crc/add-properties! diff --git a/frontend/src/app/plugins/utils.cljs b/frontend/src/app/plugins/utils.cljs index dfb8242a12..0e94c1543d 100644 --- a/frontend/src/app/plugins/utils.cljs +++ b/frontend/src/app/plugins/utils.cljs @@ -11,6 +11,7 @@ [app.common.data.macros :as dm] [app.common.json :as json] [app.common.schema :as sm] + [app.common.types.component :as ctk] [app.common.types.container :as ctn] [app.common.types.file :as ctf] [app.common.types.tokens-lib :as ctob] @@ -258,4 +259,9 @@ (if-let [explain (-> cause ex-data ::sm/explain)] (println (sm/humanize-explain explain)) (js/console.log (ex-data cause))) - (js/console.log (.-stack cause))) \ No newline at end of file + (js/console.log (.-stack cause))) + +(defn is-main-component-proxy? + [p] + (when-let [shape (proxy->shape p)] + (ctk/main-instance? shape))) diff --git a/plugins/libs/plugin-types/index.d.ts b/plugins/libs/plugin-types/index.d.ts index 543182ac11..93a01f733d 100644 --- a/plugins/libs/plugin-types/index.d.ts +++ b/plugins/libs/plugin-types/index.d.ts @@ -1297,6 +1297,15 @@ export interface Context { * @param shapes to flatten */ flatten(shapes: Shape[]): Path[]; + + /** + * Combine several standard Components into a VariantComponent. Similar to doing it + * with the contextual menu on the Penpot interface. + * All the shapes passed as arguments should be main instances. + * @param shapes A list of main instances of the components to combine. + * @return The variant container created + */ + createVariantFromComponents(shapes: Board[]): VariantContainer; } /** @@ -3818,8 +3827,9 @@ export interface ShapeBase extends PluginData { * on the Penpot interface. * The current shape must be a component main instance. * @param ids A list of ids of the main instances of the components to combine with this one. + * @return The variant container created */ - combineAsVariants(ids: string[]): void; + combineAsVariants(ids: string[]): VariantContainer; /** * @return Returns true when the current shape is the head of a components tree nested structure, diff --git a/plugins/libs/plugins-runtime/src/lib/api/index.ts b/plugins/libs/plugins-runtime/src/lib/api/index.ts index 6d452d2ddc..01dd9008de 100644 --- a/plugins/libs/plugins-runtime/src/lib/api/index.ts +++ b/plugins/libs/plugins-runtime/src/lib/api/index.ts @@ -358,6 +358,11 @@ export function createApi( checkPermission('content:write'); return plugin.context.flatten(shapes); }, + + createVariantFromComponents(shapes: Board[]): VariantContainer { + checkPermission('content:write'); + return plugin.context.createVariantFromComponents(shapes); + }, }; return {