mirror of
https://github.com/penpot/penpot.git
synced 2025-06-10 17:31:39 +02:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
f5c913d26e
14 changed files with 349 additions and 210 deletions
|
@ -228,19 +228,9 @@
|
||||||
[:objects-storage-s3-endpoint {:optional true} ::sm/uri]
|
[:objects-storage-s3-endpoint {:optional true} ::sm/uri]
|
||||||
[:objects-storage-s3-io-threads {:optional true} ::sm/int]]))
|
[:objects-storage-s3-io-threads {:optional true} ::sm/int]]))
|
||||||
|
|
||||||
(def default-flags
|
|
||||||
[:enable-backend-api-doc
|
|
||||||
:enable-backend-openapi-doc
|
|
||||||
:enable-backend-worker
|
|
||||||
:enable-secure-session-cookies
|
|
||||||
:enable-email-verification
|
|
||||||
:enable-v2-migration])
|
|
||||||
|
|
||||||
(defn- parse-flags
|
(defn- parse-flags
|
||||||
[config]
|
[config]
|
||||||
(flags/parse flags/default
|
(flags/parse flags/default (:flags config)))
|
||||||
default-flags
|
|
||||||
(:flags config)))
|
|
||||||
|
|
||||||
(defn read-env
|
(defn read-env
|
||||||
[prefix]
|
[prefix]
|
||||||
|
|
|
@ -143,7 +143,7 @@
|
||||||
(keep flag->feature))
|
(keep flag->feature))
|
||||||
|
|
||||||
(defn get-enabled-features
|
(defn get-enabled-features
|
||||||
"Get the globally enabled fratures set."
|
"Get the globally enabled features set."
|
||||||
[flags]
|
[flags]
|
||||||
(into default-features xf-flag-to-feature flags))
|
(into default-features xf-flag-to-feature flags))
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,145 @@
|
||||||
(ns app.common.flags
|
(ns app.common.flags
|
||||||
"Flags parsing algorithm."
|
"Flags parsing algorithm."
|
||||||
(:require
|
(:require
|
||||||
|
[clojure.set :as set]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
|
|
||||||
|
(def login
|
||||||
|
"Flags related to login features"
|
||||||
|
#{;; Allows registration with login / password
|
||||||
|
;; if disabled, it's still possible to register/login with providers
|
||||||
|
:registration
|
||||||
|
;; Redundant flag. TODO: remove it
|
||||||
|
:login
|
||||||
|
;; enables the section of Access Tokens on profile.
|
||||||
|
:access-tokens
|
||||||
|
;; Uses email and password as credentials.
|
||||||
|
:login-with-password
|
||||||
|
;; Uses Github authentication as credentials.
|
||||||
|
:login-with-github
|
||||||
|
;; Uses GitLab authentication as credentials.
|
||||||
|
:login-with-gitlab
|
||||||
|
;; Uses Google/Gmail authentication as credentials.
|
||||||
|
:login-with-google
|
||||||
|
;; Uses LDAP authentication as credentials.
|
||||||
|
:login-with-ldap
|
||||||
|
;; Uses any generic authentication provider that implements OIDC protocol as credentials.
|
||||||
|
:login-with-oidc
|
||||||
|
;; Allows registration with Open ID
|
||||||
|
:oidc-registration
|
||||||
|
;; This logs to console the invitation tokens. It's useful in case the SMTP is not configured.
|
||||||
|
:log-invitation-tokens})
|
||||||
|
|
||||||
|
(def email
|
||||||
|
"Flags related to email features"
|
||||||
|
#{;; Uses the domains in whitelist as the only allowed domains to register in the application.
|
||||||
|
;; Used with PENPOT_REGISTRATION_DOMAIN_WHITELIST
|
||||||
|
:email-whitelist
|
||||||
|
;; Prevents the domains in blacklist to register in the application.
|
||||||
|
;; Used with PENPOT_REGISTRATION_DOMAIN_BLACKLIST
|
||||||
|
:email-blacklist
|
||||||
|
;; Skips the email verification process. Not recommended for production environments.
|
||||||
|
:email-verification
|
||||||
|
;; Only used if SMTP is disabled. Logs the emails into the console.
|
||||||
|
:log-emails
|
||||||
|
;; Enable it to configure email settings.
|
||||||
|
:smtp
|
||||||
|
;; Enables the debug mode of the SMTP library.
|
||||||
|
:smtp-debug})
|
||||||
|
|
||||||
|
(def varia
|
||||||
|
"Rest of the flags"
|
||||||
|
#{:audit-log
|
||||||
|
:audit-log-archive
|
||||||
|
:audit-log-gc
|
||||||
|
:auto-file-snapshot
|
||||||
|
;; enables the `/api/doc` endpoint that lists all the rpc methods available.
|
||||||
|
:backend-api-doc
|
||||||
|
;; TODO: remove it and use only `backend-api-doc` flag
|
||||||
|
:backend-openapi-doc
|
||||||
|
;; Disable it to start the RPC without the worker.
|
||||||
|
:backend-worker
|
||||||
|
;; Only for development
|
||||||
|
:component-thumbnails
|
||||||
|
;; enables the default cors configuration that allows all domains (currently this configuration is only used for development).
|
||||||
|
:cors
|
||||||
|
;; Enables the templates dialog on Penpot dashboard.
|
||||||
|
:dashboard-templates-section
|
||||||
|
;; disabled by default. When enabled, Penpot create demo users with a 7 days expiration.
|
||||||
|
:demo-users
|
||||||
|
;; disabled by default. When enabled, it displays a warning that this is a test instance and data will be deleted periodically.
|
||||||
|
:demo-warning
|
||||||
|
;; Activates the schema validation during update file.
|
||||||
|
:file-schema-validation
|
||||||
|
;; Reports the schema validation errors internally.
|
||||||
|
:soft-file-schema-validation
|
||||||
|
;; Activates the referential integrity validation during update file; related to components-v2.
|
||||||
|
:file-validation
|
||||||
|
;; Reports the referential integrity validation errors internally.
|
||||||
|
:soft-file-validation
|
||||||
|
;; TODO: deprecate this flag and consolidate the code
|
||||||
|
:frontend-svgo
|
||||||
|
;; TODO: deprecate this flag and consolidate the code
|
||||||
|
:exporter-svgo
|
||||||
|
;; TODO: deprecate this flag and consolidate the code
|
||||||
|
:backend-svgo
|
||||||
|
;; If enabled, it makes the Google Fonts available.
|
||||||
|
:google-fonts-provider
|
||||||
|
;; Only for development.
|
||||||
|
:nrepl-server
|
||||||
|
;; Interactive repl. Only for development.
|
||||||
|
:urepl-server
|
||||||
|
;; Programatic access to the runtime, used in administrative tasks.
|
||||||
|
;; It's mandatory to enable it to use the `manage.py` script.
|
||||||
|
:prepl-server
|
||||||
|
;; Shows the onboarding modals right after registration.
|
||||||
|
:onboarding
|
||||||
|
:quotes
|
||||||
|
:soft-quotes
|
||||||
|
;; Concurrency limit.
|
||||||
|
:rpc-climit
|
||||||
|
;; Rate limit.
|
||||||
|
:rpc-rlimit
|
||||||
|
;; Soft rate limit.
|
||||||
|
:soft-rpc-rlimit
|
||||||
|
;; Disable it if you want to serve Penpot under a different domain than `http://localhost` without HTTPS.
|
||||||
|
:secure-session-cookies
|
||||||
|
;; If `cors` enabled, this is ignored.
|
||||||
|
:strict-session-cookies
|
||||||
|
:telemetry
|
||||||
|
:terms-and-privacy-checkbox
|
||||||
|
;; Only for developtment.
|
||||||
|
:tiered-file-data-storage
|
||||||
|
:transit-readable-response
|
||||||
|
:user-feedback
|
||||||
|
;; TODO: remove this flag.
|
||||||
|
:v2-migration
|
||||||
|
:webhooks
|
||||||
|
;; TODO: deprecate this flag and consolidate the code
|
||||||
|
:export-file-v3
|
||||||
|
:render-wasm-dpr
|
||||||
|
:hide-release-modal})
|
||||||
|
|
||||||
|
(def all-flags
|
||||||
|
(set/union email login varia))
|
||||||
|
|
||||||
(def default
|
(def default
|
||||||
"A common flags that affects both: backend and frontend."
|
"Flags with default configuration"
|
||||||
[:enable-registration
|
[:enable-registration
|
||||||
|
:enable-login-with-password
|
||||||
:enable-export-file-v3
|
:enable-export-file-v3
|
||||||
:enable-login-with-password])
|
:enable-frontend-svgo
|
||||||
|
:enable-exporter-svgo
|
||||||
|
:enable-backend-svgo
|
||||||
|
:enable-backend-api-doc
|
||||||
|
:enable-backend-openapi-doc
|
||||||
|
:enable-backend-worker
|
||||||
|
:enable-secure-session-cookies
|
||||||
|
:enable-email-verification
|
||||||
|
:enable-onboarding
|
||||||
|
:enable-dashboard-templates-section
|
||||||
|
:enable-google-fonts-provider
|
||||||
|
:enable-component-thumbnails])
|
||||||
|
|
||||||
(defn parse
|
(defn parse
|
||||||
[& flags]
|
[& flags]
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
;; Copyright (c) KALEIDOS INC
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
(ns app.common.types.page
|
(ns app.common.types.page
|
||||||
|
(:refer-clojure :exclude [empty?])
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.geom.point :as-alias gpt]
|
[app.common.geom.point :as-alias gpt]
|
||||||
|
@ -98,3 +99,8 @@
|
||||||
(defn get-frame-flow
|
(defn get-frame-flow
|
||||||
[flows frame-id]
|
[flows frame-id]
|
||||||
(d/seek #(= (:starting-frame %) frame-id) (vals flows)))
|
(d/seek #(= (:starting-frame %) frame-id) (vals flows)))
|
||||||
|
|
||||||
|
(defn is-empty?
|
||||||
|
"Check if page is empty or contains shapes"
|
||||||
|
[page]
|
||||||
|
(= 1 (count (:objects page))))
|
||||||
|
|
|
@ -63,17 +63,11 @@
|
||||||
:browser
|
:browser
|
||||||
:webworker))
|
:webworker))
|
||||||
|
|
||||||
(def default-flags
|
|
||||||
[:enable-onboarding
|
|
||||||
:enable-dashboard-templates-section
|
|
||||||
:enable-google-fonts-provider
|
|
||||||
:enable-component-thumbnails])
|
|
||||||
|
|
||||||
(defn- parse-flags
|
(defn- parse-flags
|
||||||
[global]
|
[global]
|
||||||
(let [flags (obj/get global "penpotFlags" "")
|
(let [flags (obj/get global "penpotFlags" "")
|
||||||
flags (sequence (map keyword) (str/words flags))]
|
flags (sequence (map keyword) (str/words flags))]
|
||||||
(flags/parse flags/default default-flags flags)))
|
(flags/parse flags/default flags)))
|
||||||
|
|
||||||
(defn- parse-version
|
(defn- parse-version
|
||||||
[global]
|
[global]
|
||||||
|
|
|
@ -469,10 +469,11 @@
|
||||||
|
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(-> state
|
(let [file (dissoc file :data)]
|
||||||
(assoc-in [:files id] file)
|
(-> state
|
||||||
(assoc-in [:recent-files id] file)
|
(assoc-in [:files id] file)
|
||||||
(update-in [:projects project-id :count] inc)))))
|
(assoc-in [:recent-files id] file)
|
||||||
|
(update-in [:projects project-id :count] inc))))))
|
||||||
|
|
||||||
(defn create-file
|
(defn create-file
|
||||||
[{:keys [project-id name] :as params}]
|
[{:keys [project-id name] :as params}]
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
[app.common.types.components-list :as ctkl]
|
[app.common.types.components-list :as ctkl]
|
||||||
[app.common.types.container :as ctn]
|
[app.common.types.container :as ctn]
|
||||||
[app.common.types.file :as ctf]
|
[app.common.types.file :as ctf]
|
||||||
|
[app.common.types.page :as ctp]
|
||||||
[app.common.types.shape :as cts]
|
[app.common.types.shape :as cts]
|
||||||
[app.common.types.shape-tree :as ctst]
|
[app.common.types.shape-tree :as ctst]
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
|
@ -108,9 +109,6 @@
|
||||||
(declare ^:private workspace-initialized)
|
(declare ^:private workspace-initialized)
|
||||||
(declare ^:private fetch-libraries)
|
(declare ^:private fetch-libraries)
|
||||||
(declare ^:private libraries-fetched)
|
(declare ^:private libraries-fetched)
|
||||||
(declare ^:private preload-data-uris)
|
|
||||||
|
|
||||||
;; (declare go-to-layout)
|
|
||||||
|
|
||||||
;; --- Initialize Workspace
|
;; --- Initialize Workspace
|
||||||
|
|
||||||
|
@ -273,6 +271,15 @@
|
||||||
(rx/of (dws/select-shapes frames-id)
|
(rx/of (dws/select-shapes frames-id)
|
||||||
dwz/zoom-to-selected-shape)))))
|
dwz/zoom-to-selected-shape)))))
|
||||||
|
|
||||||
|
(defn- select-frame-tool
|
||||||
|
[file-id page-id]
|
||||||
|
(ptk/reify ::select-frame-tool
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [page (dsh/lookup-page state file-id page-id)]
|
||||||
|
(when (ctp/is-empty? page)
|
||||||
|
(rx/of (dwd/select-for-drawing :frame)))))))
|
||||||
|
|
||||||
(defn- fetch-bundle
|
(defn- fetch-bundle
|
||||||
"Multi-stage file bundle fetch coordinator"
|
"Multi-stage file bundle fetch coordinator"
|
||||||
[file-id]
|
[file-id]
|
||||||
|
@ -314,13 +321,10 @@
|
||||||
(defn initialize-workspace
|
(defn initialize-workspace
|
||||||
[file-id]
|
[file-id]
|
||||||
(assert (uuid? file-id) "expected valud uuid for `file-id`")
|
(assert (uuid? file-id) "expected valud uuid for `file-id`")
|
||||||
|
|
||||||
(ptk/reify ::initialize-workspace
|
(ptk/reify ::initialize-workspace
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(-> state
|
(-> state
|
||||||
(dissoc :files)
|
|
||||||
(dissoc :workspace-ready)
|
|
||||||
(assoc :recent-colors (:recent-colors storage/user))
|
(assoc :recent-colors (:recent-colors storage/user))
|
||||||
(assoc :recent-fonts (:recent-fonts storage/user))
|
(assoc :recent-fonts (:recent-fonts storage/user))
|
||||||
(assoc :current-file-id file-id)
|
(assoc :current-file-id file-id)
|
||||||
|
@ -395,11 +399,9 @@
|
||||||
(dissoc
|
(dissoc
|
||||||
:current-file-id
|
:current-file-id
|
||||||
:workspace-editor-state
|
:workspace-editor-state
|
||||||
:files
|
|
||||||
:workspace-media-objects
|
:workspace-media-objects
|
||||||
:workspace-persistence
|
:workspace-persistence
|
||||||
:workspace-presence
|
:workspace-presence
|
||||||
:workspace-ready
|
|
||||||
:workspace-undo)
|
:workspace-undo)
|
||||||
(update :workspace-global dissoc :read-only?)
|
(update :workspace-global dissoc :read-only?)
|
||||||
(assoc-in [:workspace-global :options-mode] :design)))
|
(assoc-in [:workspace-global :options-mode] :design)))
|
||||||
|
@ -426,48 +428,69 @@
|
||||||
;; Make this event callable through dynamic resolution
|
;; Make this event callable through dynamic resolution
|
||||||
(defmethod ptk/resolve ::reload-current-file [_ _] (reload-current-file))
|
(defmethod ptk/resolve ::reload-current-file [_ _] (reload-current-file))
|
||||||
|
|
||||||
(defn initialize-page
|
|
||||||
[page-id]
|
|
||||||
(assert (uuid? page-id) "expected valid uuid for `page-id`")
|
|
||||||
|
|
||||||
(ptk/reify ::initialize-page
|
|
||||||
|
(def ^:private xf:collect-file-media
|
||||||
|
"Resolve and collect all file media on page objects"
|
||||||
|
(comp (map second)
|
||||||
|
(keep (fn [{:keys [metadata fill-image]}]
|
||||||
|
(cond
|
||||||
|
(some? metadata) (cf/resolve-file-media metadata)
|
||||||
|
(some? fill-image) (cf/resolve-file-media fill-image))))))
|
||||||
|
|
||||||
|
|
||||||
|
(defn- initialize-page*
|
||||||
|
"Second phase of page initialization, once we know the page is
|
||||||
|
available on the sate"
|
||||||
|
[file-id page-id page]
|
||||||
|
(ptk/reify ::initialize-page*
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(if-let [{:keys [id] :as page} (dsh/lookup-page state page-id)]
|
;; selection; when user abandon the current page, the selection is lost
|
||||||
;; we maintain a cache of page state for user convenience with the exception of the
|
(let [local (dm/get-in state [:workspace-cache [file-id page-id]] default-workspace-local)]
|
||||||
;; selection; when user abandon the current page, the selection is lost
|
(-> state
|
||||||
(let [local (dm/get-in state [:workspace-cache id] default-workspace-local)]
|
(assoc :current-page-id page-id)
|
||||||
(-> state
|
(assoc :workspace-local (assoc local :selected (d/ordered-set)))
|
||||||
(assoc :current-page-id id)
|
(assoc :workspace-trimmed-page (dm/select-keys page [:id :name]))
|
||||||
(assoc :workspace-local (assoc local :selected (d/ordered-set)))
|
|
||||||
(assoc :workspace-trimmed-page (dm/select-keys page [:id :name]))
|
|
||||||
|
|
||||||
;; FIXME: this should be done on `initialize-layout` (?)
|
;; FIXME: this should be done on `initialize-layout` (?)
|
||||||
(update :workspace-layout layout/load-layout-flags)
|
(update :workspace-layout layout/load-layout-flags)
|
||||||
(update :workspace-global layout/load-layout-state)))
|
(update :workspace-global layout/load-layout-state))))
|
||||||
|
|
||||||
state))
|
ptk/EffectEvent
|
||||||
|
(effect [_ _ _]
|
||||||
|
(let [uris (into #{} xf:collect-file-media (:objects page))]
|
||||||
|
(->> (rx/from uris)
|
||||||
|
(rx/subs! #(http/fetch-data-uri % false)))))))
|
||||||
|
|
||||||
|
(defn initialize-page
|
||||||
|
[file-id page-id]
|
||||||
|
(assert (uuid? file-id) "expected valid uuid for `file-id`")
|
||||||
|
|
||||||
|
(ptk/reify ::initialize-page
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(if (dsh/lookup-page state page-id)
|
(if-let [page (dsh/lookup-page state file-id page-id)]
|
||||||
(let [file-id (:current-file-id state)]
|
(rx/of (initialize-page* file-id page-id page)
|
||||||
(rx/of (preload-data-uris page-id)
|
(dwth/watch-state-changes file-id page-id)
|
||||||
(dwth/watch-state-changes file-id page-id)
|
(dwl/watch-component-changes)
|
||||||
(dwl/watch-component-changes)))
|
(when (cf/external-feature-flag "boards-02" "test")
|
||||||
(rx/of (dcm/go-to-workspace))))))
|
(select-frame-tool file-id page-id)))
|
||||||
|
(rx/of (dcm/go-to-workspace :file-id file-id ::rt/replace true))))))
|
||||||
|
|
||||||
(defn finalize-page
|
(defn finalize-page
|
||||||
[page-id]
|
[file-id page-id]
|
||||||
|
(assert (uuid? file-id) "expected valid uuid for `file-id`")
|
||||||
(assert (uuid? page-id) "expected valid uuid for `page-id`")
|
(assert (uuid? page-id) "expected valid uuid for `page-id`")
|
||||||
|
|
||||||
(ptk/reify ::finalize-page
|
(ptk/reify ::finalize-page
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(let [local (-> (:workspace-local state)
|
(let [local (-> (:workspace-local state)
|
||||||
(dissoc :edition :edit-path :selected))
|
(dissoc :edition :edit-path :selected))
|
||||||
exit? (not= :workspace (dm/get-in state [:route :data :name]))
|
exit? (not= :workspace (rt/lookup-name state))
|
||||||
state (-> state
|
state (-> state
|
||||||
(update :workspace-cache assoc page-id local)
|
(update :workspace-cache assoc [file-id page-id] local)
|
||||||
(dissoc :current-page-id
|
(dissoc :current-page-id
|
||||||
:workspace-local
|
:workspace-local
|
||||||
:workspace-trimmed-page
|
:workspace-trimmed-page
|
||||||
|
@ -476,22 +499,6 @@
|
||||||
(cond-> state
|
(cond-> state
|
||||||
exit? (dissoc :workspace-drawing))))))
|
exit? (dissoc :workspace-drawing))))))
|
||||||
|
|
||||||
(defn- preload-data-uris
|
|
||||||
"Preloads the image data so it's ready when necessary"
|
|
||||||
[page-id]
|
|
||||||
(ptk/reify ::preload-data-uris
|
|
||||||
ptk/EffectEvent
|
|
||||||
(effect [_ state _]
|
|
||||||
(let [xform (comp (map second)
|
|
||||||
(keep (fn [{:keys [metadata fill-image]}]
|
|
||||||
(cond
|
|
||||||
(some? metadata) (cf/resolve-file-media metadata)
|
|
||||||
(some? fill-image) (cf/resolve-file-media fill-image)))))
|
|
||||||
uris (into #{} xform (dsh/lookup-page-objects state page-id))]
|
|
||||||
|
|
||||||
(->> (rx/from uris)
|
|
||||||
(rx/subs! #(http/fetch-data-uri % false)))))))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Workspace Page CRUD
|
;; Workspace Page CRUD
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
|
@ -120,6 +120,11 @@
|
||||||
([id params & {:as options}]
|
([id params & {:as options}]
|
||||||
(navigate id params options)))
|
(navigate id params options)))
|
||||||
|
|
||||||
|
(defn lookup-name
|
||||||
|
[state]
|
||||||
|
(dm/get-in state [:route :data :name]))
|
||||||
|
|
||||||
|
;; FIXME: rename to lookup-params
|
||||||
(defn get-params
|
(defn get-params
|
||||||
[state]
|
[state]
|
||||||
(dm/get-in state [:route :params :query]))
|
(dm/get-in state [:route :params :query]))
|
||||||
|
|
|
@ -127,7 +127,6 @@
|
||||||
{::mf/props :obj
|
{::mf/props :obj
|
||||||
::mf/private true}
|
::mf/private true}
|
||||||
[{:keys [team-id children]}]
|
[{:keys [team-id children]}]
|
||||||
|
|
||||||
(mf/with-effect [team-id]
|
(mf/with-effect [team-id]
|
||||||
(st/emit! (dtm/initialize-team team-id))
|
(st/emit! (dtm/initialize-team team-id))
|
||||||
(fn []
|
(fn []
|
||||||
|
|
|
@ -621,9 +621,10 @@
|
||||||
[:> comment-content* {:content (:content item)}]]
|
[:> comment-content* {:content (:content item)}]]
|
||||||
|
|
||||||
[:div {:class (stl/css :replies)}
|
[:div {:class (stl/css :replies)}
|
||||||
(let [total-comments (:count-comments item 1)
|
(let [total-comments (:count-comments item)
|
||||||
total-replies (dec total-comments)
|
unread-comments (:count-unread-comments item)
|
||||||
unread-replies (:count-unread-comments item 0)]
|
total-replies (dec total-comments)
|
||||||
|
unread-replies (if (= unread-comments total-comments) (dec unread-comments) unread-comments)]
|
||||||
[:*
|
[:*
|
||||||
(when (> total-replies 0)
|
(when (> total-replies 0)
|
||||||
(if (= total-replies 1)
|
(if (= total-replies 1)
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.main.data.common :as dcm]
|
[app.main.data.common :as dcm]
|
||||||
[app.main.data.helpers :as dsh]
|
|
||||||
[app.main.data.persistence :as dps]
|
[app.main.data.persistence :as dps]
|
||||||
[app.main.data.plugins :as dpl]
|
[app.main.data.plugins :as dpl]
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
|
@ -43,13 +42,6 @@
|
||||||
[okulary.core :as l]
|
[okulary.core :as l]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
(defn- make-workspace-ready-ref
|
|
||||||
[file-id]
|
|
||||||
(l/derived (fn [state]
|
|
||||||
(and (= file-id (:workspace-ready state))
|
|
||||||
(some? (dsh/lookup-file-data state file-id))))
|
|
||||||
st/state))
|
|
||||||
|
|
||||||
(mf/defc workspace-content*
|
(mf/defc workspace-content*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [file layout page wglobal]}]
|
[{:keys [file layout page wglobal]}]
|
||||||
|
@ -129,35 +121,32 @@
|
||||||
|
|
||||||
(mf/defc workspace-page*
|
(mf/defc workspace-page*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [page-id file layout wglobal]}]
|
[{:keys [page-id file-id file layout wglobal]}]
|
||||||
(let [page-id (hooks/use-equal-memo page-id)
|
(let [page (mf/deref refs/workspace-page)]
|
||||||
page (mf/deref refs/workspace-page)]
|
|
||||||
|
|
||||||
(mf/with-effect []
|
(mf/with-effect []
|
||||||
(let [focus-out #(st/emit! (dw/workspace-focus-lost))
|
(let [focus-out #(st/emit! (dw/workspace-focus-lost))
|
||||||
key (events/listen globals/window "blur" focus-out)]
|
key (events/listen globals/window "blur" focus-out)]
|
||||||
(partial events/unlistenByKey key)))
|
(partial events/unlistenByKey key)))
|
||||||
|
|
||||||
(mf/with-effect [page-id]
|
(mf/with-effect [file-id page-id]
|
||||||
(if (some? page-id)
|
(st/emit! (dw/initialize-page file-id page-id))
|
||||||
(st/emit! (dw/initialize-page page-id))
|
|
||||||
(st/emit! (dcm/go-to-workspace ::rt/replace true)))
|
|
||||||
|
|
||||||
(fn []
|
(fn []
|
||||||
(when (some? page-id)
|
(when page-id
|
||||||
(st/emit! (dw/finalize-page page-id)))))
|
(st/emit! (dw/finalize-page file-id page-id)))))
|
||||||
|
|
||||||
(if (some? page)
|
(if (some? page)
|
||||||
[:> workspace-content* {:file file
|
[:> workspace-content* {:file file
|
||||||
:page page
|
:page page
|
||||||
:wglobal wglobal
|
:wglobal wglobal
|
||||||
:layout layout}]
|
:layout layout}]
|
||||||
[:& workspace-loader*])))
|
[:> workspace-loader*])))
|
||||||
|
|
||||||
|
|
||||||
(def ^:private ref:file-without-data
|
(def ^:private ref:file-without-data
|
||||||
(l/derived (fn [file]
|
(l/derived (fn [file]
|
||||||
(dissoc file :data))
|
(-> file
|
||||||
|
(dissoc :data)
|
||||||
|
(assoc ::has-data (contains? file :data))))
|
||||||
refs/file
|
refs/file
|
||||||
=))
|
=))
|
||||||
|
|
||||||
|
@ -181,10 +170,6 @@
|
||||||
read-only? (mf/deref refs/workspace-read-only?)
|
read-only? (mf/deref refs/workspace-read-only?)
|
||||||
read-only? (or read-only? (not (:can-edit permissions)))
|
read-only? (or read-only? (not (:can-edit permissions)))
|
||||||
|
|
||||||
ready* (mf/with-memo [file-id]
|
|
||||||
(make-workspace-ready-ref file-id))
|
|
||||||
ready? (mf/deref ready*)
|
|
||||||
|
|
||||||
design-tokens? (features/use-feature "design-tokens/v1")
|
design-tokens? (features/use-feature "design-tokens/v1")
|
||||||
|
|
||||||
background-color (:background-color wglobal)]
|
background-color (:background-color wglobal)]
|
||||||
|
@ -207,6 +192,10 @@
|
||||||
(st/emit! ::dps/force-persist
|
(st/emit! ::dps/force-persist
|
||||||
(dw/finalize-workspace file-id))))
|
(dw/finalize-workspace file-id))))
|
||||||
|
|
||||||
|
(mf/with-effect [file page-id]
|
||||||
|
(when-not page-id
|
||||||
|
(st/emit! (dcm/go-to-workspace :file-id file-id ::rt/replace true))))
|
||||||
|
|
||||||
[:> (mf/provider ctx/current-project-id) {:value project-id}
|
[:> (mf/provider ctx/current-project-id) {:value project-id}
|
||||||
[:> (mf/provider ctx/current-file-id) {:value file-id}
|
[:> (mf/provider ctx/current-file-id) {:value file-id}
|
||||||
[:> (mf/provider ctx/current-page-id) {:value page-id}
|
[:> (mf/provider ctx/current-page-id) {:value page-id}
|
||||||
|
@ -219,9 +208,11 @@
|
||||||
:touch-action "none"}}
|
:touch-action "none"}}
|
||||||
[:> context-menu*]
|
[:> context-menu*]
|
||||||
|
|
||||||
(if ^boolean ready?
|
(if (::has-data file)
|
||||||
[:> workspace-page* {:page-id page-id
|
[:> workspace-page*
|
||||||
:file file
|
{:page-id page-id
|
||||||
:wglobal wglobal
|
:file-id file-id
|
||||||
:layout layout}]
|
:file file
|
||||||
|
:wglobal wglobal
|
||||||
|
:layout layout}]
|
||||||
[:> workspace-loader*])]]]]]]]))
|
[:> workspace-loader*])]]]]]]]))
|
||||||
|
|
|
@ -403,104 +403,104 @@
|
||||||
:h h :s s :v v
|
:h h :s s :v v
|
||||||
:alpha (/ alpha 255)}))))
|
:alpha (/ alpha 255)}))))
|
||||||
|
|
||||||
[:div {:class (stl/css :colorpicker)
|
[:*
|
||||||
:ref node-ref
|
[:div {:class (stl/css :colorpicker)
|
||||||
:style {:touch-action "none"}}
|
:ref node-ref
|
||||||
[:div {:class (stl/css :top-actions)}
|
:style {:touch-action "none"}}
|
||||||
[:div {:class (stl/css :top-actions-right)}
|
[:div {:class (stl/css :top-actions)}
|
||||||
(when (= :gradient selected-mode)
|
[:div {:class (stl/css :top-actions-right)}
|
||||||
[:div {:class (stl/css :opacity-input-wrapper)}
|
(when (= :gradient selected-mode)
|
||||||
[:span {:class (stl/css :icon-text)} "%"]
|
[:div {:class (stl/css :opacity-input-wrapper)}
|
||||||
[:> numeric-input*
|
[:span {:class (stl/css :icon-text)} "%"]
|
||||||
{:value (-> data :opacity opacity->string)
|
[:> numeric-input*
|
||||||
:on-change handle-change-gradient-opacity
|
{:value (-> data :opacity opacity->string)
|
||||||
:default 100
|
:on-change handle-change-gradient-opacity
|
||||||
:min 0
|
:default 100
|
||||||
:max 100}]])
|
:min 0
|
||||||
|
:max 100}]])
|
||||||
|
|
||||||
(when (or (not disable-gradient) (not disable-image))
|
(when (or (not disable-gradient) (not disable-image))
|
||||||
[:div {:class (stl/css :select)}
|
[:div {:class (stl/css :select)}
|
||||||
[:& select
|
[:& select
|
||||||
{:default-value selected-mode
|
{:default-value selected-mode
|
||||||
:options options
|
:options options
|
||||||
:on-change handle-change-mode}]])]
|
:on-change handle-change-mode}]])]
|
||||||
|
|
||||||
(when (not= selected-mode :image)
|
(when (not= selected-mode :image)
|
||||||
[:button {:class (stl/css-case :picker-btn true
|
[:button {:class (stl/css-case :picker-btn true
|
||||||
:selected picking-color?)
|
:selected picking-color?)
|
||||||
:on-click handle-click-picker}
|
:on-click handle-click-picker}
|
||||||
i/picker])]
|
i/picker])]
|
||||||
|
|
||||||
(when (= selected-mode :gradient)
|
(when (= selected-mode :gradient)
|
||||||
[:> gradients*
|
[:> gradients*
|
||||||
{:type (:type state)
|
{:type (:type state)
|
||||||
:stops (:stops state)
|
:stops (:stops state)
|
||||||
:editing-stop (:editing-stop state)
|
:editing-stop (:editing-stop state)
|
||||||
:on-stop-edit-start handle-stop-edit-start
|
:on-stop-edit-start handle-stop-edit-start
|
||||||
:on-stop-edit-finish handle-stop-edit-finish
|
:on-stop-edit-finish handle-stop-edit-finish
|
||||||
:on-select-stop handle-change-gradient-selected-stop
|
:on-select-stop handle-change-gradient-selected-stop
|
||||||
:on-change-type handle-change-gradient-type
|
:on-change-type handle-change-gradient-type
|
||||||
:on-change-stop handle-gradient-change-stop
|
:on-change-stop handle-gradient-change-stop
|
||||||
:on-add-stop-auto handle-gradient-add-stop-auto
|
:on-add-stop-auto handle-gradient-add-stop-auto
|
||||||
:on-add-stop-preview handle-gradient-add-stop-preview
|
:on-add-stop-preview handle-gradient-add-stop-preview
|
||||||
:on-remove-stop handle-gradient-remove-stop
|
:on-remove-stop handle-gradient-remove-stop
|
||||||
:on-rotate-stops handle-rotate-stops
|
:on-rotate-stops handle-rotate-stops
|
||||||
:on-reverse-stops handle-reverse-stops
|
:on-reverse-stops handle-reverse-stops
|
||||||
:on-reorder-stops handle-reorder-stops}])
|
:on-reorder-stops handle-reorder-stops}])
|
||||||
|
|
||||||
(if (= selected-mode :image)
|
(if (= selected-mode :image)
|
||||||
(let [uri (cfg/resolve-file-media (:image current-color))
|
(let [uri (cfg/resolve-file-media (:image current-color))
|
||||||
keep-aspect-ratio? (-> current-color :image :keep-aspect-ratio)]
|
keep-aspect-ratio? (-> current-color :image :keep-aspect-ratio)]
|
||||||
[:div {:class (stl/css :select-image)}
|
[:div {:class (stl/css :select-image)}
|
||||||
[:div {:class (stl/css :content)}
|
[:div {:class (stl/css :content)}
|
||||||
(when (:image current-color)
|
(when (:image current-color)
|
||||||
[:img {:src uri}])]
|
[:img {:src uri}])]
|
||||||
|
|
||||||
(when (some? (:image current-color))
|
(when (some? (:image current-color))
|
||||||
[:div {:class (stl/css :checkbox-option)}
|
[:div {:class (stl/css :checkbox-option)}
|
||||||
[:label {:for "keep-aspect-ratio"
|
[:label {:for "keep-aspect-ratio"
|
||||||
:class (stl/css-case :global/checked keep-aspect-ratio?)}
|
:class (stl/css-case :global/checked keep-aspect-ratio?)}
|
||||||
[:span {:class (stl/css-case :global/checked keep-aspect-ratio?)}
|
[:span {:class (stl/css-case :global/checked keep-aspect-ratio?)}
|
||||||
(when keep-aspect-ratio?
|
(when keep-aspect-ratio?
|
||||||
i/status-tick)]
|
i/status-tick)]
|
||||||
(tr "media.keep-aspect-ratio")
|
(tr "media.keep-aspect-ratio")
|
||||||
[:input {:type "checkbox"
|
[:input {:type "checkbox"
|
||||||
:id "keep-aspect-ratio"
|
:id "keep-aspect-ratio"
|
||||||
:checked keep-aspect-ratio?
|
:checked keep-aspect-ratio?
|
||||||
:on-change handle-change-keep-aspect-ratio}]]])
|
:on-change handle-change-keep-aspect-ratio}]]])
|
||||||
[:button
|
[:button
|
||||||
{:class (stl/css :choose-image)
|
{:class (stl/css :choose-image)
|
||||||
:title (tr "media.choose-image")
|
:title (tr "media.choose-image")
|
||||||
:aria-label (tr "media.choose-image")
|
:aria-label (tr "media.choose-image")
|
||||||
:on-click on-fill-image-click}
|
:on-click on-fill-image-click}
|
||||||
(tr "media.choose-image")
|
(tr "media.choose-image")
|
||||||
[:& file-uploader
|
[:& file-uploader
|
||||||
{:input-id "fill-image-upload"
|
{:input-id "fill-image-upload"
|
||||||
:accept "image/jpeg,image/png"
|
:accept "image/jpeg,image/png"
|
||||||
:multi false
|
:multi false
|
||||||
:ref fill-image-ref
|
:ref fill-image-ref
|
||||||
:on-selected on-fill-image-selected}]]])
|
:on-selected on-fill-image-selected}]]])
|
||||||
[:*
|
[:*
|
||||||
[:div {:class (stl/css :colorpicker-tabs)}
|
[:div {:class (stl/css :colorpicker-tabs)}
|
||||||
[:> tab-switcher* {:tabs tabs
|
[:> tab-switcher* {:tabs tabs
|
||||||
:default-selected "ramp"
|
:default-selected "ramp"
|
||||||
:on-change-tab on-change-tab}]]
|
:on-change-tab on-change-tab}]]
|
||||||
|
|
||||||
[:& color-inputs
|
[:& color-inputs
|
||||||
{:type type
|
{:type type
|
||||||
:disable-opacity disable-opacity
|
:disable-opacity disable-opacity
|
||||||
:color current-color
|
:color current-color
|
||||||
:on-change handle-change-color}]
|
:on-change handle-change-color}]
|
||||||
|
|
||||||
[:& libraries
|
|
||||||
{:state state
|
|
||||||
:current-color current-color
|
|
||||||
:disable-gradient disable-gradient
|
|
||||||
:disable-opacity disable-opacity
|
|
||||||
:disable-image disable-image
|
|
||||||
:on-select-color on-select-library-color
|
|
||||||
:on-add-library-color on-add-library-color}]])
|
|
||||||
|
|
||||||
|
[:& libraries
|
||||||
|
{:state state
|
||||||
|
:current-color current-color
|
||||||
|
:disable-gradient disable-gradient
|
||||||
|
:disable-opacity disable-opacity
|
||||||
|
:disable-image disable-image
|
||||||
|
:on-select-color on-select-library-color
|
||||||
|
:on-add-library-color on-add-library-color}]])]
|
||||||
(when (fn? on-accept)
|
(when (fn? on-accept)
|
||||||
[:div {:class (stl/css :actions)}
|
[:div {:class (stl/css :actions)}
|
||||||
[:button {:class (stl/css-case
|
[:button {:class (stl/css-case
|
||||||
|
@ -520,32 +520,41 @@
|
||||||
max-y (- vh h)
|
max-y (- vh h)
|
||||||
rulers? (mf/deref refs/rulers?)
|
rulers? (mf/deref refs/rulers?)
|
||||||
left-offset (if rulers? 40 18)
|
left-offset (if rulers? 40 18)
|
||||||
right-offset (+ w 40)]
|
right-offset (+ w 40)
|
||||||
|
top-offset (dm/str (- y 70) "px")
|
||||||
|
bottom-offset "1rem"
|
||||||
|
max-height-top (str "calc(100vh - " top-offset)
|
||||||
|
max-height-bottom (str "calc(100vh -" bottom-offset)]
|
||||||
(cond
|
(cond
|
||||||
(or (nil? x) (nil? y))
|
(or (nil? x) (nil? y))
|
||||||
#js {:left "auto" :right "16rem" :top "4rem"}
|
#js {:left "auto" :right "16rem" :top "4rem" :maxHeight "calc(100vh - 4rem)"}
|
||||||
|
|
||||||
(= position :left)
|
(= position :left)
|
||||||
(if (> y max-y)
|
(if (> y max-y)
|
||||||
#js {:left (dm/str (- x right-offset) "px")
|
#js {:left (dm/str (- x right-offset) "px")
|
||||||
:bottom "1rem"}
|
:bottom bottom-offset
|
||||||
|
:maxHeight max-height-bottom}
|
||||||
#js {:left (dm/str (- x right-offset) "px")
|
#js {:left (dm/str (- x right-offset) "px")
|
||||||
:top (dm/str (- y 70) "px")})
|
:top top-offset
|
||||||
|
:maxHeight max-height-top})
|
||||||
|
|
||||||
(= position :right)
|
(= position :right)
|
||||||
(if (> y max-y)
|
(if (> y max-y)
|
||||||
#js {:left (dm/str (+ x 80) "px")
|
#js {:left (dm/str (+ x 80) "px")
|
||||||
:bottom "1rem"}
|
:bottom bottom-offset
|
||||||
|
:maxHeight max-height-bottom}
|
||||||
#js {:left (dm/str (+ x 80) "px")
|
#js {:left (dm/str (+ x 80) "px")
|
||||||
:top (dm/str (- y 70) "px")})
|
:top top-offset
|
||||||
|
:maxHeight max-height-top})
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(if (> y max-y)
|
(if (> y max-y)
|
||||||
#js {:left (dm/str (+ x left-offset) "px")
|
#js {:left (dm/str (+ x left-offset) "px")
|
||||||
:bottom "1rem"}
|
:bottom bottom-offset
|
||||||
|
:maxHeight max-height-bottom}
|
||||||
#js {:left (dm/str (+ x left-offset) "px")
|
#js {:left (dm/str (+ x left-offset) "px")
|
||||||
:top (dm/str (- y 70) "px")}))))
|
:top top-offset
|
||||||
|
:maxHeight max-height-top}))))
|
||||||
|
|
||||||
(mf/defc colorpicker-modal
|
(mf/defc colorpicker-modal
|
||||||
{::mf/register modal/components
|
{::mf/register modal/components
|
||||||
|
|
|
@ -13,10 +13,14 @@
|
||||||
width: auto;
|
width: auto;
|
||||||
padding: var(--sp-m);
|
padding: var(--sp-m);
|
||||||
width: $sz-284;
|
width: $sz-284;
|
||||||
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.colorpicker {
|
.colorpicker {
|
||||||
border-radius: $br-8;
|
border-radius: $br-8;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.colorpicker-tabs {
|
.colorpicker-tabs {
|
||||||
|
|
|
@ -63,10 +63,10 @@
|
||||||
target-container-id (or target-container-id (:parent-id shape))]
|
target-container-id (or target-container-id (:parent-id shape))]
|
||||||
|
|
||||||
(filter some?
|
(filter some?
|
||||||
[(when target-page-id (dw/initialize-page target-page-id))
|
[(when target-page-id (dw/initialize-page (:id file) target-page-id))
|
||||||
(dws/select-shape target-container-id)
|
(dws/select-shape target-container-id)
|
||||||
(dw/paste-shapes pdata)
|
(dw/paste-shapes pdata)
|
||||||
(when target-page-id (dw/initialize-page (:id page)))])))
|
(when target-page-id (dw/initialize-page (:id file) (:id page)))])))
|
||||||
|
|
||||||
(defn- sync-file [file]
|
(defn- sync-file [file]
|
||||||
(map (fn [component-tag]
|
(map (fn [component-tag]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue