mirror of
https://github.com/penpot/penpot.git
synced 2025-08-06 14:18:24 +02:00
WIP:
This commit is contained in:
parent
ede2d4becd
commit
62c5c8d5c8
8 changed files with 500 additions and 295 deletions
|
@ -11,10 +11,9 @@
|
||||||
|
|
||||||
(enable-console-print!)
|
(enable-console-print!)
|
||||||
|
|
||||||
(let [dom (dom/getElement "app")]
|
|
||||||
(ui/mount! dom))
|
|
||||||
|
|
||||||
(defonce +setup+
|
(defonce +setup+
|
||||||
(do
|
(do
|
||||||
|
(println "BOOTSTRAP")
|
||||||
|
(ui/init)
|
||||||
(rs/emit! (dl/load-data))
|
(rs/emit! (dl/load-data))
|
||||||
(rx/on-value s/stream #(dl/persist-state %))))
|
(rx/on-value s/stream #(dl/persist-state %))))
|
||||||
|
|
|
@ -21,5 +21,3 @@
|
||||||
IPrintWithWriter
|
IPrintWithWriter
|
||||||
(-pr-writer [mv writer _]
|
(-pr-writer [mv writer _]
|
||||||
(-write writer "#<event:u.s.p/toggle-pagebar>"))))
|
(-write writer "#<event:u.s.p/toggle-pagebar>"))))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
(ns uxbox.ui
|
(ns uxbox.ui
|
||||||
(:require [sablono.core :as html :refer-macros [html]]
|
(:require [sablono.core :as html :refer-macros [html]]
|
||||||
|
[goog.dom :as gdom]
|
||||||
[rum.core :as rum]
|
[rum.core :as rum]
|
||||||
[cats.labs.lens :as l]
|
[cats.labs.lens :as l]
|
||||||
[uxbox.state :as s]
|
[uxbox.state :as s]
|
||||||
|
@ -19,25 +20,25 @@
|
||||||
(defn app-render
|
(defn app-render
|
||||||
[own]
|
[own]
|
||||||
(let [{:keys [location location-params] :as state} (rum/react state)]
|
(let [{:keys [location location-params] :as state} (rum/react state)]
|
||||||
(html
|
(case location
|
||||||
[:section
|
:auth/login (ui.users/login)
|
||||||
(ui.lb/lightbox)
|
:dashboard/projects (ui.dashboard.projects/projects)
|
||||||
(case location
|
:dashboard/elements (ui.dashboard.elements/elements)
|
||||||
:auth/login (ui.users/login)
|
:dashboard/icons (ui.dashboard.elements/icons)
|
||||||
:dashboard/projects (ui.dashboard.projects/projects)
|
:dashboard/colors (ui.dashboard.elements/colors)
|
||||||
:dashboard/elements (ui.dashboard.elements/elements)
|
:workspace/page (let [projectid (:project-uuid location-params)
|
||||||
:dashboard/icons (ui.dashboard.elements/icons)
|
pageid (:page-uuid location-params)]
|
||||||
:dashboard/colors (ui.dashboard.elements/colors)
|
(ui.w/workspace projectid pageid))
|
||||||
:workspace/page (let [projectid (:project-uuid location-params)
|
nil
|
||||||
pageid (:page-uuid location-params)]
|
)))
|
||||||
(ui.w/workspace projectid pageid))
|
|
||||||
nil
|
|
||||||
)])))
|
|
||||||
|
|
||||||
(def app
|
(def app
|
||||||
(util/component {:render app-render
|
(util/component {:render app-render
|
||||||
:mixins [rum/reactive]
|
:mixins [rum/reactive]
|
||||||
:name "app"}))
|
:name "app"}))
|
||||||
(defn mount!
|
(defn init
|
||||||
[el]
|
[]
|
||||||
(rum/mount (app) el))
|
(let [app-dom (gdom/getElement "app")
|
||||||
|
lb-dom (gdom/getElement "lightbox")]
|
||||||
|
(rum/mount (app) app-dom)
|
||||||
|
(rum/mount (ui.lb/lightbox) lb-dom)))
|
||||||
|
|
|
@ -1,271 +1,14 @@
|
||||||
(ns uxbox.ui.workspace
|
(ns uxbox.ui.workspace
|
||||||
(:require [sablono.core :as html :refer-macros [html]]
|
(:require [sablono.core :as html :refer-macros [html]]
|
||||||
[rum.core :as rum]
|
[rum.core :as rum]
|
||||||
[cats.labs.lens :as l]
|
|
||||||
[cuerdas.core :as str]
|
|
||||||
[uxbox.util :as util]
|
[uxbox.util :as util]
|
||||||
[uxbox.router :as r]
|
[uxbox.router :as r]
|
||||||
[uxbox.rstore :as rs]
|
[uxbox.rstore :as rs]
|
||||||
[uxbox.state :as s]
|
[uxbox.state :as s]
|
||||||
[uxbox.data.projects :as dp]
|
[uxbox.data.projects :as dp]
|
||||||
[uxbox.data.workspace :as dw]
|
[uxbox.ui.workspace.base :as wb]
|
||||||
[uxbox.ui.icons.dashboard :as icons]
|
[uxbox.ui.workspace.rules :as wr]
|
||||||
[uxbox.ui.icons :as i]
|
[uxbox.ui.workspace.workarea :as wa]))
|
||||||
[uxbox.ui.lightbox :as lightbox]
|
|
||||||
[uxbox.ui.keyboard :as k]
|
|
||||||
[uxbox.ui.users :as ui.u]
|
|
||||||
[uxbox.ui.navigation :as nav]
|
|
||||||
[uxbox.ui.dom :as dom]))
|
|
||||||
|
|
||||||
(def ^:static project-state
|
|
||||||
(as-> (util/dep-in [:projects-by-id] [:workspace :project]) $
|
|
||||||
(l/focus-atom $ s/state)))
|
|
||||||
|
|
||||||
(def ^:static page-state
|
|
||||||
(as-> (util/dep-in [:pages-by-id] [:workspace :page]) $
|
|
||||||
(l/focus-atom $ s/state)))
|
|
||||||
|
|
||||||
(def ^:static pages-state
|
|
||||||
(as-> (util/getter #(let [pid (get-in % [:workspace :project])]
|
|
||||||
(dp/project-pages % pid))) $
|
|
||||||
(l/focus-atom $ s/state)))
|
|
||||||
|
|
||||||
(def ^:static workspace-state
|
|
||||||
(as-> (l/in [:workspace]) $
|
|
||||||
(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)
|
|
||||||
toggle #(rs/emit! (dw/toggle-pagesbar))]
|
|
||||||
(html
|
|
||||||
[:header#workspace-bar.workspace-bar
|
|
||||||
[:div.main-icon
|
|
||||||
(nav/link (r/route-for :dashboard/projects) i/logo-icon)]
|
|
||||||
[:div.project-tree-btn
|
|
||||||
{:on-click toggle}
|
|
||||||
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 "workspace-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]}))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; Project Bar
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(defn- project-sidebar-pageitem-render
|
|
||||||
[own parent page numpages]
|
|
||||||
(letfn [(on-edit [e]
|
|
||||||
(let [data {:edit true :form page}]
|
|
||||||
(reset! parent data)))]
|
|
||||||
(let [curpage (rum/react page-state)
|
|
||||||
active? (= (:id curpage) (:id page))
|
|
||||||
deletable? (> numpages 1)
|
|
||||||
navigate #(rs/emit! (dp/go-to-project (:project page) (:id page)))
|
|
||||||
delete #(rs/emit! (dp/delete-page (:id page)))]
|
|
||||||
(html
|
|
||||||
[:li.single-page
|
|
||||||
{:class (when active? "current")
|
|
||||||
:on-click navigate}
|
|
||||||
[:div.tree-icon i/page]
|
|
||||||
[:span (:name page)]
|
|
||||||
[:div.options
|
|
||||||
[:div {:on-click on-edit} i/pencil]
|
|
||||||
[:div {:class (when-not deletable? "hide")
|
|
||||||
:on-click delete}
|
|
||||||
i/trash]]]))))
|
|
||||||
|
|
||||||
(def project-sidebar-pageitem
|
|
||||||
(util/component
|
|
||||||
{:render project-sidebar-pageitem-render
|
|
||||||
:name "project-sidebar-pageitem"
|
|
||||||
:mixins [rum/reactive]}))
|
|
||||||
|
|
||||||
(defn- project-sidebar-pagelist-render
|
|
||||||
[own parent]
|
|
||||||
(let [project (rum/react project-state)
|
|
||||||
pages (rum/react pages-state)
|
|
||||||
name (:name project)]
|
|
||||||
(html
|
|
||||||
[:div.project-bar-inside
|
|
||||||
[:span.project-name name]
|
|
||||||
[:ul.tree-view
|
|
||||||
(for [page pages]
|
|
||||||
(let [component (project-sidebar-pageitem parent page (count pages))]
|
|
||||||
(rum/with-key component (str (:id page)))))]
|
|
||||||
[:button.btn-primary.btn-small
|
|
||||||
{:on-click #(reset! parent {:edit true :form {}})}
|
|
||||||
"+ Add new page"]])))
|
|
||||||
|
|
||||||
(def project-sidebar-pagelist
|
|
||||||
(util/component
|
|
||||||
{:render project-sidebar-pagelist-render
|
|
||||||
:name "project-sidebar-pagelist"
|
|
||||||
:mixins [rum/reactive]}))
|
|
||||||
|
|
||||||
(defn- project-sidebar-form-render
|
|
||||||
[own parent]
|
|
||||||
(let [form (:form @parent)
|
|
||||||
project @project-state]
|
|
||||||
(letfn [(on-change [e]
|
|
||||||
(let [value (dom/event->value e)]
|
|
||||||
(swap! parent assoc-in [:form :name] value)))
|
|
||||||
(persist []
|
|
||||||
(if (nil? (:id form))
|
|
||||||
(let [data {:project (:id project)
|
|
||||||
:width (:width project)
|
|
||||||
:height (:height project)
|
|
||||||
:name (:name form)}]
|
|
||||||
(rs/emit! (dp/create-page data))
|
|
||||||
(reset! parent {:edit false}))
|
|
||||||
(do
|
|
||||||
(rs/emit! (dp/update-page-name (:id form) (:name form)))
|
|
||||||
(reset! parent {:edit false}))))
|
|
||||||
(on-save [e]
|
|
||||||
(persist))
|
|
||||||
(on-key-up [e]
|
|
||||||
(when (k/enter? e)
|
|
||||||
(persist)))
|
|
||||||
(on-cancel [e]
|
|
||||||
(reset! parent {:edit false}))]
|
|
||||||
(html
|
|
||||||
[:div.project-bar-inside
|
|
||||||
[:input.input-text
|
|
||||||
{:name "test"
|
|
||||||
:auto-focus true
|
|
||||||
:placeholder "Page title"
|
|
||||||
:type "text"
|
|
||||||
:value (get-in @parent [:form :name] "")
|
|
||||||
:on-change on-change
|
|
||||||
:on-key-up on-key-up}]
|
|
||||||
[:button.btn-primary.btn-small
|
|
||||||
{:disabled (str/empty? (str/trim (get-in @parent [:form :name] "")))
|
|
||||||
:on-click on-save}
|
|
||||||
"Save"]
|
|
||||||
[:button.btn-primary.btn-small
|
|
||||||
{:on-click on-cancel}
|
|
||||||
"Cancel"]]))))
|
|
||||||
|
|
||||||
(def project-sidebar-form
|
|
||||||
(util/component
|
|
||||||
{:render project-sidebar-form-render
|
|
||||||
:name "project-sidebar-form"
|
|
||||||
:mixins [rum/reactive]}))
|
|
||||||
|
|
||||||
(defn project-sidebar-render
|
|
||||||
[own]
|
|
||||||
(let [local (:rum/local own)
|
|
||||||
workspace (rum/react workspace-state)
|
|
||||||
project (rum/react project-state)]
|
|
||||||
(html
|
|
||||||
[:div#project-bar.project-bar
|
|
||||||
(when-not (:visible-pagebar workspace false)
|
|
||||||
{:class "toggle"})
|
|
||||||
(if (:edit @local)
|
|
||||||
(project-sidebar-form local)
|
|
||||||
(project-sidebar-pagelist local))])))
|
|
||||||
|
|
||||||
(def project-sidebar
|
|
||||||
(util/component
|
|
||||||
{:render project-sidebar-render
|
|
||||||
:name "project-sidebar"
|
|
||||||
:mixins [rum/reactive (rum/local {:edit false :form {}})]}))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Workspace
|
;; Workspace
|
||||||
|
@ -275,18 +18,20 @@
|
||||||
[own projectid]
|
[own projectid]
|
||||||
(html
|
(html
|
||||||
[:div
|
[:div
|
||||||
(header)
|
(wb/header)
|
||||||
[:main.main-content
|
[:main.main-content
|
||||||
[:section.workspace-content
|
[:section.workspace-content
|
||||||
;; Toolbar
|
;; Toolbar
|
||||||
(toolbar)
|
(wb/toolbar)
|
||||||
;; Project bar
|
;; Project bar
|
||||||
(project-sidebar)
|
(wb/project-sidebar)
|
||||||
;; ;; Rules
|
;; Rules
|
||||||
;; (horizontal-rule (rum/react ws/zoom))
|
(wr/h-rule)
|
||||||
;; (vertical-rule (rum/react ws/zoom))
|
(wr/v-rule)
|
||||||
;; ;; Working area
|
|
||||||
;; (working-area conn @open-toolboxes page project shapes (rum/react ws/zoom) (rum/react ws/grid?))
|
;; Canvas
|
||||||
|
(wa/working-area)
|
||||||
|
;; (working-area conn @open-toolboxes page project shapes (rum/react ws/zoom) (rum/react ws/grid?))
|
||||||
;; ;; Aside
|
;; ;; Aside
|
||||||
;; (when-not (empty? @open-toolboxes)
|
;; (when-not (empty? @open-toolboxes)
|
||||||
;; (aside conn open-toolboxes page shapes))
|
;; (aside conn open-toolboxes page shapes))
|
||||||
|
@ -295,15 +40,12 @@
|
||||||
(defn workspace-will-mount
|
(defn workspace-will-mount
|
||||||
[own]
|
[own]
|
||||||
(let [[projectid pageid] (:rum/props own)]
|
(let [[projectid pageid] (:rum/props own)]
|
||||||
(println "workspace-will-mount" projectid pageid)
|
|
||||||
(rs/emit! (dp/initialize-workspace projectid pageid))
|
(rs/emit! (dp/initialize-workspace projectid pageid))
|
||||||
own))
|
own))
|
||||||
|
|
||||||
(defn workspace-transfer-state
|
(defn workspace-transfer-state
|
||||||
[old-state state]
|
[old-state state]
|
||||||
(let [[projectid pageid] (:rum/props state)]
|
(let [[projectid pageid] (:rum/props state)]
|
||||||
(println "workspace-transfer-state" old-state)
|
|
||||||
(println "workspace-transfer-state" state)
|
|
||||||
(rs/emit! (dp/initialize-workspace projectid pageid))))
|
(rs/emit! (dp/initialize-workspace projectid pageid))))
|
||||||
|
|
||||||
(def ^:static workspace
|
(def ^:static workspace
|
||||||
|
|
285
frontend/uxbox/ui/workspace/base.cljs
Normal file
285
frontend/uxbox/ui/workspace/base.cljs
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
(ns uxbox.ui.workspace.base
|
||||||
|
(:require [sablono.core :as html :refer-macros [html]]
|
||||||
|
[rum.core :as rum]
|
||||||
|
[beicon.core :as rx]
|
||||||
|
[cats.labs.lens :as l]
|
||||||
|
[cuerdas.core :as str]
|
||||||
|
[uxbox.util :as util]
|
||||||
|
[uxbox.router :as r]
|
||||||
|
[uxbox.rstore :as rs]
|
||||||
|
[uxbox.state :as s]
|
||||||
|
[uxbox.data.projects :as dp]
|
||||||
|
[uxbox.data.workspace :as dw]
|
||||||
|
[uxbox.ui.icons.dashboard :as icons]
|
||||||
|
[uxbox.ui.icons :as i]
|
||||||
|
[uxbox.ui.lightbox :as lightbox]
|
||||||
|
[uxbox.ui.keyboard :as k]
|
||||||
|
[uxbox.ui.users :as ui.u]
|
||||||
|
[uxbox.ui.navigation :as nav]
|
||||||
|
[uxbox.ui.dom :as dom]))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Lenses
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(def ^:static project-state
|
||||||
|
(as-> (util/dep-in [:projects-by-id] [:workspace :project]) $
|
||||||
|
(l/focus-atom $ s/state)))
|
||||||
|
|
||||||
|
(def ^:static page-state
|
||||||
|
(as-> (util/dep-in [:pages-by-id] [:workspace :page]) $
|
||||||
|
(l/focus-atom $ s/state)))
|
||||||
|
|
||||||
|
(def ^:static pages-state
|
||||||
|
(as-> (util/getter #(let [pid (get-in % [:workspace :project])]
|
||||||
|
(dp/project-pages % pid))) $
|
||||||
|
(l/focus-atom $ s/state)))
|
||||||
|
|
||||||
|
(def ^:static workspace-state
|
||||||
|
(as-> (l/in [:workspace]) $
|
||||||
|
(l/focus-atom $ s/state)))
|
||||||
|
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Streams
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defonce scroll-bus (rx/bus))
|
||||||
|
(defonce top-scroll-s (rx/dedupe (rx/map :top scroll-bus)))
|
||||||
|
(defonce left-scroll-s (rx/dedupe (rx/map :left scroll-bus)))
|
||||||
|
|
||||||
|
(defonce top-scroll (rx/to-atom top-scroll-s))
|
||||||
|
(defonce left-scroll (rx/to-atom left-scroll-s))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; 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)
|
||||||
|
toggle #(rs/emit! (dw/toggle-pagesbar))]
|
||||||
|
(html
|
||||||
|
[:header#workspace-bar.workspace-bar
|
||||||
|
[:div.main-icon
|
||||||
|
(nav/link (r/route-for :dashboard/projects) i/logo-icon)]
|
||||||
|
[:div.project-tree-btn
|
||||||
|
{:on-click toggle}
|
||||||
|
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 "workspace-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]}))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Project Bar
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defn- project-sidebar-pageitem-render
|
||||||
|
[own parent page numpages]
|
||||||
|
(letfn [(on-edit [e]
|
||||||
|
(let [data {:edit true :form page}]
|
||||||
|
(reset! parent data)))]
|
||||||
|
(let [curpage (rum/react page-state)
|
||||||
|
active? (= (:id curpage) (:id page))
|
||||||
|
deletable? (> numpages 1)
|
||||||
|
navigate #(rs/emit! (dp/go-to-project (:project page) (:id page)))
|
||||||
|
delete #(rs/emit! (dp/delete-page (:id page)))]
|
||||||
|
(html
|
||||||
|
[:li.single-page
|
||||||
|
{:class (when active? "current")
|
||||||
|
:on-click navigate}
|
||||||
|
[:div.tree-icon i/page]
|
||||||
|
[:span (:name page)]
|
||||||
|
[:div.options
|
||||||
|
[:div {:on-click on-edit} i/pencil]
|
||||||
|
[:div {:class (when-not deletable? "hide")
|
||||||
|
:on-click delete}
|
||||||
|
i/trash]]]))))
|
||||||
|
|
||||||
|
(def project-sidebar-pageitem
|
||||||
|
(util/component
|
||||||
|
{:render project-sidebar-pageitem-render
|
||||||
|
:name "project-sidebar-pageitem"
|
||||||
|
:mixins [rum/reactive]}))
|
||||||
|
|
||||||
|
(defn- project-sidebar-pagelist-render
|
||||||
|
[own parent]
|
||||||
|
(let [project (rum/react project-state)
|
||||||
|
pages (rum/react pages-state)
|
||||||
|
name (:name project)]
|
||||||
|
(html
|
||||||
|
[:div.project-bar-inside
|
||||||
|
[:span.project-name name]
|
||||||
|
[:ul.tree-view
|
||||||
|
(for [page pages]
|
||||||
|
(let [component (project-sidebar-pageitem parent page (count pages))]
|
||||||
|
(rum/with-key component (str (:id page)))))]
|
||||||
|
[:button.btn-primary.btn-small
|
||||||
|
{:on-click #(reset! parent {:edit true :form {}})}
|
||||||
|
"+ Add new page"]])))
|
||||||
|
|
||||||
|
(def project-sidebar-pagelist
|
||||||
|
(util/component
|
||||||
|
{:render project-sidebar-pagelist-render
|
||||||
|
:name "project-sidebar-pagelist"
|
||||||
|
:mixins [rum/reactive]}))
|
||||||
|
|
||||||
|
(defn- project-sidebar-form-render
|
||||||
|
[own parent]
|
||||||
|
(let [form (:form @parent)
|
||||||
|
project @project-state]
|
||||||
|
(letfn [(on-change [e]
|
||||||
|
(let [value (dom/event->value e)]
|
||||||
|
(swap! parent assoc-in [:form :name] value)))
|
||||||
|
(persist []
|
||||||
|
(if (nil? (:id form))
|
||||||
|
(let [data {:project (:id project)
|
||||||
|
:width (:width project)
|
||||||
|
:height (:height project)
|
||||||
|
:name (:name form)}]
|
||||||
|
(rs/emit! (dp/create-page data))
|
||||||
|
(reset! parent {:edit false}))
|
||||||
|
(do
|
||||||
|
(rs/emit! (dp/update-page-name (:id form) (:name form)))
|
||||||
|
(reset! parent {:edit false}))))
|
||||||
|
(on-save [e]
|
||||||
|
(persist))
|
||||||
|
(on-key-up [e]
|
||||||
|
(when (k/enter? e)
|
||||||
|
(persist)))
|
||||||
|
(on-cancel [e]
|
||||||
|
(reset! parent {:edit false}))]
|
||||||
|
(html
|
||||||
|
[:div.project-bar-inside
|
||||||
|
[:input.input-text
|
||||||
|
{:name "test"
|
||||||
|
:auto-focus true
|
||||||
|
:placeholder "Page title"
|
||||||
|
:type "text"
|
||||||
|
:value (get-in @parent [:form :name] "")
|
||||||
|
:on-change on-change
|
||||||
|
:on-key-up on-key-up}]
|
||||||
|
[:button.btn-primary.btn-small
|
||||||
|
{:disabled (str/empty? (str/trim (get-in @parent [:form :name] "")))
|
||||||
|
:on-click on-save}
|
||||||
|
"Save"]
|
||||||
|
[:button.btn-primary.btn-small
|
||||||
|
{:on-click on-cancel}
|
||||||
|
"Cancel"]]))))
|
||||||
|
|
||||||
|
(def project-sidebar-form
|
||||||
|
(util/component
|
||||||
|
{:render project-sidebar-form-render
|
||||||
|
:name "project-sidebar-form"
|
||||||
|
:mixins [rum/reactive]}))
|
||||||
|
|
||||||
|
(defn project-sidebar-render
|
||||||
|
[own]
|
||||||
|
(let [local (:rum/local own)
|
||||||
|
workspace (rum/react workspace-state)
|
||||||
|
project (rum/react project-state)]
|
||||||
|
(html
|
||||||
|
[:div#project-bar.project-bar
|
||||||
|
(when-not (:visible-pagebar workspace false)
|
||||||
|
{:class "toggle"})
|
||||||
|
(if (:edit @local)
|
||||||
|
(project-sidebar-form local)
|
||||||
|
(project-sidebar-pagelist local))])))
|
||||||
|
|
||||||
|
(def project-sidebar
|
||||||
|
(util/component
|
||||||
|
{:render project-sidebar-render
|
||||||
|
:name "project-sidebar"
|
||||||
|
:mixins [rum/reactive (rum/local {:edit false :form {}})]}))
|
104
frontend/uxbox/ui/workspace/rules.cljs
Normal file
104
frontend/uxbox/ui/workspace/rules.cljs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
(ns uxbox.ui.workspace.rules
|
||||||
|
(:require [sablono.core :as html :refer-macros [html]]
|
||||||
|
[rum.core :as rum]
|
||||||
|
[cuerdas.core :as str]
|
||||||
|
[beicon.core :as rx]
|
||||||
|
[uxbox.util :as util]
|
||||||
|
[uxbox.state :as s]
|
||||||
|
[uxbox.ui.dom :as dom]
|
||||||
|
[uxbox.ui.workspace.base :as wd]))
|
||||||
|
|
||||||
|
(def viewport-height 3000)
|
||||||
|
(def viewport-width 3000)
|
||||||
|
|
||||||
|
(def document-start-x 50)
|
||||||
|
(def document-start-y 50)
|
||||||
|
|
||||||
|
(defn h-rule-render
|
||||||
|
[zoom]
|
||||||
|
(let [left (rum/react wd/left-scroll)
|
||||||
|
width viewport-width
|
||||||
|
start-width document-start-x
|
||||||
|
padding 20
|
||||||
|
zoom 1
|
||||||
|
big-ticks-mod (/ 100 zoom)
|
||||||
|
mid-ticks-mod (/ 50 zoom)
|
||||||
|
step-size 10
|
||||||
|
ticks (concat (range (- padding start-width) 0 step-size)
|
||||||
|
(range 0 (- width start-width padding) step-size))
|
||||||
|
lines (fn [position value padding]
|
||||||
|
(cond
|
||||||
|
(< (mod value big-ticks-mod) step-size)
|
||||||
|
(html
|
||||||
|
[:g
|
||||||
|
[:line {:x1 position :x2 position :y1 5 :y2 padding :stroke "#7f7f7f"}]
|
||||||
|
[:text {:x (+ position 2) :y 13 :fill "#7f7f7f" :style {:font-size "12px"}} value]])
|
||||||
|
(< (mod value mid-ticks-mod) step-size)
|
||||||
|
(html
|
||||||
|
[:line {:key position :x1 position :x2 position :y1 10 :y2 padding :stroke "#7f7f7f"}])
|
||||||
|
:else
|
||||||
|
(html
|
||||||
|
[:line {:key position :x1 position :x2 position :y1 15 :y2 padding :stroke "#7f7f7f"}])))]
|
||||||
|
(html
|
||||||
|
[:svg.horizontal-rule
|
||||||
|
{:width 3000
|
||||||
|
:height 3000
|
||||||
|
:style {:left (str (- (- left 50)) "px")}}
|
||||||
|
[:g
|
||||||
|
[:rect {:x padding :y 0 :width width :height padding :fill "#bab7b7"}]
|
||||||
|
[:rect {:x 0 :y 0 :width padding :height padding :fill "#bab7b7"}]]
|
||||||
|
[:g
|
||||||
|
(for [tick ticks]
|
||||||
|
(let [position (* (+ tick start-width) zoom)]
|
||||||
|
(rum/with-key (lines position tick padding) (str tick))))]])))
|
||||||
|
|
||||||
|
(def h-rule
|
||||||
|
(util/component
|
||||||
|
{:render h-rule-render
|
||||||
|
:name "h-rule"
|
||||||
|
:mixins [rum/reactive]}))
|
||||||
|
|
||||||
|
|
||||||
|
(defn v-rule-render
|
||||||
|
[zoom]
|
||||||
|
(let [height viewport-height
|
||||||
|
start-height document-start-y
|
||||||
|
top (rum/react wd/top-scroll)
|
||||||
|
zoom 1
|
||||||
|
padding 20
|
||||||
|
big-ticks-mod (/ 100 zoom)
|
||||||
|
mid-ticks-mod (/ 50 zoom)
|
||||||
|
step-size 10
|
||||||
|
ticks (concat (range (- padding start-height) 0 step-size)
|
||||||
|
(range 0 (- height start-height padding) step-size))
|
||||||
|
lines (fn [position value padding]
|
||||||
|
(cond
|
||||||
|
(< (mod value big-ticks-mod) step-size)
|
||||||
|
(html
|
||||||
|
[:g {:key position}
|
||||||
|
[:line {:y1 position :y2 position :x1 5 :x2 padding :stroke "#7f7f7f"}]
|
||||||
|
[:text {:y position :x 5 :transform (str/format "rotate(90 0 %s)" position) :fill "#7f7f7f" :style {:font-size "12px"}} value]])
|
||||||
|
|
||||||
|
(< (mod value mid-ticks-mod) step-size)
|
||||||
|
(html
|
||||||
|
[:line {:key position :y1 position :y2 position :x1 10 :x2 padding :stroke "#7f7f7f"}])
|
||||||
|
|
||||||
|
:else
|
||||||
|
(html
|
||||||
|
[:line {:key position :y1 position :y2 position :x1 15 :x2 padding :stroke "#7f7f7f"}])))]
|
||||||
|
(html
|
||||||
|
[:svg.vertical-rule
|
||||||
|
{:width 3000
|
||||||
|
:height 3000
|
||||||
|
:style {:top (str (- top) "px")}}
|
||||||
|
[:g
|
||||||
|
[:rect {:x 0 :y padding :height height :width padding :fill "#bab7b7"}]
|
||||||
|
(for [tick ticks]
|
||||||
|
(let [position (* (+ tick start-height) zoom)]
|
||||||
|
(rum/with-key (lines position tick padding) (str tick))))]])))
|
||||||
|
|
||||||
|
(def v-rule
|
||||||
|
(util/component
|
||||||
|
{:render v-rule-render
|
||||||
|
:name "v-rule"
|
||||||
|
:mixins [rum/reactive]}))
|
76
frontend/uxbox/ui/workspace/workarea.cljs
Normal file
76
frontend/uxbox/ui/workspace/workarea.cljs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
(ns uxbox.ui.workspace.workarea
|
||||||
|
(:require [sablono.core :as html :refer-macros [html]]
|
||||||
|
[rum.core :as rum]
|
||||||
|
[uxbox.util :as util]
|
||||||
|
[uxbox.router :as r]
|
||||||
|
[uxbox.rstore :as rs]
|
||||||
|
[uxbox.state :as s]
|
||||||
|
[uxbox.data.projects :as dp]
|
||||||
|
[uxbox.ui.workspace.base :as wb]
|
||||||
|
[uxbox.ui.workspace.rules :as wr]))
|
||||||
|
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Coordinates Debug
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defonce canvas-coordinates (atom {}))
|
||||||
|
|
||||||
|
(defn coordenates-render
|
||||||
|
[]
|
||||||
|
(let [[x y] (rum/react canvas-coordinates)]
|
||||||
|
(html
|
||||||
|
[:div
|
||||||
|
{:style {:position "absolute" :left "80px" :top "20px"}}
|
||||||
|
[:table
|
||||||
|
[:tr
|
||||||
|
[:td "X:"]
|
||||||
|
[:td (or x 1)]]
|
||||||
|
[:tr
|
||||||
|
[:td "Y:"]
|
||||||
|
[:td y]]]])))
|
||||||
|
|
||||||
|
(def coordinates
|
||||||
|
(util/component
|
||||||
|
{:render coordenates-render
|
||||||
|
:name "coordenates"
|
||||||
|
:mixins [rum/reactive]}))
|
||||||
|
|
||||||
|
(defn background-render
|
||||||
|
[]
|
||||||
|
(html
|
||||||
|
[:rect
|
||||||
|
{:x 0 :y 0 :width "100%" :height "100%" :fill "white"}]))
|
||||||
|
|
||||||
|
(def background
|
||||||
|
(util/component
|
||||||
|
{:render background-render
|
||||||
|
:name "background"
|
||||||
|
:mixins [rum/static]}))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Work Area
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defn working-area-render
|
||||||
|
[own]
|
||||||
|
(html
|
||||||
|
[:section.workspace-canvas
|
||||||
|
#_{:class (when (empty? open-setting-boxes)
|
||||||
|
"no-tool-bar")
|
||||||
|
:on-scroll (constantly nil)}
|
||||||
|
#_(when (:selected page)
|
||||||
|
(element-options conn
|
||||||
|
page-cursor
|
||||||
|
project-cursor
|
||||||
|
zoom-cursor
|
||||||
|
shapes-cursor))
|
||||||
|
(coordinates)
|
||||||
|
#_(viewport conn page shapes zoom grid?)]))
|
||||||
|
|
||||||
|
(def working-area
|
||||||
|
(util/component
|
||||||
|
{:render working-area-render
|
||||||
|
:name "working-area"
|
||||||
|
:mixins []}))
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
<link href="/css/main.css" rel="stylesheet" type="text/css">
|
<link href="/css/main.css" rel="stylesheet" type="text/css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<section id="app" tabindex="1">
|
<section id="app" tabindex="1"></section>
|
||||||
</section>
|
<section id="lightbox" tabindex="2"></section>
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue