mirror of
https://github.com/penpot/penpot.git
synced 2025-01-08 07:50:43 -05:00
Merge pull request #5105 from penpot/superalex-merge-conflicts-2
Merge conflicts
This commit is contained in:
commit
19a613e90c
37 changed files with 734 additions and 399 deletions
|
@ -93,6 +93,10 @@
|
|||
- Fix layer panel overflowing [Taiga #8665](https://tree.taiga.io/project/penpot/issue/8665)
|
||||
- Fix problem when creating a component instance from grid layout [Github #4881](https://github.com/penpot/penpot/issues/4881)
|
||||
- Fix problem when dismissing shared library update [Taiga #8669](https://tree.taiga.io/project/penpot/issue/8669)
|
||||
- Fix visual problem with stroke cap menu [Taiga #8730](https://tree.taiga.io/project/penpot/issue/8730)
|
||||
- Fix issue when exporting libraries when merging libraries [Taiga #8758](https://tree.taiga.io/project/penpot/issue/8758)
|
||||
- Fix problem with comments max length [Taiga #8778](https://tree.taiga.io/project/penpot/issue/8778)
|
||||
- Fix copy/paste images in Safari [Taiga #8771](https://tree.taiga.io/project/penpot/issue/8771)
|
||||
|
||||
## 2.1.5
|
||||
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
[{:id "wireframing-kit"
|
||||
:name "Wireframe library"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/wireframing-kit.penpot"}
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/wireframing-kit.penpot"}
|
||||
{:id "prototype-examples"
|
||||
:name "Prototype template"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/prototype-examples.penpot"}
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/prototype-examples.penpot"}
|
||||
{:id "plants-app"
|
||||
:name "UI mockup example"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Plants-app.penpot"}
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/Plants-app.penpot"}
|
||||
{:id "penpot-design-system"
|
||||
:name "Design system example"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Penpot-Design-system.penpot"}
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/Penpot-Design-system.penpot"}
|
||||
{:id "tutorial-for-beginners"
|
||||
:name "Tutorial for beginners"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/tutorial-for-beginners.penpot"}
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/tutorial-for-beginners.penpot"}
|
||||
{:id "lucide-icons"
|
||||
:name "Lucide Icons"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Lucide-icons.penpot"}
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/Lucide-icons.penpot"}
|
||||
{:id "font-awesome"
|
||||
:name "Font Awesome"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Font-Awesome.penpot"}
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/FontAwesome.penpot"}
|
||||
{:id "black-white-mobile-templates"
|
||||
:name "Black & White Mobile Templates"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Black-White-Mobile-Templates.penpot"}
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/Black-&-White-Mobile-Templates.penpot"}
|
||||
{:id "avataaars"
|
||||
:name "Avataaars"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Avataaars-by-Pablo-Stanley.penpot"}
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/Avataaars-by-Pablo-Stanley.penpot"}
|
||||
{:id "ux-notes"
|
||||
:name "UX Notes"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/UX-Notes.penpot"}
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/UX-Notes.penpot"}
|
||||
{:id "whiteboarding-kit"
|
||||
:name "Whiteboarding Kit"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Whiteboarding-mapping-kit.penpot"}
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/Whiteboarding-mapping-kit.penpot"}
|
||||
{:id "open-color-scheme"
|
||||
:name "Open Color Scheme"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Open-Color-Scheme.penpot"}
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/Open%20Color%20Scheme%20(v1.9.1).penpot"}
|
||||
{:id "flex-layout-playground"
|
||||
:name "Flex Layout Playground"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Flex-Layout-Playground.penpot"}
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/Flex%20Layout%20Playground.penpot"}
|
||||
{:id "welcome"
|
||||
:name "Welcome"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/welcome.penpot"}]
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/welcome.penpot"}]
|
||||
|
|
|
@ -292,7 +292,7 @@
|
|||
[:map {:title "create-comment-thread"}
|
||||
[:file-id ::sm/uuid]
|
||||
[:position ::gpt/point]
|
||||
[:content [:string {:max 250}]]
|
||||
[:content [:string {:max 750}]]
|
||||
[:page-id ::sm/uuid]
|
||||
[:frame-id ::sm/uuid]
|
||||
[:share-id {:optional true} [:maybe ::sm/uuid]]])
|
||||
|
|
|
@ -269,7 +269,7 @@
|
|||
|
||||
(defn get-minimal-file
|
||||
[cfg id & {:as opts}]
|
||||
(let [opts (assoc opts ::sql/columns [:id :modified-at :revn :data-ref-id :data-backend])]
|
||||
(let [opts (assoc opts ::sql/columns [:id :modified-at :deleted-at :revn :data-ref-id :data-backend])]
|
||||
(db/get cfg :file {:id id} opts)))
|
||||
|
||||
(defn get-file-etag
|
||||
|
@ -483,7 +483,7 @@
|
|||
[:file-id ::sm/uuid]
|
||||
[:page-id {:optional true} ::sm/uuid]
|
||||
[:share-id {:optional true} ::sm/uuid]
|
||||
[:object-id {:optional true} [:or ::sm/uuid ::sm/coll-of-uuid]]
|
||||
[:object-id {:optional true} [:or ::sm/uuid [::sm/set ::sm/uuid]]]
|
||||
[:features {:optional true} ::cfeat/features]])
|
||||
|
||||
(sv/defmethod ::get-page
|
||||
|
|
|
@ -231,7 +231,7 @@
|
|||
"INSERT INTO file_tagged_object_thumbnail (file_id, object_id, tag, media_id)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT (file_id, object_id, tag)
|
||||
DO UPDATE SET updated_at=?, media_id=?, deleted_at=null
|
||||
DO UPDATE SET updated_at=?, media_id=?, deleted_at=?
|
||||
RETURNING *")
|
||||
|
||||
(defn- persist-thumbnail!
|
||||
|
@ -249,17 +249,19 @@
|
|||
:content-type mtype
|
||||
:bucket "file-object-thumbnail"})))
|
||||
|
||||
|
||||
|
||||
(defn- create-file-object-thumbnail!
|
||||
[{:keys [::sto/storage] :as cfg} file-id object-id media tag]
|
||||
(let [tsnow (dt/now)
|
||||
media (persist-thumbnail! storage media tsnow)
|
||||
[{:keys [::sto/storage] :as cfg} file object-id media tag]
|
||||
(let [file-id (:id file)
|
||||
timestamp (dt/now)
|
||||
media (persist-thumbnail! storage media timestamp)
|
||||
[th1 th2] (db/tx-run! cfg (fn [{:keys [::db/conn]}]
|
||||
(let [th1 (db/exec-one! conn [sql:get-file-object-thumbnail file-id object-id tag])
|
||||
th2 (db/exec-one! conn [sql:create-file-object-thumbnail
|
||||
file-id object-id tag (:id media)
|
||||
tsnow (:id media)])]
|
||||
file-id object-id tag
|
||||
(:id media)
|
||||
timestamp
|
||||
(:id media)
|
||||
(:deleted-at file)])]
|
||||
[th1 th2])))]
|
||||
|
||||
(when (and (some? th1)
|
||||
|
@ -292,8 +294,8 @@
|
|||
(media/validate-media-size! media)
|
||||
|
||||
(db/run! cfg files/check-edition-permissions! profile-id file-id)
|
||||
|
||||
(create-file-object-thumbnail! cfg file-id object-id media (or tag "frame")))
|
||||
(when-let [file (files/get-minimal-file cfg file-id {::db/check-deleted false})]
|
||||
(create-file-object-thumbnail! cfg file object-id media (or tag "frame"))))
|
||||
|
||||
;; --- MUTATION COMMAND: delete-file-object-thumbnail
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
:created-at (:created-at file)
|
||||
:modified-at (:modified-at file)
|
||||
:data-backend nil
|
||||
:data-ref-id nil
|
||||
:has-media-trimmed false}
|
||||
{:id (:id file)})))
|
||||
|
||||
|
|
|
@ -133,7 +133,13 @@
|
|||
file))
|
||||
|
||||
(def ^:private sql:get-files-for-library
|
||||
"SELECT f.id, f.data, f.modified_at, f.features, f.version
|
||||
"SELECT f.id,
|
||||
f.data,
|
||||
f.modified_at,
|
||||
f.features,
|
||||
f.version,
|
||||
f.data_backend,
|
||||
f.data_ref_id
|
||||
FROM file AS f
|
||||
LEFT JOIN file_library_rel AS fl ON (fl.file_id = f.id)
|
||||
WHERE fl.library_file_id = ?
|
||||
|
|
|
@ -6,4 +6,4 @@
|
|||
|
||||
(ns app.common.files.defaults)
|
||||
|
||||
(def version 52)
|
||||
(def version 55)
|
||||
|
|
|
@ -863,11 +863,9 @@
|
|||
(assoc shadow :color color)))
|
||||
|
||||
(update-object [object]
|
||||
(d/update-when object :shadow
|
||||
#(into []
|
||||
(comp (map fix-shadow)
|
||||
(filter valid-shadow?))
|
||||
%)))
|
||||
(let [xform (comp (map fix-shadow)
|
||||
(filter valid-shadow?))]
|
||||
(d/update-when object :shadow #(into [] xform %))))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-object))]
|
||||
|
@ -1010,7 +1008,6 @@
|
|||
|
||||
(defn migrate-up-51
|
||||
"This migration fixes library invalid colors"
|
||||
|
||||
[data]
|
||||
(let [update-colors
|
||||
(fn [colors]
|
||||
|
@ -1018,6 +1015,38 @@
|
|||
(update data :colors update-colors)))
|
||||
|
||||
(defn migrate-up-52
|
||||
"Fixes incorrect value on `layout-wrap-type` prop"
|
||||
[data]
|
||||
(letfn [(update-shape [shape]
|
||||
(if (= :no-wrap (:layout-wrap-type shape))
|
||||
(assoc shape :layout-wrap-type :nowrap)
|
||||
shape))
|
||||
|
||||
(update-page [page]
|
||||
(d/update-when page :objects update-vals update-shape))]
|
||||
|
||||
(update data :pages-index update-vals update-page)))
|
||||
|
||||
(defn migrate-up-54
|
||||
"Fixes shapes with invalid colors in shadow: it first tries a non
|
||||
destructive fix, and if it is not possible, then, shadow is removed"
|
||||
[data]
|
||||
(letfn [(fix-shadow [shadow]
|
||||
(update shadow :color d/without-nils))
|
||||
|
||||
(update-shape [shape]
|
||||
(let [xform (comp (map fix-shadow)
|
||||
(filter valid-shadow?))]
|
||||
(d/update-when shape :shadow #(into [] xform %))))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-shape))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
|
||||
(defn migrate-up-55
|
||||
"This migration moves page options to the page level"
|
||||
[data]
|
||||
(let [update-page
|
||||
|
@ -1084,4 +1113,7 @@
|
|||
{:id 49 :migrate-up migrate-up-49}
|
||||
{:id 50 :migrate-up migrate-up-50}
|
||||
{:id 51 :migrate-up migrate-up-51}
|
||||
{:id 52 :migrate-up migrate-up-52}])
|
||||
{:id 52 :migrate-up migrate-up-52}
|
||||
{:id 53 :migrate-up migrate-up-26}
|
||||
{:id 54 :migrate-up migrate-up-54}
|
||||
{:id 55 :migrate-up migrate-up-55}])
|
||||
|
|
|
@ -80,21 +80,23 @@
|
|||
[:opacity {:optional true} [:maybe ::sm/safe-number]]
|
||||
[:offset ::sm/safe-number]]]]])
|
||||
|
||||
(def schema:color-attrs
|
||||
[:map {:title "ColorAttrs"}
|
||||
[:id {:optional true} ::sm/uuid]
|
||||
[:name {:optional true} :string]
|
||||
[:path {:optional true} [:maybe :string]]
|
||||
[:value {:optional true} [:maybe :string]]
|
||||
[:color {:optional true} [:maybe ::rgb-color]]
|
||||
[:opacity {:optional true} [:maybe ::sm/safe-number]]
|
||||
[:modified-at {:optional true} ::sm/inst]
|
||||
[:ref-id {:optional true} ::sm/uuid]
|
||||
[:ref-file {:optional true} ::sm/uuid]
|
||||
[:gradient {:optional true} [:maybe schema:gradient]]
|
||||
[:image {:optional true} [:maybe schema:image-color]]
|
||||
[:plugin-data {:optional true} ::ctpg/plugin-data]])
|
||||
|
||||
(def schema:color
|
||||
[:and
|
||||
[:map {:title "Color"}
|
||||
[:id {:optional true} ::sm/uuid]
|
||||
[:name {:optional true} :string]
|
||||
[:path {:optional true} [:maybe :string]]
|
||||
[:value {:optional true} [:maybe :string]]
|
||||
[:color {:optional true} [:maybe ::rgb-color]]
|
||||
[:opacity {:optional true} [:maybe ::sm/safe-number]]
|
||||
[:modified-at {:optional true} ::sm/inst]
|
||||
[:ref-id {:optional true} ::sm/uuid]
|
||||
[:ref-file {:optional true} ::sm/uuid]
|
||||
[:gradient {:optional true} [:maybe schema:gradient]]
|
||||
[:image {:optional true} [:maybe schema:image-color]]
|
||||
[:plugin-data {:optional true} ::ctpg/plugin-data]]
|
||||
[:and schema:color-attrs
|
||||
[::sm/contains-any {:strict true} [:color :gradient :image]]])
|
||||
|
||||
(def schema:recent-color
|
||||
|
@ -111,12 +113,13 @@
|
|||
(sm/register! ::gradient schema:gradient)
|
||||
(sm/register! ::image-color schema:image-color)
|
||||
(sm/register! ::recent-color schema:recent-color)
|
||||
(sm/register! ::color-attrs schema:color-attrs)
|
||||
|
||||
(def valid-color?
|
||||
(sm/lazy-validator schema:color))
|
||||
(def check-color!
|
||||
(sm/check-fn schema:color))
|
||||
|
||||
(def valid-recent-color?
|
||||
(sm/lazy-validator schema:recent-color))
|
||||
(def check-recent-color!
|
||||
(sm/check-fn schema:recent-color))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; HELPERS
|
||||
|
|
|
@ -125,6 +125,9 @@
|
|||
|
||||
(sm/register! ::stroke schema:stroke)
|
||||
|
||||
(def check-stroke!
|
||||
(sm/check-fn schema:stroke))
|
||||
|
||||
(def schema:shape-base-attrs
|
||||
[:map {:title "ShapeMinimalRecord"}
|
||||
[:id ::sm/uuid]
|
||||
|
|
|
@ -27,3 +27,6 @@
|
|||
[:color ::ctc/color]])
|
||||
|
||||
(sm/register! ::shadow schema:shadow)
|
||||
|
||||
(def check-shadow!
|
||||
(sm/check-fn schema:shadow))
|
||||
|
|
|
@ -177,3 +177,15 @@ test("Bug 7489 - Workspace-palette items stay hidden when opening with keyboard-
|
|||
),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("Bug 8784 - Use keyboard arrow to move inside a text input does not change tabs", async ({
|
||||
page,
|
||||
}) => {
|
||||
const workspacePage = new WorkspacePage(page);
|
||||
await workspacePage.setupEmptyFile();
|
||||
await workspacePage.goToWorkspace();
|
||||
await workspacePage.pageName.click();
|
||||
await page.keyboard.press("ArrowLeft");
|
||||
|
||||
await expect(workspacePage.pageName).toHaveText("Page 1");
|
||||
});
|
||||
|
|
|
@ -143,4 +143,3 @@
|
|||
(reinit))))
|
||||
|
||||
(set! (.-stackTraceLimit js/Error) 50)
|
||||
|
||||
|
|
|
@ -170,13 +170,22 @@
|
|||
accepting invitation, or third party auth signup or singin."
|
||||
[profile]
|
||||
(letfn [(get-redirect-events []
|
||||
(let [team-id (get-current-team-id profile)
|
||||
welcome-file-id (get-in profile [:props :welcome-file-id])]
|
||||
(if (some? welcome-file-id)
|
||||
(rx/of
|
||||
(rt/nav' :workspace {:project-id (:default-project-id profile)
|
||||
:file-id welcome-file-id})
|
||||
(update-profile-props {:welcome-file-id nil}))
|
||||
(let [team-id (get-current-team-id profile)
|
||||
welcome-file-id (dm/get-in profile [:props :welcome-file-id])
|
||||
redirect-href (::login-redirect storage/session)]
|
||||
|
||||
(cond
|
||||
(some? redirect-href)
|
||||
(binding [storage/*sync* true]
|
||||
(swap! storage/session dissoc ::login-redirect)
|
||||
(rx/of (rt/nav-raw :href redirect-href)))
|
||||
|
||||
(some? welcome-file-id)
|
||||
(rx/of (rt/nav' :workspace {:project-id (:default-project-id profile)
|
||||
:file-id welcome-file-id})
|
||||
(update-profile-props {:welcome-file-id nil}))
|
||||
|
||||
:else
|
||||
(rx/of (rt/nav' :dashboard-projects {:team-id team-id})))))]
|
||||
|
||||
(ptk/reify ::logged-in
|
||||
|
|
|
@ -86,7 +86,8 @@
|
|||
[beicon.v2.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[potok.v2.core :as ptk]))
|
||||
[potok.v2.core :as ptk]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(def default-workspace-local {:zoom 1})
|
||||
(log/set-level! :debug)
|
||||
|
@ -1559,15 +1560,40 @@
|
|||
shapes (->> (cfh/selected-with-children objects selected)
|
||||
(keep (d/getf objects)))]
|
||||
|
||||
(->> (rx/from shapes)
|
||||
(rx/merge-map (partial prepare-object objects frame-id))
|
||||
(rx/reduce collect-data initial)
|
||||
(rx/map (partial sort-selected state))
|
||||
(rx/map (partial advance-copies state selected))
|
||||
(rx/map #(t/encode-str % {:type :json-verbose}))
|
||||
(rx/map wapi/write-to-clipboard)
|
||||
(rx/catch on-copy-error)
|
||||
(rx/ignore)))))))))
|
||||
;; The clipboard API doesn't handle well asynchronous calls because it expects to use
|
||||
;; the clipboard in an user interaction. If you do an async call the callback is outside
|
||||
;; the thread of the UI and so Safari blocks the copying event.
|
||||
;; We use the API `ClipboardItem` that allows promises to be passed and so the event
|
||||
;; will wait for the promise to resolve and everything should work as expected.
|
||||
;; This only works in the current versions of the browsers.
|
||||
(if (some? (unchecked-get ug/global "ClipboardItem"))
|
||||
(let [resolve-data-promise
|
||||
(p/create
|
||||
(fn [resolve reject]
|
||||
(->> (rx/from shapes)
|
||||
(rx/merge-map (partial prepare-object objects frame-id))
|
||||
(rx/reduce collect-data initial)
|
||||
(rx/map (partial sort-selected state))
|
||||
(rx/map (partial advance-copies state selected))
|
||||
(rx/map #(t/encode-str % {:type :json-verbose}))
|
||||
(rx/map #(wapi/create-blob % "text/plain"))
|
||||
(rx/subs! resolve reject))))]
|
||||
(->> (rx/from (wapi/write-to-clipboard-promise "text/plain" resolve-data-promise))
|
||||
(rx/catch on-copy-error)
|
||||
(rx/ignore)))
|
||||
|
||||
;; FIXME: this is to support Firefox versions below 116 that don't support `ClipboardItem`
|
||||
;; after the version 116 is less common we could remove this.
|
||||
;; https://caniuse.com/?search=ClipboardItem
|
||||
(->> (rx/from shapes)
|
||||
(rx/merge-map (partial prepare-object objects frame-id))
|
||||
(rx/reduce collect-data initial)
|
||||
(rx/map (partial sort-selected state))
|
||||
(rx/map (partial advance-copies state selected))
|
||||
(rx/map #(t/encode-str % {:type :json-verbose}))
|
||||
(rx/map wapi/write-to-clipboard)
|
||||
(rx/catch on-copy-error)
|
||||
(rx/ignore))))))))))
|
||||
|
||||
(declare ^:private paste-transit)
|
||||
(declare ^:private paste-text)
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
[app.common.files.helpers :as cfh]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.color :as ctc]
|
||||
[app.common.types.shape :refer [check-stroke!]]
|
||||
[app.common.types.shape.shadow :refer [check-shadow!]]
|
||||
[app.main.broadcast :as mbc]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.modal :as md]
|
||||
|
@ -21,7 +24,6 @@
|
|||
[app.main.data.workspace.state-helpers :as wsh]
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.util.color :as uc]
|
||||
[app.util.storage :as storage]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -165,6 +167,15 @@
|
|||
|
||||
(defn add-fill
|
||||
[ids color]
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid color struct"
|
||||
(ctc/check-color! color))
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid coll of uuid's"
|
||||
(every? uuid? ids))
|
||||
|
||||
(ptk/reify ::add-fill
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
@ -175,6 +186,15 @@
|
|||
|
||||
(defn remove-fill
|
||||
[ids color position]
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid color struct"
|
||||
(ctc/check-color! color))
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid coll of uuid's"
|
||||
(every? uuid? ids))
|
||||
|
||||
(ptk/reify ::remove-fill
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
@ -187,13 +207,21 @@
|
|||
|
||||
(defn remove-all-fills
|
||||
[ids color]
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid color struct"
|
||||
(ctc/check-color! color))
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid coll of uuid's"
|
||||
(every? uuid? ids))
|
||||
|
||||
(ptk/reify ::remove-all-fills
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(let [remove-all (fn [shape _] (assoc shape :fills []))]
|
||||
(transform-fill state ids color remove-all)))))
|
||||
|
||||
|
||||
(defn change-hide-fill-on-export
|
||||
[ids hide-fill-on-export]
|
||||
(ptk/reify ::change-hide-fill-on-export
|
||||
|
@ -272,17 +300,25 @@
|
|||
;; example using the color selection from
|
||||
;; multiple shapes) let's use the first stop
|
||||
;; color
|
||||
attrs (cond-> attrs
|
||||
(:gradient attrs) (get-in [:gradient :stops 0]))
|
||||
new-attrs (-> (merge (get-in shape [:shadow index :color]) attrs)
|
||||
(d/without-nils))]
|
||||
(assoc-in shape [:shadow index :color] new-attrs))))))))
|
||||
attrs (cond-> attrs
|
||||
(:gradient attrs)
|
||||
(dm/get-in [:gradient :stops 0]))
|
||||
|
||||
attrs' (-> (dm/get-in shape [:shadow index :color])
|
||||
(merge attrs)
|
||||
(d/without-nils))]
|
||||
(assoc-in shape [:shadow index :color] attrs'))))))))
|
||||
|
||||
(defn add-shadow
|
||||
[ids shadow]
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid shadow struct"
|
||||
(check-shadow! shadow))
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid coll of uuid's"
|
||||
(sm/check-coll-of-uuid! ids))
|
||||
(every? uuid? ids))
|
||||
|
||||
(ptk/reify ::add-shadow
|
||||
ptk/WatchEvent
|
||||
|
@ -293,6 +329,15 @@
|
|||
|
||||
(defn add-stroke
|
||||
[ids stroke]
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid stroke struct"
|
||||
(check-stroke! stroke))
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid coll of uuid's"
|
||||
(every? uuid? ids))
|
||||
|
||||
(ptk/reify ::add-stroke
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
|
@ -301,6 +346,11 @@
|
|||
|
||||
(defn remove-stroke
|
||||
[ids position]
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid coll of uuid's"
|
||||
(every? uuid? ids))
|
||||
|
||||
(ptk/reify ::remove-stroke
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
|
@ -314,6 +364,11 @@
|
|||
|
||||
(defn remove-all-strokes
|
||||
[ids]
|
||||
|
||||
(dm/assert!
|
||||
"expected a valid coll of uuid's"
|
||||
(every? uuid? ids))
|
||||
|
||||
(ptk/reify ::remove-all-strokes
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
|
@ -376,7 +431,7 @@
|
|||
:on-change handle-change-color}
|
||||
:allow-click-outside true})))))))
|
||||
|
||||
(defn color-att->text
|
||||
(defn- color-att->text
|
||||
[color]
|
||||
{:fill-color (when (:color color) (str/lower (:color color)))
|
||||
:fill-opacity (:opacity color)
|
||||
|
@ -395,26 +450,57 @@
|
|||
(some? has-color?)
|
||||
(assoc-in [:fills index] parsed-new-color))))
|
||||
|
||||
(def ^:private schema:change-color-operation
|
||||
[:map
|
||||
[:prop [:enum :fill :stroke :shadow :content]]
|
||||
[:shape-id ::sm/uuid]
|
||||
[:index :int]])
|
||||
|
||||
(def ^:private schema:change-color-operations
|
||||
[:vector schema:change-color-operation])
|
||||
|
||||
(def ^:private check-change-color-operations!
|
||||
(sm/check-fn schema:change-color-operations))
|
||||
|
||||
(defn change-color-in-selected
|
||||
[new-color shapes-by-color old-color]
|
||||
[operations new-color old-color]
|
||||
|
||||
(dm/verify!
|
||||
"expected valid change color operations"
|
||||
(check-change-color-operations! operations))
|
||||
|
||||
(dm/verify!
|
||||
"expected a valid color struct for new-color param"
|
||||
(ctc/check-color! new-color))
|
||||
|
||||
(dm/verify!
|
||||
"expected a valid color struct for old-color param"
|
||||
(ctc/check-color! old-color))
|
||||
|
||||
(ptk/reify ::change-color-in-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [undo-id (js/Symbol)]
|
||||
(rx/concat
|
||||
(rx/of (dwu/start-undo-transaction undo-id))
|
||||
(->> (rx/from shapes-by-color)
|
||||
(rx/map (fn [shape] (case (:prop shape)
|
||||
:fill (change-fill [(:shape-id shape)] new-color (:index shape))
|
||||
:stroke (change-stroke [(:shape-id shape)] new-color (:index shape))
|
||||
:shadow (change-shadow [(:shape-id shape)] new-color (:index shape))
|
||||
:content (dwt/update-text-with-function
|
||||
(:shape-id shape)
|
||||
(partial change-text-color old-color new-color (:index shape)))))))
|
||||
(->> (rx/from operations)
|
||||
(rx/map (fn [{:keys [shape-id index] :as operation}]
|
||||
(case (:prop operation)
|
||||
:fill (change-fill [shape-id] new-color index)
|
||||
:stroke (change-stroke [shape-id] new-color index)
|
||||
:shadow (change-shadow [shape-id] new-color index)
|
||||
:content (dwt/update-text-with-function
|
||||
shape-id
|
||||
(partial change-text-color old-color new-color index))))))
|
||||
(rx/of (dwu/commit-undo-transaction undo-id)))))))
|
||||
|
||||
(defn apply-color-from-palette
|
||||
[color stroke?]
|
||||
|
||||
(dm/assert!
|
||||
"should be a valid color"
|
||||
(ctc/check-color! color))
|
||||
|
||||
(ptk/reify ::apply-color-from-palette
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
@ -437,9 +523,10 @@
|
|||
|
||||
result (cond-> result (not group?) (conj cur))]
|
||||
(recur (rest pending) result))))]
|
||||
|
||||
(if stroke?
|
||||
(rx/of (change-stroke ids (merge uc/empty-color color) 0))
|
||||
(rx/of (change-fill ids (merge uc/empty-color color) 0)))))))
|
||||
(rx/of (change-stroke ids color 0))
|
||||
(rx/of (change-fill ids color 0)))))))
|
||||
|
||||
(declare activate-colorpicker-color)
|
||||
(declare activate-colorpicker-gradient)
|
||||
|
@ -448,15 +535,22 @@
|
|||
|
||||
(defn apply-color-from-colorpicker
|
||||
[color]
|
||||
|
||||
(dm/assert!
|
||||
"expected valid color structure"
|
||||
(ctc/check-color! color))
|
||||
|
||||
(ptk/reify ::apply-color-from-colorpicker
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of
|
||||
(cond
|
||||
(:image color) (activate-colorpicker-image)
|
||||
(:color color) (activate-colorpicker-color)
|
||||
(= :linear (get-in color [:gradient :type])) (activate-colorpicker-gradient :linear-gradient)
|
||||
(= :radial (get-in color [:gradient :type])) (activate-colorpicker-gradient :radial-gradient))))))
|
||||
;; FIXME: revisit this
|
||||
(let [gradient-type (dm/get-in color [:gradient :type])]
|
||||
(rx/of
|
||||
(cond
|
||||
(:image color) (activate-colorpicker-image)
|
||||
(:color color) (activate-colorpicker-color)
|
||||
(= :linear gradient-type) (activate-colorpicker-gradient :linear-gradient)
|
||||
(= :radial gradient-type) (activate-colorpicker-gradient :radial-gradient)))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -596,7 +690,8 @@
|
|||
(update :current-color merge changes)
|
||||
(update :current-color materialize-color-components)
|
||||
(update :current-color #(if (not= type :image) (dissoc % :image) %))
|
||||
;; current color can be a library one I'm changing via colorpicker
|
||||
;; current color can be a library one
|
||||
;; I'm changing via colorpicker
|
||||
(d/dissoc-in [:current-color :id])
|
||||
(d/dissoc-in [:current-color :file-id]))]
|
||||
(if-let [stop (:editing-stop state)]
|
||||
|
@ -614,7 +709,8 @@
|
|||
:colorpicker
|
||||
:type)
|
||||
formated-color (get-color-from-colorpicker-state (:colorpicker state))
|
||||
;; Type is set to color on closing the colorpicker, but we can can close it while still uploading an image fill
|
||||
;; Type is set to color on closing the colorpicker, but we
|
||||
;; can can close it while still uploading an image fill
|
||||
ignore-color? (and (= selected-type :color) (nil? (:color formated-color)))]
|
||||
(when (and add-recent? (not ignore-color?))
|
||||
(rx/of (dwl/add-recent-color formated-color)))))))
|
||||
|
@ -686,6 +782,7 @@
|
|||
|
||||
(defn select-color
|
||||
[position add-color]
|
||||
;; FIXME: revisit
|
||||
(ptk/reify ::select-color
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
|
|
|
@ -116,8 +116,13 @@
|
|||
(update :id #(or % (uuid/next)))
|
||||
(assoc :name (or (get-in color [:image :name])
|
||||
(:color color)
|
||||
(uc/gradient-type->string (get-in color [:gradient :type])))))]
|
||||
(dm/assert! ::ctc/color color)
|
||||
(uc/gradient-type->string (get-in color [:gradient :type]))))
|
||||
(d/without-nils))]
|
||||
|
||||
(dm/assert!
|
||||
"expect valid color structure"
|
||||
(ctc/check-color! color))
|
||||
|
||||
(ptk/reify ::add-color
|
||||
ev/Event
|
||||
(-data [_] color)
|
||||
|
@ -135,8 +140,8 @@
|
|||
[color]
|
||||
|
||||
(dm/assert!
|
||||
"expected valid recent color map"
|
||||
(ctc/valid-recent-color? color))
|
||||
"expected valid recent color structure"
|
||||
(ctc/check-recent-color! color))
|
||||
|
||||
(ptk/reify ::add-recent-color
|
||||
ptk/UpdateEvent
|
||||
|
@ -155,7 +160,7 @@
|
|||
(update [_ state]
|
||||
(assoc-in state [:workspace-local :color-for-rename] nil))))
|
||||
|
||||
(defn- do-update-color
|
||||
(defn- update-color*
|
||||
[it state color file-id]
|
||||
(let [data (get state :workspace-data)
|
||||
[path name] (cfh/parse-path-name (:name color))
|
||||
|
@ -171,19 +176,20 @@
|
|||
|
||||
(defn update-color
|
||||
[color file-id]
|
||||
(let [color (d/without-nils color)]
|
||||
|
||||
(dm/assert!
|
||||
"expected valid parameters"
|
||||
(ctc/valid-color? color))
|
||||
(dm/assert!
|
||||
"expected valid color data structure"
|
||||
(ctc/check-color! color))
|
||||
|
||||
(dm/assert!
|
||||
"expected file-id"
|
||||
(uuid? file-id))
|
||||
(dm/assert!
|
||||
"expected file-id"
|
||||
(uuid? file-id))
|
||||
|
||||
(ptk/reify ::update-color
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(do-update-color it state color file-id))))
|
||||
(ptk/reify ::update-color
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(update-color* it state color file-id)))))
|
||||
|
||||
(defn rename-color
|
||||
[file-id id new-name]
|
||||
|
@ -198,9 +204,10 @@
|
|||
(if (str/empty? new-name)
|
||||
(rx/empty)
|
||||
(let [data (get state :workspace-data)
|
||||
object (get-in data [:colors id])
|
||||
object (assoc object :name new-name)]
|
||||
(do-update-color it state object file-id)))))))
|
||||
color (get-in data [:colors id])
|
||||
color (assoc color :name new-name)
|
||||
color (d/without-nils color)]
|
||||
(update-color* it state color file-id)))))))
|
||||
|
||||
(defn delete-color
|
||||
[{:keys [id] :as params}]
|
||||
|
|
|
@ -42,7 +42,8 @@
|
|||
(mf/lazy-component app.main.ui.workspace/workspace))
|
||||
|
||||
(mf/defc main-page
|
||||
{::mf/props :obj}
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [route profile]}]
|
||||
(let [{:keys [data params]} route
|
||||
props (get profile :props)
|
||||
|
@ -68,6 +69,7 @@
|
|||
(:onboarding-viewed props)
|
||||
(not= (:release-notes-viewed props) (:main cf/version))
|
||||
(not= "0.0" (:main cf/version)))]
|
||||
|
||||
[:& (mf/provider ctx/current-route) {:value route}
|
||||
(case (:name data)
|
||||
(:auth-login
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
[app.util.i18n :refer [tr]]
|
||||
[app.util.keyboard :as k]
|
||||
[app.util.router :as rt]
|
||||
[app.util.storage :as s]
|
||||
[beicon.v2.core :as rx]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -47,10 +48,21 @@
|
|||
(defn- login-with-oidc
|
||||
[event provider params]
|
||||
(dom/prevent-default event)
|
||||
|
||||
(binding [s/*sync* true]
|
||||
(if (some? (:save-login-redirect params))
|
||||
;; Save the current login raw uri for later redirect user back to
|
||||
;; the same page, we need it to be synchronous because the user is
|
||||
;; going to be redirected instantly to the oidc provider uri
|
||||
(swap! s/session assoc :login-redirect (rt/get-current-href))
|
||||
;; Clean the login redirect
|
||||
(swap! s/session dissoc :login-redirect)))
|
||||
|
||||
;; FIXME: this code should be probably moved outside of the UI
|
||||
(->> (rp/cmd! :login-with-oidc (assoc params :provider provider))
|
||||
(rx/subs! (fn [{:keys [redirect-uri] :as rsp}]
|
||||
(if redirect-uri
|
||||
(.replace js/location redirect-uri)
|
||||
(st/emit! (rt/nav-raw :uri redirect-uri))
|
||||
(log/error :hint "unexpected response from OIDC method"
|
||||
:resp (pr-str rsp))))
|
||||
(fn [cause]
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
on-focus (unchecked-get props "on-focus")
|
||||
on-blur (unchecked-get props "on-blur")
|
||||
placeholder (unchecked-get props "placeholder")
|
||||
max-length (unchecked-get props "max-length")
|
||||
on-change (unchecked-get props "on-change")
|
||||
on-esc (unchecked-get props "on-esc")
|
||||
on-ctrl-enter (unchecked-get props "on-ctrl-enter")
|
||||
|
@ -88,7 +89,8 @@
|
|||
:on-blur on-blur
|
||||
:value value
|
||||
:placeholder placeholder
|
||||
:on-change on-change*}]))
|
||||
:on-change on-change*
|
||||
:max-length max-length}]))
|
||||
|
||||
(mf/defc reply-form
|
||||
[{:keys [thread] :as props}]
|
||||
|
@ -128,7 +130,8 @@
|
|||
:on-focus on-focus
|
||||
:select-on-focus? false
|
||||
:on-ctrl-enter on-submit
|
||||
:on-change on-change}]
|
||||
:on-change on-change
|
||||
:max-length 750}]
|
||||
(when (or @show-buttons? (seq @content))
|
||||
[:div {:class (stl/css :buttons-wrapper)}
|
||||
[:input.btn-secondary
|
||||
|
@ -196,7 +199,8 @@
|
|||
:select-on-focus? false
|
||||
:on-esc on-esc
|
||||
:on-change on-change
|
||||
:on-ctrl-enter on-submit}]
|
||||
:on-ctrl-enter on-submit
|
||||
:max-length 750}]
|
||||
[:div {:class (stl/css :buttons-wrapper)}
|
||||
|
||||
[:input {:on-click on-esc
|
||||
|
@ -233,7 +237,8 @@
|
|||
:select-on-focus true
|
||||
:select-on-focus? false
|
||||
:on-ctrl-enter on-submit*
|
||||
:on-change on-change}]
|
||||
:on-change on-change
|
||||
:max-length 750}]
|
||||
[:div {:class (stl/css :buttons-wrapper)}
|
||||
[:input {:type "button"
|
||||
:value "Cancel"
|
||||
|
|
|
@ -42,9 +42,7 @@
|
|||
(let [search-term (get-in route [:params :query :search-term])
|
||||
team-id (get-in route [:params :path :team-id])
|
||||
project-id (get-in route [:params :path :project-id])]
|
||||
(cond->
|
||||
{:search-term search-term}
|
||||
|
||||
(cond-> {:search-term search-term}
|
||||
(uuid-str? team-id)
|
||||
(assoc :team-id (uuid team-id))
|
||||
|
||||
|
@ -84,10 +82,10 @@
|
|||
|
||||
(mf/use-effect on-resize)
|
||||
|
||||
|
||||
[:div {:class (stl/css :dashboard-content)
|
||||
:style {:pointer-events (when file-menu-open? "none")}
|
||||
:on-click clear-selected-fn :ref container}
|
||||
:on-click clear-selected-fn
|
||||
:ref container}
|
||||
(case section
|
||||
:dashboard-projects
|
||||
[:*
|
||||
|
@ -146,7 +144,8 @@
|
|||
(l/derived :current-team-id st/state))
|
||||
|
||||
(mf/defc dashboard
|
||||
[{:keys [route profile] :as props}]
|
||||
{::mf/props :obj}
|
||||
[{:keys [route profile]}]
|
||||
(let [section (get-in route [:data :name])
|
||||
params (parse-params route)
|
||||
|
||||
|
@ -181,13 +180,13 @@
|
|||
|
||||
[:& (mf/provider ctx/current-team-id) {:value team-id}
|
||||
[:& (mf/provider ctx/current-project-id) {:value project-id}
|
||||
;; NOTE: dashboard events and other related functions assumes
|
||||
;; that the team is a implicit context variable that is
|
||||
;; available using react context or accessing
|
||||
;; the :current-team-id on the state. We set the key to the
|
||||
;; team-id because we want to completely refresh all the
|
||||
;; components on team change. Many components assumes that the
|
||||
;; team is already set so don't put the team into mf/deps.
|
||||
;; NOTE: dashboard events and other related functions assumes
|
||||
;; that the team is a implicit context variable that is
|
||||
;; available using react context or accessing
|
||||
;; the :current-team-id on the state. We set the key to the
|
||||
;; team-id because we want to completely refresh all the
|
||||
;; components on team change. Many components assumes that the
|
||||
;; team is already set so don't put the team into mf/deps.
|
||||
(when (and team initialized?)
|
||||
[:main {:class (stl/css :dashboard)
|
||||
:key (:id team)}
|
||||
|
|
|
@ -177,8 +177,7 @@
|
|||
|
||||
class (dm/str class " " (stl/css :tabs))
|
||||
|
||||
props (mf/spread-props props {:class class
|
||||
:on-key-down on-key-down})]
|
||||
props (mf/spread-props props {:class class})]
|
||||
|
||||
(mf/with-effect [tabs]
|
||||
(mf/set-ref-val! tabs-ref tabs))
|
||||
|
@ -190,6 +189,7 @@
|
|||
:tabs tabs
|
||||
:on-ref on-ref
|
||||
:selected selected
|
||||
:on-key-down on-key-down
|
||||
:on-click on-click}]]
|
||||
|
||||
(let [active-tab (get-tab tabs selected)
|
||||
|
|
|
@ -101,7 +101,17 @@
|
|||
}
|
||||
|
||||
.tab-panel {
|
||||
--tab-panel-outline-color: none;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
--tab-panel-outline-color: var(--color-accent-primary);
|
||||
}
|
||||
|
||||
display: grid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: $b-1 solid var(--tab-panel-outline-color);
|
||||
}
|
||||
|
|
|
@ -260,6 +260,14 @@
|
|||
(when ^boolean obj
|
||||
(apply (.-f obj) args)))))))
|
||||
|
||||
(defn use-ref-value
|
||||
"Returns a ref that will be automatically updated when the value is changed"
|
||||
[v]
|
||||
(let [ref (mf/use-ref v)]
|
||||
(mf/with-effect [v]
|
||||
(mf/set-ref-val! ref v))
|
||||
ref))
|
||||
|
||||
(defn use-equal-memo
|
||||
[val]
|
||||
(let [ref (mf/use-ref nil)]
|
||||
|
|
|
@ -106,9 +106,9 @@
|
|||
(st/emit! (rt/navigated match))
|
||||
|
||||
:else
|
||||
;; We just recheck with an additional profile request; this avoids
|
||||
;; some race conditions that causes unexpected redirects on
|
||||
;; invitations workflows (and probably other cases).
|
||||
;; We just recheck with an additional profile request; this
|
||||
;; avoids some race conditions that causes unexpected redirects
|
||||
;; on invitations workflows (and probably other cases).
|
||||
(->> (rp/cmd! :get-profile)
|
||||
(rx/subs! (fn [{:keys [id] :as profile}]
|
||||
(cond
|
||||
|
|
|
@ -7,19 +7,21 @@
|
|||
(ns app.main.ui.static
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
["rxjs" :as rxjs]
|
||||
[app.common.data :as d]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.uri :as u]
|
||||
[app.main.data.common :as dc]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.auth.login :refer [login-methods]]
|
||||
[app.main.ui.auth.recovery-request :refer [recovery-request-page recovery-sent-page]]
|
||||
[app.main.ui.auth.register :refer [register-methods register-validate-form register-success-page terms-register]]
|
||||
[app.main.ui.auth.register :as register]
|
||||
[app.main.ui.dashboard.sidebar :refer [sidebar]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.viewer.header :as header]
|
||||
[app.main.ui.viewer.header :as viewer.header]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
|
@ -29,10 +31,14 @@
|
|||
[potok.v2.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
;; FIXME: this is a workaround until we export this class on beicon library
|
||||
(def TimeoutError rxjs/TimeoutError)
|
||||
|
||||
(mf/defc error-container*
|
||||
{::mf/props :obj}
|
||||
[{:keys [children]}]
|
||||
(let [profile-id (:profile-id @st/state)]
|
||||
(let [profile-id (:profile-id @st/state)
|
||||
on-nav-root (mf/use-fn #(st/emit! (rt/nav-root)))]
|
||||
[:section {:class (stl/css :exception-layout)}
|
||||
[:button
|
||||
{:class (stl/css :exception-header)
|
||||
|
@ -44,7 +50,7 @@
|
|||
[:div {:class (stl/css :deco-before)} i/logo-error-screen]
|
||||
(when-not profile-id
|
||||
[:button {:class (stl/css :login-header)
|
||||
:on-click rt/nav-root}
|
||||
:on-click on-nav-root}
|
||||
(tr "labels.login")])
|
||||
|
||||
[:div {:class (stl/css :exception-content)}
|
||||
|
@ -65,8 +71,8 @@
|
|||
{::mf/props :obj}
|
||||
[{:keys [show-dialog]}]
|
||||
(let [current-section (mf/use-state :login)
|
||||
user-email (mf/use-state "")
|
||||
register-token (mf/use-state "")
|
||||
user-email (mf/use-state "")
|
||||
register-token (mf/use-state "")
|
||||
|
||||
set-section
|
||||
(mf/use-fn
|
||||
|
@ -85,29 +91,37 @@
|
|||
#(reset! current-section :login))
|
||||
|
||||
success-login
|
||||
(fn []
|
||||
(reset! show-dialog false)
|
||||
(.reload js/window.location true))
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! show-dialog false)
|
||||
(st/emit! (rt/reload true))))
|
||||
|
||||
success-register
|
||||
(fn [data]
|
||||
(reset! register-token (:token data))
|
||||
(reset! current-section :register-validate))
|
||||
(mf/use-fn
|
||||
(fn [data]
|
||||
(reset! register-token (:token data))
|
||||
(reset! current-section :register-validate)))
|
||||
|
||||
register-email-sent
|
||||
(fn [email]
|
||||
(reset! user-email email)
|
||||
(reset! current-section :register-email-sent))
|
||||
(mf/use-fn
|
||||
(fn [email]
|
||||
(reset! user-email email)
|
||||
(reset! current-section :register-email-sent)))
|
||||
|
||||
recovery-email-sent
|
||||
(fn [email]
|
||||
(reset! user-email email)
|
||||
(reset! current-section :recovery-email-sent))]
|
||||
(mf/use-fn
|
||||
(fn [email]
|
||||
(reset! user-email email)
|
||||
(reset! current-section :recovery-email-sent)))
|
||||
|
||||
on-nav-root
|
||||
(mf/use-fn #(st/emit! (rt/nav-root)))]
|
||||
|
||||
[:div {:class (stl/css :overlay)}
|
||||
[:div {:class (stl/css :dialog-login)}
|
||||
[:div {:class (stl/css :modal-close)}
|
||||
[:button {:class (stl/css :modal-close-button) :on-click rt/nav-root}
|
||||
[:button {:class (stl/css :modal-close-button)
|
||||
:on-click on-nav-root}
|
||||
i/close]]
|
||||
[:div {:class (stl/css :login)}
|
||||
[:div {:class (stl/css :logo)} i/logo]
|
||||
|
@ -118,19 +132,21 @@
|
|||
[:div {:class (stl/css :logo-title)} (tr "labels.login")]
|
||||
[:div {:class (stl/css :logo-subtitle)} (tr "not-found.login.free")]
|
||||
[:& login-methods {:on-recovery-request set-section-recovery
|
||||
:on-success-callback success-login}]
|
||||
:on-success-callback success-login
|
||||
:params {:save-login-redirect true}}]
|
||||
[:hr {:class (stl/css :separator)}]
|
||||
[:div {:class (stl/css :change-section)}
|
||||
(tr "auth.register")
|
||||
" "
|
||||
[:a {:data-section "register"
|
||||
:on-click set-section} (tr "auth.register-submit")]]]
|
||||
:on-click set-section}
|
||||
(tr "auth.register-submit")]]]
|
||||
|
||||
:register
|
||||
[:*
|
||||
[:div {:class (stl/css :logo-title)} (tr "not-found.login.signup-free")]
|
||||
[:div {:class (stl/css :logo-subtitle)} (tr "not-found.login.start-using")]
|
||||
[:& register-methods {:on-success-callback success-register :hide-separator true}]
|
||||
[:& register/register-methods {:on-success-callback success-register :hide-separator true}]
|
||||
#_[:hr {:class (stl/css :separator)}]
|
||||
[:div {:class (stl/css :separator)}]
|
||||
[:div {:class (stl/css :change-section)}
|
||||
|
@ -140,12 +156,13 @@
|
|||
:on-click set-section} (tr "auth.login-here")]]
|
||||
[:div {:class (stl/css :links)}
|
||||
[:hr {:class (stl/css :separator)}]
|
||||
[:& terms-register]]]
|
||||
[:& register/terms-register]]]
|
||||
|
||||
:register-validate
|
||||
[:div {:class (stl/css :form-container)}
|
||||
[:& register-validate-form {:params {:token @register-token}
|
||||
:on-success-callback register-email-sent}]
|
||||
[:& register/register-validate-form
|
||||
{:params {:token @register-token}
|
||||
:on-success-callback register-email-sent}]
|
||||
[:div {:class (stl/css :links)}
|
||||
[:div {:class (stl/css :register)}
|
||||
[:a {:data-section "register"
|
||||
|
@ -154,7 +171,7 @@
|
|||
|
||||
:register-email-sent
|
||||
[:div {:class (stl/css :form-container)}
|
||||
[:& register-success-page {:params {:email @user-email :hide-logo true}}]]
|
||||
[:& register/register-success-page {:params {:email @user-email :hide-logo true}}]]
|
||||
|
||||
:recovery-request
|
||||
[:& recovery-request-page {:go-back-callback set-section-login
|
||||
|
@ -166,43 +183,57 @@
|
|||
|
||||
(mf/defc request-dialog
|
||||
{::mf/props :obj}
|
||||
[{:keys [title content button-text on-button-click cancel-text]}]
|
||||
(let [on-click (or on-button-click rt/nav-root)]
|
||||
[{:keys [title content button-text on-button-click cancel-text on-close]}]
|
||||
(let [on-click (or on-button-click on-close)]
|
||||
[:div {:class (stl/css :overlay)}
|
||||
[:div {:class (stl/css :dialog)}
|
||||
[:div {:class (stl/css :modal-close)}
|
||||
[:button {:class (stl/css :modal-close-button) :on-click rt/nav-root}
|
||||
[:button {:class (stl/css :modal-close-button) :on-click on-close}
|
||||
i/close]]
|
||||
[:div {:class (stl/css :dialog-title)} title]
|
||||
(for [txt content]
|
||||
[:div txt])
|
||||
(for [[index content] (d/enumerate content)]
|
||||
[:div {:key index} content])
|
||||
[:div {:class (stl/css :sign-info)}
|
||||
(when cancel-text
|
||||
[:button {:class (stl/css :cancel-button) :on-click rt/nav-root} cancel-text])
|
||||
[:button {:class (stl/css :cancel-button)
|
||||
:on-click on-close}
|
||||
cancel-text])
|
||||
[:button {:on-click on-click} button-text]]]]))
|
||||
|
||||
|
||||
(mf/defc request-access
|
||||
{::mf/props :obj}
|
||||
[{:keys [file-id team-id is-default workspace?]}]
|
||||
(let [profile (:profile @st/state)
|
||||
(let [profile (mf/deref refs/profile)
|
||||
requested* (mf/use-state {:sent false :already-requested false})
|
||||
requested (deref requested*)
|
||||
show-dialog (mf/use-state true)
|
||||
|
||||
on-close
|
||||
(mf/use-fn
|
||||
(mf/deps profile)
|
||||
(fn []
|
||||
(st/emit! (rt/nav :dashboard-projects {:team-id (:default-team-id profile)}))))
|
||||
|
||||
on-success
|
||||
(mf/use-fn
|
||||
#(reset! requested* {:sent true :already-requested false}))
|
||||
|
||||
on-error
|
||||
(mf/use-fn
|
||||
#(reset! requested* {:sent true :already-requested true}))
|
||||
|
||||
on-request-access
|
||||
(mf/use-fn
|
||||
(mf/deps file-id team-id workspace?)
|
||||
(fn []
|
||||
(let [params (if (some? file-id) {:file-id file-id :is-viewer (not workspace?)} {:team-id team-id})
|
||||
mdata {:on-success on-success :on-error on-error}]
|
||||
(st/emit! (dc/create-team-access-request (with-meta params mdata))))))]
|
||||
|
||||
(let [params (if (some? file-id)
|
||||
{:file-id file-id
|
||||
:is-viewer (not workspace?)}
|
||||
{:team-id team-id})
|
||||
mdata {:on-success on-success
|
||||
:on-error on-error}]
|
||||
(st/emit! (dc/create-team-access-request
|
||||
(with-meta params mdata))))))]
|
||||
|
||||
[:*
|
||||
(if (some? file-id)
|
||||
|
@ -214,17 +245,24 @@
|
|||
[:div {:class (stl/css :project-name)} (tr "not-found.no-permission.project-name")]
|
||||
[:div {:class (stl/css :file-name)} (tr "not-found.no-permission.penpot-file")]]]
|
||||
[:div {:class (stl/css :workspace-right)}]]
|
||||
|
||||
[:div {:class (stl/css :viewer)}
|
||||
[:& header/header {:project {:name (tr "not-found.no-permission.project-name")}
|
||||
:index 0
|
||||
:file {:name (tr "not-found.no-permission.penpot-file")}
|
||||
:page nil
|
||||
:frame nil
|
||||
:permissions {:is-logged true}
|
||||
:zoom 1
|
||||
:section :interactions
|
||||
:shown-thumbnails false
|
||||
:interactions-mode nil}]])
|
||||
;; FIXME: the viewer header was never designed to be reused
|
||||
;; from other parts of the application, and this code looks
|
||||
;; like a fast workaround reusing it as-is without a proper
|
||||
;; component adaptation for be able to use it easily it on
|
||||
;; viewer context or static error page context
|
||||
[:& viewer.header/header {:project
|
||||
{:name (tr "not-found.no-permission.project-name")}
|
||||
:index 0
|
||||
:file {:name (tr "not-found.no-permission.penpot-file")}
|
||||
:page nil
|
||||
:frame nil
|
||||
:permissions {:is-logged true}
|
||||
:zoom 1
|
||||
:section :interactions
|
||||
:shown-thumbnails false
|
||||
:interactions-mode nil}]])
|
||||
|
||||
[:div {:class (stl/css :dashboard)}
|
||||
[:div {:class (stl/css :dashboard-sidebar)}
|
||||
|
@ -242,22 +280,27 @@
|
|||
[:& login-dialog {:show-dialog show-dialog}]
|
||||
|
||||
is-default
|
||||
[:& request-dialog {:title (tr "not-found.no-permission.project") :button-text (tr "not-found.no-permission.go-dashboard")}]
|
||||
[:& request-dialog {:title (tr "not-found.no-permission.project")
|
||||
:button-text (tr "not-found.no-permission.go-dashboard")
|
||||
:on-close on-close}]
|
||||
|
||||
(and (some? file-id) (:already-requested requested))
|
||||
[:& request-dialog {:title (tr "not-found.no-permission.already-requested.file")
|
||||
:content [(tr "not-found.no-permission.already-requested.or-others.file")]
|
||||
:button-text (tr "not-found.no-permission.go-dashboard")}]
|
||||
:button-text (tr "not-found.no-permission.go-dashboard")
|
||||
:on-close on-close}]
|
||||
|
||||
(:already-requested requested)
|
||||
[:& request-dialog {:title (tr "not-found.no-permission.already-requested.project")
|
||||
:content [(tr "not-found.no-permission.already-requested.or-others.project")]
|
||||
:button-text (tr "not-found.no-permission.go-dashboard")}]
|
||||
:button-text (tr "not-found.no-permission.go-dashboard")
|
||||
:on-close on-close}]
|
||||
|
||||
(:sent requested)
|
||||
[:& request-dialog {:title (tr "not-found.no-permission.done.success")
|
||||
:content [(tr "not-found.no-permission.done.remember")]
|
||||
:button-text (tr "not-found.no-permission.go-dashboard")}]
|
||||
:button-text (tr "not-found.no-permission.go-dashboard")
|
||||
:on-close on-close}]
|
||||
|
||||
(some? file-id)
|
||||
[:& request-dialog {:title (tr "not-found.no-permission.file")
|
||||
|
@ -265,7 +308,8 @@
|
|||
(tr "not-found.no-permission.if-approves")]
|
||||
:button-text (tr "not-found.no-permission.ask")
|
||||
:on-button-click on-request-access
|
||||
:cancel-text (tr "not-found.no-permission.go-dashboard")}]
|
||||
:cancel-text (tr "not-found.no-permission.go-dashboard")
|
||||
:on-close on-close}]
|
||||
|
||||
(some? team-id)
|
||||
[:& request-dialog {:title (tr "not-found.no-permission.project")
|
||||
|
@ -273,7 +317,8 @@
|
|||
(tr "not-found.no-permission.if-approves")]
|
||||
:button-text (tr "not-found.no-permission.ask")
|
||||
:on-button-click on-request-access
|
||||
:cancel-text (tr "not-found.no-permission.go-dashboard")}]))]))
|
||||
:cancel-text (tr "not-found.no-permission.go-dashboard")
|
||||
:on-close on-close}]))]))
|
||||
|
||||
(mf/defc not-found*
|
||||
[]
|
||||
|
@ -379,64 +424,92 @@
|
|||
[:div {:class (stl/css :sign-info)}
|
||||
[:button {:on-click on-reset} (tr "labels.retry")]]]))
|
||||
|
||||
(defn- load-info
|
||||
"Load exception page info"
|
||||
[path-params]
|
||||
(let [default {:loaded true}
|
||||
stream (cond
|
||||
(:file-id path-params)
|
||||
(->> (rp/cmd! :get-file-info {:id (:file-id path-params)})
|
||||
(rx/map (fn [info]
|
||||
{:loaded true
|
||||
:file-id (:id info)})))
|
||||
|
||||
(:team-id path-params)
|
||||
(->> (rp/cmd! :get-team-info {:id (:team-id path-params)})
|
||||
(rx/map (fn [info]
|
||||
{:loaded true
|
||||
:team-id (:id info)
|
||||
:team-default (:is-default info)})))
|
||||
|
||||
:else
|
||||
(rx/of default))]
|
||||
|
||||
(->> stream
|
||||
(rx/timeout 3000)
|
||||
(rx/catch (fn [cause]
|
||||
(if (instance? TimeoutError cause)
|
||||
(rx/of default)
|
||||
(rx/throw cause)))))))
|
||||
|
||||
|
||||
(mf/defc exception-page*
|
||||
{::mf/props :obj}
|
||||
[{:keys [data route] :as props}]
|
||||
|
||||
(let [file-info (mf/use-state {:pending true})
|
||||
team-info (mf/use-state {:pending true})
|
||||
type (:type data)
|
||||
path (:path route)
|
||||
(let [type (:type data)
|
||||
path (:path route)
|
||||
|
||||
workspace? (str/includes? path "workspace")
|
||||
dashboard? (str/includes? path "dashboard")
|
||||
view? (str/includes? path "view")
|
||||
query-params (:query-params route)
|
||||
path-params (:path-params route)
|
||||
|
||||
request-access? (and
|
||||
(or workspace? dashboard? view?)
|
||||
(or (not (str/empty? (:file-id @file-info))) (not (str/empty? (:team-id @team-info)))))
|
||||
workspace? (str/includes? path "workspace")
|
||||
dashboard? (str/includes? path "dashboard")
|
||||
view? (str/includes? path "view")
|
||||
|
||||
query-params (u/map->query-string (:query-params route))
|
||||
pparams (:path-params route)
|
||||
on-file-info (mf/use-fn
|
||||
(fn [info]
|
||||
(reset! file-info {:file-id (:id info)})))
|
||||
on-team-info (mf/use-fn
|
||||
(fn [info]
|
||||
(reset! team-info {:team-id (:id info) :is-default (:is-default info)})))]
|
||||
;; We stora the request access info int this state
|
||||
info* (mf/use-state nil)
|
||||
info (deref info*)
|
||||
|
||||
(mf/with-effect [type path query-params pparams @file-info @team-info]
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "exception-page" :type type :path path :query-params query-params}))
|
||||
loaded? (get info :loaded false)
|
||||
|
||||
(when (and (:file-id pparams) (:pending @file-info))
|
||||
(->> (rp/cmd! :get-file-info {:id (:file-id pparams)})
|
||||
(rx/subs! on-file-info)))
|
||||
request-access?
|
||||
(and
|
||||
(or workspace? dashboard? view?)
|
||||
(or (:file-id info)
|
||||
(:team-id info)))]
|
||||
|
||||
(when (and (:team-id pparams) (:pending @team-info))
|
||||
(->> (rp/cmd! :get-team-info {:id (:team-id pparams)})
|
||||
(rx/subs! on-team-info))))
|
||||
(mf/with-effect [type path query-params path-params]
|
||||
(let [query-params (u/map->query-string query-params)
|
||||
event-params {::ev/name "exception-page"
|
||||
:type type
|
||||
:path path
|
||||
:query-params query-params}]
|
||||
(st/emit! (ptk/event ::ev/event event-params))))
|
||||
|
||||
(case (:type data)
|
||||
:not-found
|
||||
(mf/with-effect [path-params info]
|
||||
(when-not (:loaded info)
|
||||
(->> (load-info path-params)
|
||||
(rx/subs! (partial reset! info*)))))
|
||||
|
||||
(when loaded?
|
||||
(if request-access?
|
||||
[:& request-access {:file-id (:file-id @file-info)
|
||||
:team-id (:team-id @team-info)
|
||||
:is-default (:is-default @team-info)
|
||||
[:& request-access {:file-id (:file-id info)
|
||||
:team-id (:team-id info)
|
||||
:is-default (:team-default info)
|
||||
:workspace? workspace?}]
|
||||
[:> not-found* {}])
|
||||
|
||||
:authentication
|
||||
(if request-access?
|
||||
[:& request-access {:file-id (:file-id @file-info)
|
||||
:team-id (:team-id @team-info)
|
||||
:is-default (:is-default @team-info)
|
||||
:workspace? workspace?}]
|
||||
[:> not-found* {}])
|
||||
(case (:type data)
|
||||
:not-found
|
||||
[:> not-found* {}]
|
||||
|
||||
:bad-gateway
|
||||
[:> bad-gateway* props]
|
||||
:authentication
|
||||
[:> not-found* {}]
|
||||
|
||||
:service-unavailable
|
||||
[:& service-unavailable]
|
||||
:bad-gateway
|
||||
[:> bad-gateway* props]
|
||||
|
||||
[:> internal-error* props])))
|
||||
:service-unavailable
|
||||
[:& service-unavailable]
|
||||
|
||||
[:> internal-error* props])))))
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
(cond-> color
|
||||
(:value color) (assoc :color (:value color) :opacity 1)
|
||||
(:value color) (dissoc :value)
|
||||
true (assoc :file-id file-id)))
|
||||
:always (assoc :file-id file-id)))
|
||||
|
||||
color-id (:id color)
|
||||
|
||||
|
@ -70,7 +70,7 @@
|
|||
(fn [event]
|
||||
(st/emit!
|
||||
(dwl/add-recent-color color)
|
||||
(dc/apply-color-from-palette (merge uc/empty-color color) (kbd/alt? event)))))
|
||||
(dc/apply-color-from-palette color (kbd/alt? event)))))
|
||||
|
||||
rename-color
|
||||
(mf/use-fn
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.title-bar :refer [title-bar]]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
@ -21,82 +22,96 @@
|
|||
(defn- prepare-colors
|
||||
[shapes file-id shared-libs]
|
||||
(let [data (into [] (remove nil? (ctc/extract-all-colors shapes file-id shared-libs)))
|
||||
grouped-colors (group-by :attrs data)
|
||||
groups (d/group-by :attrs #(dissoc % :attrs) data)
|
||||
all-colors (distinct (mapv :attrs data))
|
||||
|
||||
tmp (group-by #(some? (:id %)) all-colors)
|
||||
library-colors (get tmp true)
|
||||
colors (get tmp false)]
|
||||
{:grouped-colors grouped-colors
|
||||
{:groups groups
|
||||
:all-colors all-colors
|
||||
:colors colors
|
||||
:library-colors library-colors}))
|
||||
|
||||
(def xf:map-shape-id
|
||||
(map :shape-id))
|
||||
|
||||
(mf/defc color-selection-menu
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shapes"]))]
|
||||
::mf/wrap-props false}
|
||||
[{:keys [shapes file-id shared-libs]}]
|
||||
(let [{:keys [grouped-colors library-colors colors]} (mf/with-memo [shapes file-id shared-libs]
|
||||
(prepare-colors shapes file-id shared-libs))
|
||||
(let [{:keys [groups library-colors colors]} (mf/with-memo [shapes file-id shared-libs]
|
||||
(prepare-colors shapes file-id shared-libs))
|
||||
|
||||
state* (mf/use-state true)
|
||||
open? (deref state*)
|
||||
state* (mf/use-state true)
|
||||
open? (deref state*)
|
||||
|
||||
has-colors? (or (some? (seq colors)) (some? (seq library-colors)))
|
||||
has-colors? (or (some? (seq colors)) (some? (seq library-colors)))
|
||||
|
||||
toggle-content (mf/use-fn #(swap! state* not))
|
||||
toggle-content (mf/use-fn #(swap! state* not))
|
||||
|
||||
expand-lib-color (mf/use-state false)
|
||||
expand-color (mf/use-state false)
|
||||
|
||||
grouped-colors* (mf/use-var nil)
|
||||
prev-colors* (mf/use-var [])
|
||||
groups-ref (h/use-ref-value groups)
|
||||
prev-colors-ref (mf/use-ref nil)
|
||||
|
||||
;; grouped-colors* (mf/use-var nil)
|
||||
;; prev-colors* (mf/use-var [])
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(fn [new-color old-color from-picker?]
|
||||
(let [old-color (-> old-color (dissoc :name :path) d/without-nils)
|
||||
(prn "new-color" new-color)
|
||||
(prn "old-color" old-color)
|
||||
(let [old-color (-> old-color
|
||||
(dissoc :name :path)
|
||||
(d/without-nils))
|
||||
|
||||
;; When dragging on the color picker sometimes all
|
||||
;; the shapes hasn't updated the color to the prev
|
||||
;; value so we need this extra calculation
|
||||
shapes-by-old-color (get @grouped-colors* old-color)
|
||||
prev-color (d/seek #(get @grouped-colors* %) @prev-colors*)
|
||||
shapes-by-prev-color (get @grouped-colors* prev-color)
|
||||
shapes-by-color (or shapes-by-prev-color shapes-by-old-color)]
|
||||
groups (mf/ref-val groups-ref)
|
||||
prev-colors (mf/ref-val prev-colors-ref)
|
||||
|
||||
prev-color (d/seek (partial get groups) prev-colors)
|
||||
|
||||
cops-old (get groups old-color)
|
||||
cops-prev (get groups prev-colors)
|
||||
cops (or cops-prev cops-old)
|
||||
old-color (or prev-color old-color)]
|
||||
|
||||
(when from-picker?
|
||||
(swap! prev-colors* conj (-> new-color (dissoc :name :path) d/without-nils)))
|
||||
(let [color (-> new-color
|
||||
(dissoc :name :path)
|
||||
(d/without-nils))]
|
||||
(mf/set-ref-val! prev-colors-ref
|
||||
(conj prev-colors color))))
|
||||
|
||||
(st/emit! (dc/change-color-in-selected new-color shapes-by-color (or prev-color old-color))))))
|
||||
(st/emit! (dc/change-color-in-selected cops new-color old-color)))))
|
||||
|
||||
on-open
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! prev-colors* [])))
|
||||
(mf/use-fn #(mf/set-ref-val! prev-colors-ref []))
|
||||
|
||||
on-close
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! prev-colors* [])))
|
||||
(mf/use-fn #(mf/set-ref-val! prev-colors-ref []))
|
||||
|
||||
on-detach
|
||||
(mf/use-fn
|
||||
(fn [color]
|
||||
(let [shapes-by-color (get @grouped-colors* color)
|
||||
new-color (assoc color :id nil :file-id nil)]
|
||||
(st/emit! (dc/change-color-in-selected new-color shapes-by-color color)))))
|
||||
(let [groups (mf/ref-val groups-ref)
|
||||
cops (get groups color)
|
||||
color' (dissoc color :id :file-id)]
|
||||
(st/emit! (dc/change-color-in-selected cops color' color)))))
|
||||
|
||||
select-only
|
||||
(mf/use-fn
|
||||
(fn [color]
|
||||
(let [shapes-by-color (get @grouped-colors* color)
|
||||
ids (into (d/ordered-set) (map :shape-id) shapes-by-color)]
|
||||
(let [groups (mf/ref-val groups-ref)
|
||||
cops (get groups color)
|
||||
ids (into (d/ordered-set) xf:map-shape-id cops)]
|
||||
(st/emit! (dws/select-shapes ids)))))]
|
||||
|
||||
(mf/with-effect [grouped-colors]
|
||||
(reset! grouped-colors* grouped-colors))
|
||||
|
||||
[:div {:class (stl/css :element-set)}
|
||||
[:div {:class (stl/css :element-title)}
|
||||
[:& title-bar {:collapsable has-colors?
|
||||
|
|
|
@ -113,9 +113,9 @@
|
|||
handle-value-change
|
||||
(mf/use-fn
|
||||
(mf/deps color on-change)
|
||||
(fn [new-value]
|
||||
(fn [value]
|
||||
(let [color (-> color
|
||||
(assoc :color new-value)
|
||||
(assoc :color value)
|
||||
(dissoc :gradient))]
|
||||
(st/emit! (dwl/add-recent-color color)
|
||||
(on-change color)))))
|
||||
|
@ -146,7 +146,9 @@
|
|||
:else
|
||||
color)
|
||||
|
||||
{:keys [x y]} (dom/get-client-position event)
|
||||
cpos (dom/get-client-position event)
|
||||
x (dm/get-prop cpos :x)
|
||||
y (dm/get-prop cpos :y)
|
||||
|
||||
props {:x x
|
||||
:y y
|
||||
|
@ -154,14 +156,14 @@
|
|||
:disable-opacity disable-opacity
|
||||
:disable-image disable-image
|
||||
;; on-change second parameter means if the source is the color-picker
|
||||
:on-change #(on-change (merge uc/empty-color %) true)
|
||||
:on-change #(on-change % true)
|
||||
:on-close (fn [value opacity id file-id]
|
||||
(when on-close
|
||||
(on-close value opacity id file-id)))
|
||||
:data color}]
|
||||
|
||||
(when on-open
|
||||
(on-open (merge uc/empty-color color)))
|
||||
(when (fn? on-open)
|
||||
(on-open color))
|
||||
|
||||
(modal/show! :colorpicker props))))
|
||||
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
}
|
||||
|
||||
.stroke-options {
|
||||
@include flexRow;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
gap: $s-4;
|
||||
grid-template-columns: 1fr 2fr 2fr;
|
||||
|
||||
.stroke-width-input-element {
|
||||
|
@ -28,11 +29,14 @@
|
|||
}
|
||||
}
|
||||
.stroke-caps-options {
|
||||
@include flexRow;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
gap: $s-4;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
}
|
||||
|
||||
.cap-select {
|
||||
width: $s-124;
|
||||
width: 100%;
|
||||
}
|
||||
.stroke-cap-dropdown,
|
||||
.stroke-cap-dropdown-start {
|
||||
|
|
|
@ -80,9 +80,6 @@
|
|||
(= id :multiple)
|
||||
(= file-id :multiple)))
|
||||
|
||||
(def empty-color
|
||||
(into {} (map #(vector % nil)) [:color :id :file-id :gradient :opacity :image]))
|
||||
|
||||
(defn get-color-name
|
||||
[color]
|
||||
(or (:color-library-name color)
|
||||
|
|
|
@ -760,8 +760,10 @@
|
|||
(.back (.-history js/window)))
|
||||
|
||||
(defn reload-current-window
|
||||
[]
|
||||
(.reload (.-location js/window)))
|
||||
([]
|
||||
(.reload globals/location))
|
||||
([force?]
|
||||
(.reload globals/location force?)))
|
||||
|
||||
(defn scroll-by!
|
||||
([element x y]
|
||||
|
|
|
@ -148,14 +148,33 @@
|
|||
(defn nav-root
|
||||
"Navigate to the root page."
|
||||
[]
|
||||
(set! (.-href globals/location) "/"))
|
||||
(ptk/reify ::nav-root
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
(set! (.-href globals/location) "/"))))
|
||||
|
||||
(defn reload
|
||||
[force?]
|
||||
(ptk/reify ::reload
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
(ts/asap (partial dom/reload-current-window force?)))))
|
||||
|
||||
(defn nav-raw
|
||||
[href]
|
||||
[& {:keys [href uri]}]
|
||||
(ptk/reify ::nav-raw
|
||||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
(set! (.-href globals/location) href))))
|
||||
(cond
|
||||
(string? uri)
|
||||
(.replace globals/location uri)
|
||||
|
||||
(string? href)
|
||||
(set! (.-href globals/location) href)))))
|
||||
|
||||
(defn get-current-href
|
||||
[]
|
||||
(.-href globals/location))
|
||||
|
||||
(defn get-current-path
|
||||
[]
|
||||
|
@ -164,6 +183,7 @@
|
|||
(subs hash 1)
|
||||
hash)))
|
||||
|
||||
|
||||
;; --- History API
|
||||
|
||||
(defn initialize-history
|
||||
|
|
|
@ -16,9 +16,17 @@
|
|||
;; Using ex/ignoring because can receive a DOMException like this when
|
||||
;; importing the code as a library: Failed to read the 'localStorage'
|
||||
;; property from 'Window': Storage is disabled inside 'data:' URLs.
|
||||
(defonce ^:private local-storage
|
||||
(defonce ^:private local-storage-backend
|
||||
(ex/ignoring (unchecked-get g/global "localStorage")))
|
||||
|
||||
(defonce ^:private session-storage-backend
|
||||
(ex/ignoring (unchecked-get g/global "sessionStorage")))
|
||||
|
||||
(def ^:dynamic *sync*
|
||||
"Dynamic variable which determines the mode of operation of the
|
||||
storage mutatio actions. By default is asynchronous."
|
||||
false)
|
||||
|
||||
(defn- encode-key
|
||||
[prefix k]
|
||||
(assert (keyword? k) "key must be keyword")
|
||||
|
@ -37,36 +45,43 @@
|
|||
(keyword kns kn))))))
|
||||
|
||||
(defn- lookup-by-index
|
||||
[prefix result index]
|
||||
[backend prefix result index]
|
||||
(try
|
||||
(let [key (.key ^js local-storage index)
|
||||
(let [key (.key ^js backend index)
|
||||
key' (decode-key prefix key)]
|
||||
(if key'
|
||||
(let [val (.getItem ^js local-storage key)]
|
||||
(let [val (.getItem ^js backend key)]
|
||||
(assoc! result key' (t/decode-str val)))
|
||||
result))
|
||||
(catch :default _
|
||||
result)))
|
||||
|
||||
(defn- load-data
|
||||
[prefix]
|
||||
(if (some? local-storage)
|
||||
(let [length (.-length ^js local-storage)]
|
||||
[backend prefix]
|
||||
(if (some? backend)
|
||||
(let [length (.-length ^js backend)]
|
||||
(loop [index 0
|
||||
result (transient {})]
|
||||
(if (< index length)
|
||||
(recur (inc index)
|
||||
(lookup-by-index prefix result index))
|
||||
(lookup-by-index backend prefix result index))
|
||||
(persistent! result))))
|
||||
{}))
|
||||
|
||||
(defn create-storage
|
||||
[prefix]
|
||||
(let [initial (load-data prefix)
|
||||
[backend prefix]
|
||||
(let [initial (load-data backend prefix)
|
||||
curr-data #js {:content initial}
|
||||
last-data #js {:content initial}
|
||||
watches (js/Map.)
|
||||
|
||||
update-key
|
||||
(fn [key val]
|
||||
(when (some? backend)
|
||||
(if (some? val)
|
||||
(.setItem ^js backend (encode-key prefix key) (t/encode-str val))
|
||||
(.removeItem ^js backend (encode-key prefix key)))))
|
||||
|
||||
on-change*
|
||||
(fn [curr-state]
|
||||
(let [prev-state (unchecked-get last-data "content")]
|
||||
|
@ -75,9 +90,7 @@
|
|||
(let [prev-val (get prev-state key)
|
||||
curr-val (get curr-state key)]
|
||||
(when-not (identical? curr-val prev-val)
|
||||
(if (some? curr-val)
|
||||
(.setItem ^js local-storage (encode-key prefix key) (t/encode-str curr-val))
|
||||
(.removeItem ^js local-storage (encode-key prefix key))))))
|
||||
(update-key key curr-val))))
|
||||
(into #{} (concat (keys curr-state)
|
||||
(keys prev-state))))
|
||||
(finally
|
||||
|
@ -103,7 +116,9 @@
|
|||
(-reset! [self newval]
|
||||
(let [oldval (unchecked-get curr-data "content")]
|
||||
(unchecked-set curr-data "content" newval)
|
||||
(on-change newval)
|
||||
(if *sync*
|
||||
(on-change* newval)
|
||||
(on-change newval))
|
||||
(when (> (.-size watches) 0)
|
||||
(-notify-watches self oldval newval))
|
||||
newval))
|
||||
|
@ -138,5 +153,7 @@
|
|||
(-remove-watch [_ key]
|
||||
(.delete watches key)))))
|
||||
|
||||
(defonce global (create-storage "penpot-global"))
|
||||
(defonce user (create-storage "penpot-user"))
|
||||
(defonce global (create-storage local-storage-backend "penpot-global"))
|
||||
(defonce user (create-storage local-storage-backend "penpot-user"))
|
||||
(defonce storage (create-storage local-storage-backend "penpot"))
|
||||
(defonce session (create-storage session-storage-backend "penpot"))
|
||||
|
|
|
@ -103,6 +103,14 @@
|
|||
(let [cboard (unchecked-get js/navigator "clipboard")]
|
||||
(.writeText ^js cboard data)))
|
||||
|
||||
(defn write-to-clipboard-promise
|
||||
[mimetype promise]
|
||||
(let [cboard (unchecked-get js/navigator "clipboard")
|
||||
data (js/ClipboardItem.
|
||||
(-> (obj/create)
|
||||
(obj/set! mimetype promise)))]
|
||||
(.write ^js cboard #js [data])))
|
||||
|
||||
(defn read-from-clipboard
|
||||
[]
|
||||
(try
|
||||
|
|
|
@ -202,108 +202,61 @@
|
|||
|
||||
(defn make-local-external-references
|
||||
[file file-id]
|
||||
(let [detach-text
|
||||
(let [change-fill
|
||||
(fn [fill]
|
||||
(cond-> fill
|
||||
(not= file-id (:fill-color-ref-file fill))
|
||||
(assoc :fill-color-ref-file file-id)))
|
||||
|
||||
change-stroke
|
||||
(fn [stroke]
|
||||
(cond-> stroke
|
||||
(not= file-id (:stroke-color-ref-file stroke))
|
||||
(assoc :stroke-color-ref-file file-id)))
|
||||
|
||||
change-text
|
||||
(fn [content]
|
||||
(->> content
|
||||
(ct/transform-nodes
|
||||
#(cond-> %
|
||||
(not= file-id (:fill-color-ref-file %))
|
||||
(assoc :fill-color-ref-file file-id)
|
||||
(fn [node]
|
||||
(-> node
|
||||
(d/update-when :fills #(mapv change-fill %))
|
||||
(cond-> (not= file-id (:typography-ref-file node))
|
||||
(assoc :typography-ref-file file-id)))))))
|
||||
|
||||
(not= file-id (:typography-ref-file %))
|
||||
(assoc :typography-ref-file file-id)))))
|
||||
|
||||
detach-shape
|
||||
change-shape
|
||||
(fn [shape]
|
||||
(cond-> shape
|
||||
(not= file-id (:fill-color-ref-file shape))
|
||||
(assoc :fill-color-ref-file file-id)
|
||||
(-> shape
|
||||
(d/update-when :fills #(mapv change-fill %))
|
||||
(d/update-when :strokes #(mapv change-stroke %))
|
||||
(cond-> (not= file-id (:component-file shape))
|
||||
(assoc :component-file file-id))
|
||||
|
||||
(not= file-id (:stroke-color-ref-file shape))
|
||||
(assoc :stroke-color-ref-file file-id)
|
||||
(cond-> (= :text (:type shape))
|
||||
(update :content change-text))))
|
||||
|
||||
(not= file-id (:component-file shape))
|
||||
(assoc :component-file file-id)
|
||||
|
||||
(= :text (:type shape))
|
||||
(update :content detach-text)))
|
||||
|
||||
detach-objects
|
||||
change-objects
|
||||
(fn [objects]
|
||||
(->> objects
|
||||
(d/mapm #(detach-shape %2))))
|
||||
(d/mapm #(change-shape %2))))
|
||||
|
||||
detach-pages
|
||||
change-pages
|
||||
(fn [pages-index]
|
||||
(->> pages-index
|
||||
(d/mapm
|
||||
(fn [_ data]
|
||||
(-> data
|
||||
(update :objects detach-objects))))))]
|
||||
(update :objects change-objects))))))]
|
||||
(-> file
|
||||
(update-in [:data :pages-index] detach-pages))))
|
||||
|
||||
(defn collect-external-references
|
||||
[file]
|
||||
|
||||
(let [get-text-refs
|
||||
(fn [content]
|
||||
(->> content
|
||||
(ct/node-seq #(or (contains? % :fill-color-ref-id)
|
||||
(contains? % :typography-ref-id)))
|
||||
|
||||
(mapcat (fn [node]
|
||||
(cond-> []
|
||||
(contains? node :fill-color-ref-id)
|
||||
(conj {:id (:fill-color-ref-id node)
|
||||
:file-id (:fill-color-ref-file node)})
|
||||
|
||||
(contains? node :typography-ref-id)
|
||||
(conj {:id (:typography-ref-id node)
|
||||
:file-id (:typography-ref-file node)}))))
|
||||
|
||||
(into [])))
|
||||
|
||||
get-shape-refs
|
||||
(fn [[_ shape]]
|
||||
(cond-> []
|
||||
(contains? shape :fill-color-ref-id)
|
||||
(conj {:id (:fill-color-ref-id shape)
|
||||
:file-id (:fill-color-ref-file shape)})
|
||||
|
||||
(contains? shape :stroke-color-ref-id)
|
||||
(conj {:id (:stroke-color-ref-id shape)
|
||||
:file-id (:stroke-color-ref-file shape)})
|
||||
|
||||
(contains? shape :component-id)
|
||||
(conj {:id (:component-id shape)
|
||||
:file-id (:component-file shape)})
|
||||
|
||||
(= :text (:type shape))
|
||||
(into (get-text-refs (:content shape)))))]
|
||||
|
||||
(->> (get-in file [:data :pages-index])
|
||||
(vals)
|
||||
(mapcat :objects)
|
||||
(mapcat get-shape-refs)
|
||||
(filter (comp some? :file-id))
|
||||
(filter (comp some? :id))
|
||||
(group-by :file-id)
|
||||
(d/mapm #(mapv :id %2)))))
|
||||
(update-in [:data :pages-index] change-pages))))
|
||||
|
||||
(defn merge-assets [target-file assets-files]
|
||||
(let [external-refs (collect-external-references target-file)
|
||||
|
||||
merge-file-assets
|
||||
(let [merge-file-assets
|
||||
(fn [target file]
|
||||
(let [colors (-> (get-in file [:data :colors])
|
||||
(select-keys (get external-refs (:id file))))
|
||||
typographies (-> (get-in file [:data :typographies])
|
||||
(select-keys (get external-refs (:id file))))
|
||||
media (-> (get-in file [:data :media])
|
||||
(select-keys (get external-refs (:id file))))
|
||||
components (-> (ctkl/components (:data file))
|
||||
(select-keys (get external-refs (:id file))))]
|
||||
(let [colors (get-in file [:data :colors])
|
||||
typographies (get-in file [:data :typographies])
|
||||
media (get-in file [:data :media])
|
||||
components (ctkl/components (:data file))]
|
||||
(cond-> target
|
||||
(d/not-empty? colors)
|
||||
(update-in [:data :colors] merge colors)
|
||||
|
@ -323,16 +276,20 @@
|
|||
(defn process-export
|
||||
[file-id export-type files]
|
||||
|
||||
(case export-type
|
||||
:all files
|
||||
:merge (let [file-list (-> files (d/without-keys [file-id]) vals)]
|
||||
(-> (select-keys files [file-id])
|
||||
(update file-id merge-assets file-list)
|
||||
(update file-id make-local-external-references file-id)
|
||||
(update file-id dissoc :libraries)))
|
||||
:detach (-> (select-keys files [file-id])
|
||||
(update file-id ctf/detach-external-references file-id)
|
||||
(update file-id dissoc :libraries))))
|
||||
(let [result
|
||||
(case export-type
|
||||
:all files
|
||||
:merge (let [file-list (-> files (d/without-keys [file-id]) vals)]
|
||||
(-> (select-keys files [file-id])
|
||||
(update file-id merge-assets file-list)
|
||||
(update file-id make-local-external-references file-id)
|
||||
(update file-id dissoc :libraries)))
|
||||
:detach (-> (select-keys files [file-id])
|
||||
(update file-id ctf/detach-external-references file-id)
|
||||
(update file-id dissoc :libraries)))]
|
||||
|
||||
;;(.log js/console (clj->js result))
|
||||
result))
|
||||
|
||||
(defn collect-files
|
||||
[file-id export-type features]
|
||||
|
|
Loading…
Reference in a new issue