mirror of
https://github.com/penpot/penpot.git
synced 2025-01-08 07:50:43 -05:00
WIP: workspace toolbar and project/page state management.
This commit is contained in:
parent
c62425cbc8
commit
688d94c1da
6 changed files with 264 additions and 141 deletions
|
@ -3,6 +3,7 @@
|
||||||
[uxbox.router :as r]
|
[uxbox.router :as r]
|
||||||
[uxbox.state :as st]
|
[uxbox.state :as st]
|
||||||
[uxbox.schema :as sc]
|
[uxbox.schema :as sc]
|
||||||
|
[uxbox.time :as time]
|
||||||
[bouncer.validators :as v]))
|
[bouncer.validators :as v]))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -39,14 +40,14 @@
|
||||||
(let [uuid (:id page)]
|
(let [uuid (:id page)]
|
||||||
(update-in state [:pages-by-id] assoc uuid page)))
|
(update-in state [:pages-by-id] assoc uuid page)))
|
||||||
|
|
||||||
;; (defn project-pages
|
(defn project-pages
|
||||||
;; "Get a ordered list of pages that
|
"Get a ordered list of pages that
|
||||||
;; belongs to a specified project."
|
belongs to a specified project."
|
||||||
;; [state projectid]
|
[state projectid]
|
||||||
;; (let [xf (comp
|
(->> (vals (:pages-by-id state))
|
||||||
;; (filter #(= projectid (:project %)))
|
(filter #(= projectid (:project %)))
|
||||||
;; (map #(get-in state [:pages-by-id %])))]
|
(sort-by :created)
|
||||||
;; (into [] xf (:pages state))))
|
(into [])))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Events
|
;; Events
|
||||||
|
@ -60,6 +61,7 @@
|
||||||
(-apply-update [_ state]
|
(-apply-update [_ state]
|
||||||
(let [page {:id (random-uuid)
|
(let [page {:id (random-uuid)
|
||||||
:project project
|
:project project
|
||||||
|
:created (time/now :unix)
|
||||||
:name name
|
:name name
|
||||||
:width width
|
:width width
|
||||||
:height height}]
|
:height height}]
|
||||||
|
@ -79,6 +81,7 @@
|
||||||
(let [proj {:id uuid
|
(let [proj {:id uuid
|
||||||
:name name
|
:name name
|
||||||
:width width
|
:width width
|
||||||
|
:created (time/now :unix)
|
||||||
:height height
|
:height height
|
||||||
:layout layout}]
|
:layout layout}]
|
||||||
(assoc-project state proj)))
|
(assoc-project state proj)))
|
||||||
|
@ -99,7 +102,9 @@
|
||||||
(reify
|
(reify
|
||||||
rs/UpdateEvent
|
rs/UpdateEvent
|
||||||
(-apply-update [_ state]
|
(-apply-update [_ state]
|
||||||
(assoc state :workspace {:project projectid :page pageid}))
|
(assoc state :workspace {:project projectid
|
||||||
|
:page pageid
|
||||||
|
:toolboxes {}}))
|
||||||
|
|
||||||
IPrintWithWriter
|
IPrintWithWriter
|
||||||
(-pr-writer [mv writer _]
|
(-pr-writer [mv writer _]
|
||||||
|
@ -117,9 +122,10 @@
|
||||||
(if pageid
|
(if pageid
|
||||||
(rs/emit! (r/navigate :main/page {:project-uuid projectid
|
(rs/emit! (r/navigate :main/page {:project-uuid projectid
|
||||||
:page-uuid pageid}))
|
:page-uuid pageid}))
|
||||||
(let [pages (get-in state [:projects-by-id projectid :pages])]
|
(let [pages (project-pages state projectid)
|
||||||
|
pageid (:id (first pages))]
|
||||||
(rs/emit! (r/navigate :main/page {:project-uuid projectid
|
(rs/emit! (r/navigate :main/page {:project-uuid projectid
|
||||||
:page-uuid (first pages)})))))
|
:page-uuid pageid})))))
|
||||||
IPrintWithWriter
|
IPrintWithWriter
|
||||||
(-pr-writer [mv writer _]
|
(-pr-writer [mv writer _]
|
||||||
(-write writer "#<event:u.s.p/go-to-project")))))
|
(-write writer "#<event:u.s.p/go-to-project")))))
|
||||||
|
|
|
@ -82,3 +82,13 @@
|
||||||
"Redirect the user to other url."
|
"Redirect the user to other url."
|
||||||
([name] (go name nil))
|
([name] (go name nil))
|
||||||
([name params] (rs/emit! (navigate name params))))
|
([name params] (rs/emit! (navigate name params))))
|
||||||
|
|
||||||
|
(defn route-for
|
||||||
|
"Given a location handler and optional parameter map, return the URI
|
||||||
|
for such handler and parameters."
|
||||||
|
([location]
|
||||||
|
(bidi/path-for routes location))
|
||||||
|
([location params]
|
||||||
|
(apply bidi/path-for routes location (into []
|
||||||
|
(mapcat (fn [[k v]] [k v]))
|
||||||
|
params))))
|
||||||
|
|
|
@ -1,13 +1,37 @@
|
||||||
(ns uxbox.time
|
(ns uxbox.time
|
||||||
(:require cljsjs.moment))
|
(:require [cljsjs.moment]))
|
||||||
|
|
||||||
|
(defn parse
|
||||||
|
([v]
|
||||||
|
(js/moment v))
|
||||||
|
([v format]
|
||||||
|
(case format
|
||||||
|
:unix (js/moment.unix v)
|
||||||
|
(js/moment v format))))
|
||||||
|
|
||||||
|
(defn iso
|
||||||
|
[v]
|
||||||
|
(.toISOString v))
|
||||||
|
|
||||||
|
(defn unix
|
||||||
|
[v]
|
||||||
|
(.unix v))
|
||||||
|
|
||||||
|
(defn now
|
||||||
|
([]
|
||||||
|
(js/moment))
|
||||||
|
([format]
|
||||||
|
(case format
|
||||||
|
:unix (unix (now))
|
||||||
|
:iso (iso (now)))))
|
||||||
|
|
||||||
(defn ago
|
(defn ago
|
||||||
[time]
|
[time]
|
||||||
(.fromNow (js/moment time)))
|
(.fromNow (parse time)))
|
||||||
|
|
||||||
(defn day
|
(defn day
|
||||||
[time]
|
[time]
|
||||||
(.calendar (js/moment. time)
|
(.calendar (parse time)
|
||||||
nil
|
nil
|
||||||
#js {:sameDay "[Today]"
|
#js {:sameDay "[Today]"
|
||||||
:sameElse "[Today]"
|
:sameElse "[Today]"
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
:main/colors (ui.elements/colors)
|
:main/colors (ui.elements/colors)
|
||||||
:main/page (let [projectid (:project-uuid location-params)
|
:main/page (let [projectid (:project-uuid location-params)
|
||||||
pageid (:page-uuid location-params)]
|
pageid (:page-uuid location-params)]
|
||||||
(rs/emit! (dp/initialize-workspace projectid pageid))
|
|
||||||
(ui.w/workspace projectid pageid))
|
(ui.w/workspace projectid pageid))
|
||||||
nil
|
nil
|
||||||
)])))
|
)])))
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -10,92 +10,143 @@
|
||||||
[uxbox.data.projects :as dp]
|
[uxbox.data.projects :as dp]
|
||||||
[uxbox.ui.icons.dashboard :as icons]
|
[uxbox.ui.icons.dashboard :as icons]
|
||||||
[uxbox.ui.icons :as i]
|
[uxbox.ui.icons :as i]
|
||||||
[uxbox.ui.dom :as dom]
|
|
||||||
[uxbox.ui.header :as ui.h]
|
|
||||||
[uxbox.ui.lightbox :as lightbox]
|
[uxbox.ui.lightbox :as lightbox]
|
||||||
[uxbox.time :refer [ago]]))
|
[uxbox.ui.header :as ui.h]
|
||||||
|
[uxbox.ui.users :as ui.u]
|
||||||
|
[uxbox.ui.navigation :as nav]
|
||||||
|
[uxbox.ui.dom :as dom]))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
(def ^:static project-state
|
||||||
;; Header
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
;; (defn header-render
|
|
||||||
;; [conn page grid? project-bar-visible?]
|
|
||||||
;; (let [{page-title :page/title
|
|
||||||
;; page-width :page/width
|
|
||||||
;; page-height :page/height} page]
|
|
||||||
;; [:header#workspace-bar.workspace-bar
|
|
||||||
;; [:div.main-icon
|
|
||||||
;; (nav/link (nav/route-for :dashboard) icons/logo-icon)]
|
|
||||||
;; (project-tree page-title project-bar-visible?)
|
|
||||||
;; [:div.workspace-options
|
|
||||||
;; [:ul.options-btn
|
|
||||||
;; [:li.tooltip.tooltip-bottom {:alt "Undo (Ctrl + Z)"}
|
|
||||||
;; icons/undo]
|
|
||||||
;; [:li.tooltip.tooltip-bottom {:alt "Redo (Ctrl + Shift + Z)"}
|
|
||||||
;; icons/redo]]
|
|
||||||
;; [:ul.options-btn
|
|
||||||
;; ;; TODO: refactor
|
|
||||||
;; [:li.tooltip.tooltip-bottom
|
|
||||||
;; {:alt "Export (Ctrl + E)"}
|
|
||||||
;; ;; page-title
|
|
||||||
;; [:a {:download (str page-title ".svg")
|
|
||||||
;; :href "#"
|
|
||||||
;; :on-click (fn [e]
|
|
||||||
;; (let [innerHTML (.-innerHTML (.getElementById js/document "page-layout"))
|
|
||||||
;; width page-width
|
|
||||||
;; height page-height
|
|
||||||
;; html (str "<svg width='" width "' height='" height "'>" innerHTML "</svg>")
|
|
||||||
;; data (js/Blob. #js [html] #js {:type "application/octet-stream"})
|
|
||||||
;; url (.createObjectURL (.-URL js/window) data)]
|
|
||||||
;; (set! (.-href (.-currentTarget e)) url)))}
|
|
||||||
;; icons/export]]
|
|
||||||
;; [:li.tooltip.tooltip-bottom
|
|
||||||
;; {:alt "Image (Ctrl + I)"}
|
|
||||||
;; icons/image]]
|
|
||||||
;; [:ul.options-btn
|
|
||||||
;; [:li.tooltip.tooltip-bottom
|
|
||||||
;; {:alt "Ruler (Ctrl + R)"}
|
|
||||||
;; icons/ruler]
|
|
||||||
;; [:li.tooltip.tooltip-bottom
|
|
||||||
;; {:alt "Grid (Ctrl + G)"
|
|
||||||
;; :class (when (rum/react ws/grid?) "selected")
|
|
||||||
;; :on-click ws/toggle-grid!}
|
|
||||||
;; icons/grid]
|
|
||||||
;; [:li.tooltip.tooltip-bottom
|
|
||||||
;; {:alt "Align (Ctrl + A)"}
|
|
||||||
;; icons/alignment]
|
|
||||||
;; [:li.tooltip.tooltip-bottom
|
|
||||||
;; {:alt "Organize (Ctrl + O)"}
|
|
||||||
;; icons/organize]]]
|
|
||||||
;; (user conn)]))
|
|
||||||
|
|
||||||
;; (def header
|
|
||||||
;; (util/component
|
|
||||||
;; {:render header-render
|
|
||||||
;; :name "header"}))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; Header
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(def project-state
|
|
||||||
(as-> (util/dep-in [:projects-by-id] [:workspace :project]) $
|
(as-> (util/dep-in [:projects-by-id] [:workspace :project]) $
|
||||||
(l/focus-atom $ s/state)))
|
(l/focus-atom $ s/state)))
|
||||||
|
|
||||||
(def page-state
|
(def ^:static page-state
|
||||||
(as-> (util/dep-in [:pages-by-id] [:workspace :page]) $
|
(as-> (util/dep-in [:pages-by-id] [:workspace :page]) $
|
||||||
(l/focus-atom $ s/state)))
|
(l/focus-atom $ s/state)))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Header
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defn on-download-clicked
|
||||||
|
[event page]
|
||||||
|
(let [content (.-innerHTML (.getElementById js/document "page-layout"))
|
||||||
|
width (:width page)
|
||||||
|
height (:height page)
|
||||||
|
html (str "<svg width='" width "' height='" height "'>" content "</svg>")
|
||||||
|
data (js/Blob. #js [html] #js {:type "application/octet-stream"})
|
||||||
|
url (.createObjectURL (.-URL js/window) data)]
|
||||||
|
(set! (.-href (.-currentTarget event)) url)))
|
||||||
|
|
||||||
|
(defn header-render
|
||||||
|
[own]
|
||||||
|
(let [page (rum/react page-state)]
|
||||||
|
(html
|
||||||
|
[:header#workspace-bar.workspace-bar
|
||||||
|
[:div.main-icon
|
||||||
|
(nav/link (r/route-for :dashboard) i/logo-icon)]
|
||||||
|
[:div.project-tree-btn
|
||||||
|
{:on-click (constantly nil)}
|
||||||
|
i/project-tree
|
||||||
|
[:span (:name page)]]
|
||||||
|
[:div.workspace-options
|
||||||
|
[:ul.options-btn
|
||||||
|
[:li.tooltip.tooltip-bottom {:alt "Undo (Ctrl + Z)"}
|
||||||
|
i/undo]
|
||||||
|
[:li.tooltip.tooltip-bottom {:alt "Redo (Ctrl + Shift + Z)"}
|
||||||
|
i/redo]]
|
||||||
|
[:ul.options-btn
|
||||||
|
;; TODO: refactor
|
||||||
|
[:li.tooltip.tooltip-bottom
|
||||||
|
{:alt "Export (Ctrl + E)"}
|
||||||
|
;; page-title
|
||||||
|
[:a {:download (str (:name page) ".svg")
|
||||||
|
:href "#" :on-click on-download-clicked}
|
||||||
|
i/export]]
|
||||||
|
[:li.tooltip.tooltip-bottom
|
||||||
|
{:alt "Image (Ctrl + I)"}
|
||||||
|
i/image]]
|
||||||
|
[:ul.options-btn
|
||||||
|
[:li.tooltip.tooltip-bottom
|
||||||
|
{:alt "Ruler (Ctrl + R)"}
|
||||||
|
i/ruler]
|
||||||
|
[:li.tooltip.tooltip-bottom
|
||||||
|
{:alt "Grid (Ctrl + G)"
|
||||||
|
:class (when false "selected")
|
||||||
|
:on-click (constantly nil)}
|
||||||
|
i/grid]
|
||||||
|
[:li.tooltip.tooltip-bottom
|
||||||
|
{:alt "Align (Ctrl + A)"}
|
||||||
|
i/alignment]
|
||||||
|
[:li.tooltip.tooltip-bottom
|
||||||
|
{:alt "Organize (Ctrl + O)"}
|
||||||
|
i/organize]]]
|
||||||
|
(ui.u/user)])))
|
||||||
|
|
||||||
|
(def header
|
||||||
|
(util/component
|
||||||
|
{:render header-render
|
||||||
|
:name "header"
|
||||||
|
:mixins [rum/reactive]}))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Toolbar
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(def ^:static toolbar-state
|
||||||
|
(as-> (l/in [:workspace :toolbars]) $
|
||||||
|
(l/focus-atom $ s/state)))
|
||||||
|
|
||||||
|
(defn- toggle-toolbox
|
||||||
|
[state item]
|
||||||
|
(update state item (fnil not false)))
|
||||||
|
|
||||||
|
(defn toolbar-render
|
||||||
|
[own]
|
||||||
|
(let [state (rum/react toolbar-state)]
|
||||||
|
(html
|
||||||
|
[:div#tool-bar.tool-bar
|
||||||
|
[:div.tool-bar-inside
|
||||||
|
[:ul.main-tools
|
||||||
|
[:li.tooltip
|
||||||
|
{:alt "Shapes (Ctrl + Shift + F)"
|
||||||
|
:class (when (:tools state) "current")
|
||||||
|
:on-click #(swap! toolbar-state toggle-toolbox :tools)}
|
||||||
|
i/shapes]
|
||||||
|
[:li.tooltip
|
||||||
|
{:alt "Icons (Ctrl + Shift + I)"
|
||||||
|
:class (when (:icons state) "current")
|
||||||
|
:on-click #(swap! toolbar-state toggle-toolbox :icons)}
|
||||||
|
i/icon-set]
|
||||||
|
[:li.tooltip
|
||||||
|
{:alt "Elements (Ctrl + Shift + L)"
|
||||||
|
:class (when (:layers state)
|
||||||
|
"current")
|
||||||
|
:on-click #(swap! toolbar-state toggle-toolbox :layers)}
|
||||||
|
i/layers]
|
||||||
|
[:li.tooltip
|
||||||
|
{:alt "Feedback (Ctrl + Shift + M)"}
|
||||||
|
i/chat]]]])))
|
||||||
|
|
||||||
|
(def ^:static toolbar
|
||||||
|
(util/component
|
||||||
|
{:render toolbar-render
|
||||||
|
:name "toolbar"
|
||||||
|
:mixins [rum/reactive]}))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Header
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(defn workspace-render
|
(defn workspace-render
|
||||||
[own projectid]
|
[own projectid]
|
||||||
(html
|
(html
|
||||||
[:div "hello world"
|
[:div
|
||||||
;; (header conn page ws/grid? project-bar-visible?)
|
(header)
|
||||||
;; [:main.main-content
|
[:main.main-content
|
||||||
;; [:section.workspace-content
|
[:section.workspace-content
|
||||||
;; ;; Toolbar
|
;; Toolbar
|
||||||
;; (toolbar open-toolboxes)
|
(toolbar)
|
||||||
;; ;; Project bar
|
;; ;; Project bar
|
||||||
;; (project-bar conn project page pages @project-bar-visible?)
|
;; (project-bar conn project page pages @project-bar-visible?)
|
||||||
;; ;; Rules
|
;; ;; Rules
|
||||||
|
@ -106,11 +157,19 @@
|
||||||
;; ;; Aside
|
;; ;; Aside
|
||||||
;; (when-not (empty? @open-toolboxes)
|
;; (when-not (empty? @open-toolboxes)
|
||||||
;; (aside conn open-toolboxes page shapes))
|
;; (aside conn open-toolboxes page shapes))
|
||||||
]))
|
]]]))
|
||||||
|
|
||||||
|
(defn workspace-will-mount
|
||||||
|
[own]
|
||||||
|
(let [[projectid pageid] (:rum/props own)]
|
||||||
|
(println "workspace-will-mount" projectid pageid)
|
||||||
|
(rs/emit! (dp/initialize-workspace projectid pageid))
|
||||||
|
own))
|
||||||
|
|
||||||
(def ^:static workspace
|
(def ^:static workspace
|
||||||
(util/component
|
(util/component
|
||||||
{:render workspace-render
|
{:render workspace-render
|
||||||
|
:will-mount workspace-will-mount
|
||||||
:name "workspace"
|
:name "workspace"
|
||||||
:mixins [rum/static]}))
|
:mixins [rum/static]}))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue