mirror of
https://github.com/penpot/penpot.git
synced 2025-01-04 13:50:12 -05:00
✨ Add facility for create anonymous objects
Speciailly designed to be work in plugins where code is submited to hard deep freeze on the sandboxing process.
This commit is contained in:
parent
59e5656bd7
commit
e16ec9c719
6 changed files with 348 additions and 151 deletions
|
@ -43,7 +43,9 @@
|
|||
{:extra-paths ["dev"]
|
||||
:extra-deps
|
||||
{thheller/shadow-cljs {:mvn/version "2.28.18"}
|
||||
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
|
||||
org.clojure/tools.namespace {:mvn/version "RELEASE"}
|
||||
criterium/criterium {:mvn/version "RELEASE"}
|
||||
cider/cider-nrepl {:mvn/version "0.48.0"}}}
|
||||
|
||||
:shadow-cljs
|
||||
|
|
34
frontend/dev/user.clj
Normal file
34
frontend/dev/user.clj
Normal file
|
@ -0,0 +1,34 @@
|
|||
;; 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 user
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.pprint :as pp]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.tools.namespace.repl :as repl]
|
||||
[clojure.pprint :refer [pprint print-table]]
|
||||
[clojure.repl :refer :all]
|
||||
[clojure.walk :refer [macroexpand-all]]
|
||||
[criterium.core :as crit]))
|
||||
|
||||
;; --- Benchmarking Tools
|
||||
|
||||
(defmacro run-quick-bench
|
||||
[& exprs]
|
||||
`(crit/with-progress-reporting (crit/quick-bench (do ~@exprs) :verbose)))
|
||||
|
||||
(defmacro run-quick-bench'
|
||||
[& exprs]
|
||||
`(crit/quick-bench (do ~@exprs)))
|
||||
|
||||
(defmacro run-bench
|
||||
[& exprs]
|
||||
`(crit/with-progress-reporting (crit/bench (do ~@exprs) :verbose)))
|
||||
|
||||
(defmacro run-bench'
|
||||
[& exprs]
|
||||
`(crit/bench (do ~@exprs)))
|
6
frontend/scripts/repl
Executable file
6
frontend/scripts/repl
Executable file
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
export OPTIONS="-A:dev -J-XX:-OmitStackTraceInFastThrow";
|
||||
|
||||
set -ex
|
||||
exec clojure $OPTIONS -M -m rebel-readline.main
|
|
@ -28,92 +28,88 @@
|
|||
[app.util.time :as dt]
|
||||
[beicon.v2.core :as rx]))
|
||||
|
||||
(declare file-version-proxy)
|
||||
|
||||
(deftype FileVersionProxy [$plugin $file $version $data]
|
||||
Object
|
||||
(restore
|
||||
[_]
|
||||
(cond
|
||||
(not (r/check-permission $plugin "content:write"))
|
||||
(u/display-not-valid :restore "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(let [project-id (:current-project-id @st/state)]
|
||||
(st/emit! (dwv/restore-version project-id $file $version :plugin)))))
|
||||
|
||||
(remove
|
||||
[_]
|
||||
(js/Promise.
|
||||
(fn [resolve reject]
|
||||
(cond
|
||||
(not (r/check-permission $plugin "content:write"))
|
||||
(u/reject-not-valid reject :remove "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(->> (rp/cmd! :delete-file-snapshot {:id $version})
|
||||
(rx/subs! #(resolve) reject))))))
|
||||
|
||||
(pin
|
||||
[_]
|
||||
(js/Promise.
|
||||
(fn [resolve reject]
|
||||
(cond
|
||||
(not (r/check-permission $plugin "content:write"))
|
||||
(u/reject-not-valid reject :pin "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
(not= "system" (:created-by $data))
|
||||
(u/reject-not-valid reject :pin "Only auto-saved versions can be pinned")
|
||||
|
||||
:else
|
||||
(let [params {:id $version
|
||||
:label (dt/format (:created-at $data) :date-full)}]
|
||||
(->> (rx/zip (rp/cmd! :get-team-users {:file-id $file})
|
||||
(rp/cmd! :update-file-snapshot params))
|
||||
(rx/subs! (fn [[users data]]
|
||||
(let [users (d/index-by :id users)]
|
||||
(resolve (file-version-proxy $plugin $file users data))))
|
||||
reject))))))))
|
||||
|
||||
(defn file-version-proxy
|
||||
[plugin-id file-id users data]
|
||||
(let [data (atom data)]
|
||||
(crc/add-properties!
|
||||
(FileVersionProxy. plugin-id file-id (:id @data) data)
|
||||
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
|
||||
{:name "$file" :enumerable false :get (constantly file-id)}
|
||||
{:name "$version" :enumerable false :get (constantly (:id @data))}
|
||||
{:name "$data" :enumerable false :get (constantly @data)}
|
||||
(obj/reify {:name "FileVersionProxy"}
|
||||
:$plugin {:get (fn [] plugin-id) :enumerable false}
|
||||
:$file {:get (fn [] file-id) :enumerable false}
|
||||
:$version {:get (fn [] (:id @data)) :enumerable false}
|
||||
:$data {:get (fn [] @data) :enumerable false}
|
||||
|
||||
{:name "label"
|
||||
:get (fn [_] (:label @data))
|
||||
:set
|
||||
(fn [_ value]
|
||||
:label
|
||||
{:get #(:label @data)
|
||||
:set
|
||||
(fn [value]
|
||||
(cond
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/display-not-valid :label "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
(or (not (string? value)) (empty? value))
|
||||
(u/display-not-valid :label value)
|
||||
|
||||
:else
|
||||
(do (swap! data assoc :label value :created-by "user")
|
||||
(->> (rp/cmd! :update-file-snapshot {:id (:id @data) :label value})
|
||||
(rx/take 1)
|
||||
(rx/subs! identity)))))}
|
||||
|
||||
:createdBy
|
||||
{:get
|
||||
(fn []
|
||||
(when-let [user-data (get users (:profile-id @data))]
|
||||
(user/user-proxy plugin-id user-data)))}
|
||||
|
||||
:createdAt
|
||||
{:get #(.toJSDate ^js (:created-at @data))}
|
||||
|
||||
:isAutosave
|
||||
{:get #(= "system" (:created-by @data))}
|
||||
|
||||
:restore
|
||||
(fn []
|
||||
(cond
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/display-not-valid :label "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
(or (not (string? value)) (empty? value))
|
||||
(u/display-not-valid :label value)
|
||||
(u/display-not-valid :restore "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
:else
|
||||
(do (swap! data assoc :label value :created-by "user")
|
||||
(->> (rp/cmd! :update-file-snapshot {:id (:id @data) :label value})
|
||||
(rx/take 1)
|
||||
(rx/subs! identity)))))}
|
||||
(let [project-id (:current-project-id @st/state)
|
||||
version-id (get @data :id)]
|
||||
(st/emit! (dwv/restore-version project-id file-id version-id :plugin)))))
|
||||
|
||||
{:name "createdBy"
|
||||
:get (fn [_]
|
||||
(when-let [user-data (get users (:profile-id @data))]
|
||||
(user/user-proxy plugin-id user-data)))}
|
||||
:remove
|
||||
(fn []
|
||||
(js/Promise.
|
||||
(fn [resolve reject]
|
||||
(cond
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/reject-not-valid reject :remove "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
{:name "createdAt"
|
||||
:get (fn [_]
|
||||
(.toJSDate ^js (:created-at @data)))}
|
||||
:else
|
||||
(let [version-id (:id @data)]
|
||||
(->> (rp/cmd! :delete-file-snapshot {:id version-id})
|
||||
(rx/subs! #(resolve) reject)))))))
|
||||
|
||||
{:name "isAutosave"
|
||||
:get (fn [_]
|
||||
(= "system" (:created-by @data)))})))
|
||||
:pin
|
||||
(fn []
|
||||
(js/Promise.
|
||||
(fn [resolve reject]
|
||||
(cond
|
||||
(not (r/check-permission plugin-id "content:write"))
|
||||
(u/reject-not-valid reject :pin "Plugin doesn't have 'content:write' permission")
|
||||
|
||||
(not= "system" (:created-by @data))
|
||||
(u/reject-not-valid reject :pin "Only auto-saved versions can be pinned")
|
||||
|
||||
:else
|
||||
(let [params {:id (:id @data)
|
||||
:label (dt/format (:created-at @data) :date-full)}]
|
||||
(->> (rx/zip (rp/cmd! :get-team-users {:file-id file-id})
|
||||
(rp/cmd! :update-file-snapshot params))
|
||||
(rx/subs! (fn [[users data]]
|
||||
(let [users (d/index-by :id users)]
|
||||
(resolve (file-version-proxy plugin-id file-id users @data))))
|
||||
reject))))))))))
|
||||
|
||||
(deftype FileProxy [$plugin $id]
|
||||
Object
|
||||
|
|
235
frontend/src/app/util/object.cljc
Normal file
235
frontend/src/app/util/object.cljc
Normal file
|
@ -0,0 +1,235 @@
|
|||
;; 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.util.object
|
||||
"A collection of helpers for work with javascript objects."
|
||||
(:refer-clojure :exclude [set! new get merge clone contains? array? into-array reify])
|
||||
#?(:cljs (:require-macros [app.util.object]))
|
||||
(:require
|
||||
[clojure.core :as c]))
|
||||
|
||||
#?(:cljs
|
||||
(defn array?
|
||||
[o]
|
||||
(.isArray js/Array o)))
|
||||
|
||||
#?(:cljs
|
||||
(defn into-array
|
||||
[o]
|
||||
(js/Array.from o)))
|
||||
|
||||
#?(:cljs
|
||||
(defn create [] #js {}))
|
||||
|
||||
#?(:cljs
|
||||
(defn get
|
||||
([obj k]
|
||||
(when (some? obj)
|
||||
(unchecked-get obj k)))
|
||||
([obj k default]
|
||||
(let [result (get obj k)]
|
||||
(if (undefined? result) default result)))))
|
||||
|
||||
#?(:cljs
|
||||
(defn contains?
|
||||
[obj k]
|
||||
(when (some? obj)
|
||||
(js/Object.hasOwn obj k))))
|
||||
|
||||
#?(:cljs
|
||||
(defn clone
|
||||
[a]
|
||||
(js/Object.assign #js {} a)))
|
||||
|
||||
#?(:cljs
|
||||
(defn merge!
|
||||
([a b]
|
||||
(js/Object.assign a b))
|
||||
([a b & more]
|
||||
(reduce merge! (merge! a b) more))))
|
||||
|
||||
#?(:cljs
|
||||
(defn merge
|
||||
([a b]
|
||||
(js/Object.assign #js {} a b))
|
||||
([a b & more]
|
||||
(reduce merge! (merge a b) more))))
|
||||
|
||||
#?(:cljs
|
||||
(defn set!
|
||||
[obj key value]
|
||||
(unchecked-set obj key value)
|
||||
obj))
|
||||
|
||||
#?(:cljs
|
||||
(defn unset!
|
||||
[obj key]
|
||||
(js-delete obj key)
|
||||
obj))
|
||||
|
||||
#?(:cljs
|
||||
(def ^:private not-found-sym
|
||||
(js/Symbol "not-found")))
|
||||
|
||||
#?(:cljs
|
||||
(defn update!
|
||||
[obj key f & args]
|
||||
(let [found (c/get obj key not-found-sym)]
|
||||
(when-not ^boolean (identical? found not-found-sym)
|
||||
(unchecked-set obj key (apply f found args)))
|
||||
obj)))
|
||||
|
||||
#?(:cljs
|
||||
(defn ^boolean in?
|
||||
[obj prop]
|
||||
(js* "~{} in ~{}" prop obj)))
|
||||
|
||||
#?(:cljs
|
||||
(defn without-empty
|
||||
[^js obj]
|
||||
(when (some? obj)
|
||||
(js* "Object.entries(~{}).reduce((a, [k,v]) => (v == null ? a : (a[k]=v, a)), {}) " obj))))
|
||||
|
||||
(defmacro add-properties!
|
||||
"Adds properties to an object using `.defineProperty`"
|
||||
[rsym & properties]
|
||||
(let [rsym (with-meta rsym {:tag 'js})
|
||||
getf-sym (with-meta (gensym (str rsym "-get-fn-")) {:tag 'js})
|
||||
setf-sym (with-meta (gensym (str rsym "-set-fn-")) {:tag 'js})
|
||||
this-sym (with-meta (gensym (str rsym "-this-")) {:tag 'js})
|
||||
target-sym (with-meta (gensym (str rsym "-target-")) {:tag 'js})]
|
||||
`(let [~target-sym ~rsym]
|
||||
;; Creates the `.defineProperty` per property
|
||||
~@(for [params properties
|
||||
:let [pname (c/get params :name)
|
||||
get-expr (c/get params :get)
|
||||
set-expr (c/get params :set)
|
||||
this? (c/get params :this true)
|
||||
enum? (c/get params :enumerable true)
|
||||
conf? (c/get params :configurable)
|
||||
writ? (c/get params :writable)]]
|
||||
`(let [~@(concat
|
||||
(when get-expr
|
||||
[getf-sym get-expr])
|
||||
(when set-expr
|
||||
[setf-sym set-expr]))]
|
||||
(.defineProperty
|
||||
js/Object
|
||||
~target-sym
|
||||
~pname
|
||||
(cljs.core/js-obj
|
||||
~@(concat
|
||||
["enumerable" (boolean enum?)]
|
||||
|
||||
(when conf?
|
||||
["configurable" true])
|
||||
|
||||
(when (some? writ?)
|
||||
["writable" true])
|
||||
|
||||
(when get-expr
|
||||
(if this?
|
||||
["get" `(fn [] (cljs.core/this-as ~this-sym (~getf-sym ~this-sym)))]
|
||||
["get" getf-sym]))
|
||||
|
||||
(when set-expr
|
||||
(if this?
|
||||
["set" `(fn [v#] (cljs.core/this-as ~this-sym (~setf-sym ~this-sym v#)))]
|
||||
["set" setf-sym])))))))
|
||||
|
||||
;; Returns the object
|
||||
~target-sym)))
|
||||
|
||||
(defn- collect-properties
|
||||
[params]
|
||||
(let [[tmeta params] (if (map? (first params))
|
||||
[(first params) (rest params)]
|
||||
[{} params])]
|
||||
(loop [params (seq params)
|
||||
props []
|
||||
defs {}
|
||||
curr :start
|
||||
ckey nil]
|
||||
(cond
|
||||
(= curr :start)
|
||||
(let [candidate (first params)]
|
||||
(cond
|
||||
(keyword? candidate)
|
||||
(recur (rest params) props defs :property candidate)
|
||||
|
||||
(nil? candidate)
|
||||
(recur (rest params) props defs :end nil)
|
||||
|
||||
:else
|
||||
(recur (rest params) props defs :definition candidate)))
|
||||
|
||||
(= :end curr)
|
||||
[tmeta props defs]
|
||||
|
||||
(= :property curr)
|
||||
(let [definition (first params)]
|
||||
(if (some? definition)
|
||||
(let [definition (if (map? definition)
|
||||
(assoc definition :name (name ckey) :this false)
|
||||
(-> {:enumerable false}
|
||||
(c/merge (meta definition))
|
||||
(assoc :name (name ckey))
|
||||
(assoc :this false)
|
||||
(assoc :get `(fn [] ~definition))))]
|
||||
(recur (rest params)
|
||||
(conj props definition)
|
||||
defs
|
||||
:start
|
||||
nil))
|
||||
(let [hint (str "expected property definition for: " curr)]
|
||||
(throw (ex-info hint {:key curr})))))
|
||||
|
||||
(= :definition curr)
|
||||
(let [[params props defs curr ckey]
|
||||
(loop [params params
|
||||
defs (update defs ckey #(or % []))]
|
||||
(let [candidate (first params)
|
||||
params (rest params)]
|
||||
(cond
|
||||
(nil? candidate)
|
||||
[params props defs :end]
|
||||
|
||||
(keyword? candidate)
|
||||
[params props defs :property candidate]
|
||||
|
||||
(symbol? candidate)
|
||||
[params props defs :definition candidate]
|
||||
|
||||
:else
|
||||
(recur params (update defs ckey conj candidate)))))]
|
||||
(recur params props defs curr ckey))
|
||||
|
||||
:else
|
||||
(throw (ex-info "invalid params" {}))))))
|
||||
|
||||
(defmacro reify
|
||||
"A domain specific variation of reify that creates anonymous objects
|
||||
on demand with the ability to assign protocol implementations and
|
||||
custom properties"
|
||||
[& params]
|
||||
(let [[tmeta properties definitions] (collect-properties params)
|
||||
obj-sym (gensym "obj-")]
|
||||
`(let [~obj-sym (cljs.core/js-obj)]
|
||||
(add-properties! ~obj-sym
|
||||
~@(when-let [tname (:name tmeta)]
|
||||
[`{:name ~'js/Symbol.toStringTag
|
||||
:this false
|
||||
:enumerable false
|
||||
:get (fn [] ~tname)}])
|
||||
~@properties)
|
||||
(let [~obj-sym ~(if-let [definitions (seq definitions)]
|
||||
`(cljs.core/specify! ~obj-sym
|
||||
~@(mapcat (fn [[k v]] (cons k v)) definitions))
|
||||
obj-sym)]
|
||||
|
||||
(cljs.core/specify! ~obj-sym
|
||||
cljs.core/IMeta
|
||||
(~'-meta [_#] ~tmeta))))))
|
|
@ -1,76 +0,0 @@
|
|||
;; 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.util.object
|
||||
"A collection of helpers for work with javascript objects."
|
||||
(:refer-clojure :exclude [set! new get merge clone contains? array? into-array]))
|
||||
|
||||
(defn array?
|
||||
[o]
|
||||
(.isArray js/Array o))
|
||||
|
||||
(defn into-array
|
||||
[o]
|
||||
(js/Array.from o))
|
||||
|
||||
(defn create [] #js {})
|
||||
|
||||
(defn get
|
||||
([obj k]
|
||||
(when (some? obj)
|
||||
(unchecked-get obj k)))
|
||||
([obj k default]
|
||||
(let [result (get obj k)]
|
||||
(if (undefined? result) default result))))
|
||||
|
||||
(defn contains?
|
||||
[obj k]
|
||||
(when (some? obj)
|
||||
(js/Object.hasOwn obj k)))
|
||||
|
||||
(defn clone
|
||||
[a]
|
||||
(js/Object.assign #js {} a))
|
||||
|
||||
(defn merge!
|
||||
([a b]
|
||||
(js/Object.assign a b))
|
||||
([a b & more]
|
||||
(reduce merge! (merge! a b) more)))
|
||||
|
||||
(defn merge
|
||||
([a b]
|
||||
(js/Object.assign #js {} a b))
|
||||
([a b & more]
|
||||
(reduce merge! (merge a b) more)))
|
||||
|
||||
(defn set!
|
||||
[obj key value]
|
||||
(unchecked-set obj key value)
|
||||
obj)
|
||||
|
||||
(defn unset!
|
||||
[obj key]
|
||||
(js-delete obj key)
|
||||
obj)
|
||||
|
||||
(def ^:private not-found-sym (js/Symbol "not-found"))
|
||||
|
||||
(defn update!
|
||||
[obj key f & args]
|
||||
(let [found (get obj key not-found-sym)]
|
||||
(when-not ^boolean (identical? found not-found-sym)
|
||||
(unchecked-set obj key (apply f found args)))
|
||||
obj))
|
||||
|
||||
(defn ^boolean in?
|
||||
[obj prop]
|
||||
(js* "~{} in ~{}" prop obj))
|
||||
|
||||
(defn without-empty
|
||||
[^js obj]
|
||||
(when (some? obj)
|
||||
(js* "Object.entries(~{}).reduce((a, [k,v]) => (v == null ? a : (a[k]=v, a)), {}) " obj)))
|
Loading…
Reference in a new issue