Skip to content

Persist enhancements #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jul 3, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions deps.edn
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{:paths ["src"]
:deps {akiroz.re-frame/storage {:mvn/version "0.1.4"}
binaryage/devtools {:mvn/version "1.0.7"}
:deps {binaryage/devtools {:mvn/version "1.0.7"}
camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.3"}
clj-kdtree/clj-kdtree {:git/url "https://github.com/abscondment/clj-kdtree.git"
:sha "5ec321c5e8006db00fa8b45a8ed9eb0b8f3dd56d"
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -104,6 +104,7 @@
"karma-cljs-test": "0.1.0",
"karma-electron": "7.3.0",
"karma-junit-reporter": "2.0.1",
"localforage": "1.10.0",
"mdn-data": "2.21.0",
"npm-run-all": "4.1.5",
"opentype.js": "1.3.4",
2 changes: 0 additions & 2 deletions src/config.cljs
Original file line number Diff line number Diff line change
@@ -6,8 +6,6 @@

(def ext "rps")

(def app-key :repath-studio)

(def app-name "Repath Studio")

(def mime-type "application/x-repath-studio")
45 changes: 20 additions & 25 deletions src/electron/main.cljs
Original file line number Diff line number Diff line change
@@ -44,11 +44,11 @@
(when (and (secure-url? url-parsed) (allowed-url? url-parsed))
(.openExternal shell url-parsed.href))))

(defn register-ipc-on-events!
[]
(defn register-ipc-on-events! []
(doseq
[[e f]
[["relaunch" #(do (.relaunch app) (.exit app))]
[["initialized" #(.close ^js @loading-window)]
["relaunch" #(doto app (.relaunch) (.exit))]
["open-remote-url" open-external!]
["open-directory" #(.showItemInFolder shell %)]
["window-minimize" #(.minimize ^js @main-window)]
@@ -59,8 +59,7 @@
(.maximize ^js @main-window))]]]
(.on ipcMain e #(f %2))))

(defn register-ipc-handle-events!
[]
(defn register-ipc-handle-events! []
(doseq
[[e f]
[["open-documents" file/open!]
@@ -70,8 +69,7 @@
["print" file/print!]]]
(.handle ipcMain e #(f %2))))

(defn register-window-events!
[]
(defn register-window-events! []
(doseq
[[window-event f]
[["maximize" #(send-to-renderer! "window-maximized")]
@@ -87,31 +85,29 @@
"window-unmaximized"))]]]
(.on ^js @main-window window-event f)))

(defn register-web-contents-events!
[]
(doseq
[[web-contents-event f]
[["will-frame-navigate" #(.preventDefault %)]
["closed" #(reset! main-window nil)]]]
(.on (.-webContents ^js @main-window) web-contents-event f)))
(defn register-web-contents-events! []
(let [web-contents (.-webContents ^js @main-window)]
(doseq
[[web-contents-event f]
[["will-frame-navigate" #(.preventDefault %)]
["closed" #(reset! main-window nil)]]]
(.on web-contents web-contents-event f))))

(defn handle-on-ready-to-show!
(defn on-ready-to-show!
[^js window]
(doseq
[action
[(if (.isMaximized window) "window-maximized" "window-unmaximized")
(if (.isFullScreen window) "window-entered-fullscreen" "window-leaved-fullscreen")
(if (.isFocused window) "window-focused" "window-blurred")
"window-loaded"]]
(if (.isFocused window) "window-focused" "window-blurred")]]
(send-to-renderer! action)))

(defn resource-path
[s]
(url/format #js {:pathname (.join path js/__dirname s)
:protocol "file"}))

(defn init-main-window!
[]
(defn init-main-window! []
(let [win-state (window-state-keeper #js {:defaultWidth 1920
:defaultHeight 1080})]
(reset! main-window
@@ -120,12 +116,12 @@
:y (.-y win-state)
:width (.-width win-state)
:height (.-height win-state)
:backgroundColor "#313131"
:titleBarStyle (when (= (.platform os) "darwin") "hidden")
:trafficLightPosition #js {:x 8 :y 10}
:icon (.join path js/__dirname "/public/img/icon.png")
:frame false
:show false
:transparent true
:webPreferences
#js {:sandbox false
:preload (.join path js/__dirname "preload.js")}}))
@@ -134,11 +130,9 @@
"ready-to-show"
(fn []
(.show ^js @main-window)
(.manage win-state ^js @main-window)
(.hide ^js @loading-window)
(.close ^js @loading-window)))
(.manage win-state ^js @main-window)))

(.on ^js @main-window "ready-to-show" #(handle-on-ready-to-show! @main-window))
(.on ^js @main-window "ready-to-show" #(on-ready-to-show! @main-window))

(.loadURL ^js @main-window
(if config/debug?
@@ -156,9 +150,10 @@
(BrowserWindow.
#js {:width 720
:height 576
:backgroundColor "#313131"
:icon (.join path js/__dirname "/public/img/icon.png")
:show false
:alwaysOnTop true
:transparent true
:frame false}))
(.once ^js @loading-window "show" init-main-window!)
(.loadURL ^js @loading-window (resource-path "/public/loading.html"))
4 changes: 3 additions & 1 deletion src/lang/ar-EG.edn
Original file line number Diff line number Diff line change
@@ -14,6 +14,9 @@
:source-code "الكود المصدري"
:changelog "سجل التغييرات"}

:renderer.app.events
{:create-doc "إنشاء مستند"}

:renderer.dialog.views
{:search-command "البحث عن أمر"
:no-results "لا توجد نتائج."
@@ -502,7 +505,6 @@
:renderer.document.events
{:save-changes "هل تريد حفظ التغييرات؟"
:create-doc "إنشاء مستند"
:init-doc "تهيئة مستند"
:create-doc-from-template "إنشاء مستند من قالب"
:load-doc "تحميل مستند"
:error-loading "خطأ أثناء تحميل %1"
4 changes: 3 additions & 1 deletion src/lang/el-GR.edn
Original file line number Diff line number Diff line change
@@ -14,6 +14,9 @@
:source-code "Πηγαίος κώδικας"
:changelog "Αρχείο αλλαγών"}

:renderer.app.events
{:create-doc "Δημιουργία εγγράφου"}

:renderer.dialog.views
{:search-command "Αναζήτηση εντολής"
:no-results "Δεν βρέθηκαν αποτελέσματα."
@@ -546,7 +549,6 @@
:renderer.document.events
{:save-changes "Θέλετε να αποθηκεύσετε τις αλλαγές σας;"
:create-doc "Δημιουργία εγγράφου"
:init-doc "Αρχικοποίηση εγγράφου"
:create-doc-from-template "Δημιουργία εγγράφου από πρότυπο"
:load-doc "Φόρτωση εγγράφου"
:error-loading "Σφάλμα κατα τη φόρτωση %1"
4 changes: 3 additions & 1 deletion src/lang/en-US.edn
Original file line number Diff line number Diff line change
@@ -14,6 +14,9 @@
:source-code "Source Code"
:changelog "Changelog"}

:renderer.app.events
{:create-doc "Create document"}

:renderer.dialog.views
{:search-command "Search for a command"
:no-results "No results found."
@@ -518,7 +521,6 @@
:renderer.document.events
{:save-changes "Do you want to save your changes?"
:create-doc "Create document"
:init-doc "Init document"
:create-doc-from-template "Create document from template"
:load-doc "Load document"
:error-loading "Error while loading %1"
3 changes: 1 addition & 2 deletions src/renderer/app/db.cljs
Original file line number Diff line number Diff line change
@@ -65,11 +65,10 @@
[:notifications {:default []} [:* Notification]]
[:debug-info {:default false} boolean?]
[:help-bar {:default true} boolean?]
[:loading {:default true} boolean?]
[:pen-mode {:default false} boolean?]
[:backdrop {:default false} boolean?]
[:lang {:optional true :persist true} Lang]
[:dir {:optional true :persist true} string?]
[:system-lang {:optional true} string?]
[:platform {:optional true} Platform]
[:versions {:optional true} [:maybe map?]]
[:env {:optional true} [:maybe map?]]
37 changes: 25 additions & 12 deletions src/renderer/app/effects.cljs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
(ns renderer.app.effects
(:require
[akiroz.re-frame.storage :as rf.storage]
["localforage" :as localforage]
[cognitect.transit :as transit]
[config :as config]
[re-frame.core :as rf]
[re-frame.db :as rf.db]
[renderer.app.db :as app.db]
[renderer.history.handlers :as history.handlers]))

(rf.storage/reg-co-fx! config/app-key {:cofx :store})
[renderer.history.handlers :as history.handlers]
[renderer.notification.events :as-alias notification.events]))

(rf/reg-cofx
::platform
@@ -36,29 +36,42 @@
(assoc coeffects :user-agent (.-userAgent js/navigator))))

(rf/reg-cofx
::system-language
::language
(fn [coeffects _]
(assoc coeffects :system-language (.-language js/navigator))))
(assoc coeffects :language (.-language js/navigator))))

(rf/reg-fx
::query-local-fonts
(fn [{:keys [on-success on-error formatter]}]
(when-not (undefined? js/window.queryLocalFonts)
(-> (.queryLocalFonts js/window)
(.then #(when on-success (rf/dispatch (conj on-success
(cond-> %
formatter formatter)))))
(.then #(when on-success (rf/dispatch (conj on-success (cond-> %
formatter formatter)))))
(.catch #(when on-error (rf/dispatch (conj on-error %))))))))

(defn json->clj
[json]
(transit/read (transit/reader :json) json))

(rf/reg-fx
::get-local-db
(fn [{:keys [on-success on-error on-finally]}]
(-> (localforage/getItem config/app-name)
(.then #(when on-success (rf/dispatch (conj on-success (json->clj %)))))
(.catch #(when on-error (rf/dispatch (conj on-error %))))
(.finally #(when on-finally (rf/dispatch on-finally))))))

(rf/reg-fx
::persist
(fn []
(let [db @rf.db/app-db
db (cond-> db
(:active-document db)
history.handlers/drop-rest)]
(->> (select-keys db app.db/persisted-keys)
(rf.storage/->store config/app-key)))))
(.catch (->> (select-keys db app.db/persisted-keys)
(transit/write (transit/writer :json))
(localforage/setItem config/app-name))
#(rf/dispatch [::notification.events/show-exception %])))))

(rf/reg-fx
::validate
@@ -70,4 +83,4 @@
(rf/reg-fx
::clear-local-storage
(fn []
(rf.storage/->store config/app-key {})))
(localforage/clear)))
140 changes: 69 additions & 71 deletions src/renderer/app/events.cljs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
(ns renderer.app.events
(:require
[malli.error :as malli.error]
[re-frame.core :as rf]
[renderer.app.db :as app.db]
[renderer.app.effects :as-alias app.effects]
[renderer.document.handlers :as document.handlers]
[renderer.effects :as-alias effects]
[renderer.event.events :as-alias event.events]
[renderer.event.impl.keyboard :as event.impl.keyboard]
[renderer.history.handlers :as history.handlers]
[renderer.notification.events :as-alias notification.events]
[renderer.notification.handlers :as notification.handlers]
[renderer.notification.views :as notification.views]
[renderer.utils.i18n :as utils.i18n]
[renderer.snap.handlers :as snap.handlers]
[renderer.theme.effects :as-alias theme.effects]
[renderer.theme.events :as-alias theme.events]
[renderer.utils.i18n :as utils.i18n :refer [t]]
[renderer.window.events :as-alias window.events]))

(def persist
@@ -23,75 +25,97 @@
db
(rf/assoc-effect :fx (conj (or fx []) [::app.effects/persist])))))))

(defonce document-listeners
[[js/document "keydown" [::event.events/keyboard] event.impl.keyboard/->clj]
[js/document "keyup" [::event.events/keyboard] event.impl.keyboard/->clj]
[js/document "fullscreenchange" [::window.events/update-fullscreen]]
[js/window "focus" [::window.events/update-focused]]
[js/window "blur" [::window.events/update-focused]]])

(defonce ipc-listeners
[["window-maximized" [::window.events/set-maximized true]]
["window-unmaximized" [::window.events/set-maximized false]]
["window-focused" [::window.events/set-focused true]]
["window-blurred" [::window.events/set-focused false]]
["window-entered-fullscreen" [::window.events/set-fullscreen true]]
["window-leaved-fullscreen" [::window.events/set-fullscreen false]]
["window-minimized" [::window.events/set-minimized true]]])

(rf/reg-event-fx
::initialize-db
::initialize
[(rf/inject-cofx ::app.effects/user-agent)
(rf/inject-cofx ::app.effects/platform)
(rf/inject-cofx ::app.effects/versions)
(rf/inject-cofx ::app.effects/env)
(rf/inject-cofx ::app.effects/system-language)]
(fn [{:keys [user-agent platform versions env system-language]} _]
(rf/inject-cofx ::app.effects/env)]
(fn [{:keys [user-agent platform versions env]} _]
{:db (assoc app.db/default
:platform platform
:versions (js->clj versions)
:env (js->clj env)
:user-agent user-agent
:system-lang system-language)}))
:user-agent user-agent)
:fx (into
[[::app.effects/get-local-db {:on-success [::load-local-db]
:on-error [::notification.events/show-exception]
:on-finally [::db-loaded]}]]
(map (partial vector ::effects/ipc-on) ipc-listeners))}))

(rf/reg-event-fx
::load-local-db
[(rf/inject-cofx :store)]
(fn [{:keys [db store]} _]
(let [app-db (merge db store)]
(fn [{:keys [db]} [_ local-stored-db]]
(let [app-db (merge db local-stored-db)]
(if (app.db/valid? app-db)
{:db app-db}
{::app.effects/clear-local-storage nil
:db (notification.handlers/add db (notification.views/spec-failed
"Invalid local configuration"
(-> app-db
app.db/explain
malli.error/humanize
str)))}))))
{::app.effects/clear-local-storage nil}))))

(rf/reg-event-fx
::db-loaded
[(rf/inject-cofx ::effects/guid)
(rf/inject-cofx ::app.effects/language)]
(fn [{:keys [db guid language]} _]
{:db (cond-> db
(not (:lang db))
(assoc :lang (if (utils.i18n/supported-lang? language)
language
"en-US"))

(not (:active-document db))
(-> (document.handlers/create guid)
(history.handlers/finalize #(t [:create-doc "Create document"])))

(:active-document db)
(snap.handlers/rebuild-tree)

:always
(assoc :loading false))
:fx (into
[[:dispatch [::theme.events/set-document-attr]]
[:dispatch ^:flush-dom [::set-lang-attrs]]
[:dispatch ^:flush-dom [::window.events/update-focused]]
[::theme.effects/add-native-listener [::theme.events/set-document-attr]]
[::effects/ipc-send ["initialized"]]]
(map (partial vector ::effects/add-listener) document-listeners))}))

(rf/reg-event-db
::set-system-fonts
(fn [db [_ fonts]]
(assoc db :system-fonts fonts)))

(rf/reg-event-fx
::set-document-lang
::set-lang-attrs
(fn [{:keys [db]} _]
{::effects/set-document-attr ["lang" (:lang db)]}))

(rf/reg-event-fx
::set-document-dir
(fn [{:keys [db]} _]
{::effects/set-document-attr ["dir" (:dir db)]}))
(let [lang (:lang db)
dir (get-in utils.i18n/languages [lang :dir])]
{:fx [[::effects/set-document-attr ["lang" lang]]
[::effects/set-document-attr ["dir" dir]]]})))

(rf/reg-event-fx
::set-lang
[persist]
(fn [{:keys [db]} [_ lang]]
{:db (cond-> db
(utils.i18n/supported-lang? lang)
(assoc :lang lang
:dir (get-in utils.i18n/languages [lang :dir])))
:dispatch-n [[::set-document-lang]
[::set-document-dir]]}))

(rf/reg-event-fx
::init-lang
[persist]
(fn [{:keys [db]} _]
(let [lang (if (utils.i18n/supported-lang? (:system-lang db))
(:system-lang db)
"en-US")]
{:db (cond-> db
(not (:lang db))
(assoc :lang lang
:dir (get-in utils.i18n/languages [lang :dir])))
:dispatch-n [[::set-document-lang]
[::set-document-dir]]})))
(assoc :lang lang))
:dispatch [::set-lang-attrs]}))

(rf/reg-event-db
::set-repl-mode
@@ -152,29 +176,3 @@
(rf/assoc-effect :fx
(conj (or fx [])
[::app.effects/validate [db event]])))))))

(rf/reg-event-fx
::add-listeners
(fn [_ _]
{:fx (->> [[js/document "keydown" [::event.events/keyboard] event.impl.keyboard/->clj]
[js/document "keyup" [::event.events/keyboard] event.impl.keyboard/->clj]
[js/document "fullscreenchange" [::window.events/update-fullscreen]]
[js/window "load" [::window.events/update-focused]]
[js/window "focus" [::window.events/update-focused]]
[js/window "blur" [::window.events/update-focused]]]
(mapv #(vector ::effects/add-listener %)))}))

(rf/reg-event-fx
::register-listeners
(fn [{:keys [db]} _]
(if (= (:platform db) "web")
{:dispatch [::add-listeners]}
{:fx (->> [["window-maximized" [::window.events/set-maximized true]]
["window-unmaximized" [::window.events/set-maximized false]]
["window-focused" [::window.events/set-focused true]]
["window-blurred" [::window.events/set-focused false]]
["window-entered-fullscreen" [::window.events/set-fullscreen true]]
["window-leaved-fullscreen" [::window.events/set-fullscreen false]]
["window-minimized" [::window.events/set-minimized true]]
["window-loaded" [::add-listeners]]]
(mapv #(vector ::effects/ipc-on %)))})))
4 changes: 4 additions & 0 deletions src/renderer/app/subs.cljs
Original file line number Diff line number Diff line change
@@ -108,6 +108,10 @@
::grid
:-> :grid)

(rf/reg-sub
::loading?
:-> :loading)

(rf/reg-sub
::panel-visible?
(fn [db [_ k]]
67 changes: 35 additions & 32 deletions src/renderer/app/views.cljs
Original file line number Diff line number Diff line change
@@ -338,35 +338,38 @@
properties-visible @(rf/subscribe [::app.subs/panel-visible? :properties])
active-tool @(rf/subscribe [::tool.subs/active])
recent-documents @(rf/subscribe [::document.subs/recent])
lang-dir @(rf/subscribe [::app.subs/lang-dir])]
[:> Direction/Provider {:dir lang-dir}
[:> Tooltip/Provider
[:div.flex.flex-col.flex-1.h-dvh.overflow-hidden.justify-between
[window.views/app-header]
(if (seq documents)
[:div.flex.h-full.flex-1.overflow-hidden.gap-px
(when tree-visible
[:div.flex-col.hidden.overflow-hidden
{:class "md:flex"
:style {:width "227px"}}
[document.views/actions]
[tree.views/root]])
[:div.flex.flex-col.flex-1.overflow-hidden.h-full
[document.views/tab-bar]
[:div.flex.h-full.flex-1.gap-px.overflow-hidden
[:div.flex.h-full.flex-col.flex-1.overflow-hidden.gap-px
[editor]]
[:div.flex.gap-px
(when properties-visible
[:div.hidden
{:class "md:flex"}
[:div.flex.flex-col.h-full.w-80
[views/scroll-area
(tool.hierarchy/right-panel active-tool)]
[:div.bg-primary.grow.flex]]])
[:div.bg-primary.flex
[views/scroll-area [toolbar.object/root]]]]]]]
[home recent-documents])
[:div]]
[dialog.views/root]
[notification.views/main]]]))
lang-dir @(rf/subscribe [::app.subs/lang-dir])
loading @(rf/subscribe [::app.subs/loading?])]
(if loading
[:div.loader]
[:> Direction/Provider {:dir lang-dir}
[:> Tooltip/Provider
[:div.flex.flex-col.flex-1.h-dvh.overflow-hidden.justify-between
[window.views/app-header]
(if (seq documents)
[:div.flex.h-full.flex-1.overflow-hidden.gap-px
(when tree-visible
[:div.flex-col.hidden.overflow-hidden
{:class "md:flex"
:style {:width "227px"}}
[document.views/actions]
[tree.views/root]])
[:div.flex.flex-col.flex-1.overflow-hidden.h-full
[document.views/tab-bar]
[:div.flex.h-full.flex-1.gap-px.overflow-hidden
[:div.flex.h-full.flex-col.flex-1.overflow-hidden.gap-px
[editor]]
[:div.flex.gap-px
(when properties-visible
[:div.hidden
{:class "md:flex"}
[:div.flex.flex-col.h-full.w-80
[views/scroll-area
(tool.hierarchy/right-panel active-tool)]
[:div.bg-primary.grow.flex]]])
[:div.bg-primary.flex
[views/scroll-area [toolbar.object/root]]]]]]]
[home recent-documents])
[:div]]
[dialog.views/root]
[notification.views/main]]])))
15 changes: 4 additions & 11 deletions src/renderer/core.cljs
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@
[renderer.attribute.impl.core]
[renderer.dialog.events]
[renderer.dialog.subs]
[renderer.document.events :as document.events]
[renderer.document.events]
[renderer.document.subs]
[renderer.element.effects]
[renderer.element.events]
@@ -33,7 +33,7 @@
[renderer.snap.events]
[renderer.snap.subs]
[renderer.theme.effects]
[renderer.theme.events :as theme.events]
[renderer.theme.events]
[renderer.theme.subs]
[renderer.timeline.events]
[renderer.timeline.subs]
@@ -75,8 +75,7 @@
(reset! root-el (ra.dom.client/create-root container))
(ra.dom.client/render @root-el [utils.error/boundary [app.views/root]])))

(defn bootstrap-cb!
[]
(defn bootstrap-cb! []
(replumb/run-repl "(in-ns 'user)" identity)
(print "Welcome to your REPL!")
(print "")
@@ -90,15 +89,9 @@
(bootstrap/init replumb.repl/st {:path "js/bootstrap"
:load-on-init '[user]} bootstrap-cb!)

(rf/dispatch-sync [::app.events/initialize-db])
(rf/dispatch-sync [::app.events/load-local-db])
(rf/dispatch-sync [::app.events/init-lang])
(rf/dispatch-sync [::theme.events/set-document-attr])
(rf/dispatch-sync [::document.events/init])
(rf/dispatch-sync [::theme.events/add-native-listener])
(rf/dispatch-sync [::app.events/initialize])
(rf/dispatch-sync [::re-pressed/add-keyboard-event-listener "keydown"])
(rf/dispatch-sync [::re-pressed/set-keydown-rules event.impl.keyboard/keydown-rules])
(rf/dispatch-sync [::app.events/register-listeners])

(.setup paper)

31 changes: 3 additions & 28 deletions src/renderer/document/events.cljs
Original file line number Diff line number Diff line change
@@ -2,25 +2,20 @@
(:require
[cljs.reader :as cljs.reader]
[config :as config]
[malli.core :as m]
[re-frame.core :as rf]
[renderer.app.db :refer [App]]
[renderer.app.events :refer [persist]]
[renderer.dialog.handlers :as dialog.handlers]
[renderer.dialog.views :as dialog.views]
[renderer.document.db :as document.db]
[renderer.document.handlers :as document.handlers]
[renderer.effects :as-alias effects]
[renderer.element.handlers :as element.handlers]
[renderer.history.handlers :as history.handlers]
[renderer.notification.events :as-alias notification.events]
[renderer.notification.handlers :as notification.handlers]
[renderer.notification.views :as notification.views]
[renderer.snap.handlers :as snap.handlers]
[renderer.utils.compatibility :as utils.compatibility]
[renderer.utils.element :as utils.element]
[renderer.utils.i18n :refer [t]]
[renderer.utils.math :refer [Vec2]]
[renderer.utils.vec :as utils.vec]
[shared :as shared]))

@@ -140,39 +135,19 @@
(= swapped-i -1)))
(assoc :document-tabs (utils.vec/swap document-tabs dragged-i swapped-i))))))

(m/=> create [:function
[:-> map? uuid? App]
[:-> map? uuid? [:maybe Vec2] App]])
(defn create
([db guid]
(create db guid [595 842]))
([db guid size]
(-> (document.handlers/create-tab db (assoc document.db/default :id guid))
(element.handlers/create-default-canvas size)
(document.handlers/center))))

(rf/reg-event-fx
::new
[(rf/inject-cofx ::effects/guid)]
(fn [{:keys [db guid]} [_]]
{:db (-> (create db guid)
(history.handlers/finalize #(t [::create-doc "Create document"])))
{:db (-> (document.handlers/create db guid)
(history.handlers/finalize #(t [:create-doc "Create document"])))
::effects/focus nil}))

(rf/reg-event-fx
::init
[(rf/inject-cofx ::effects/guid)]
(fn [{:keys [db guid]} [_]]
{:db (if (:active-document db)
(snap.handlers/rebuild-tree db)
(-> (create db guid)
(history.handlers/finalize #(t [::init-doc "Init document"]))))}))

(rf/reg-event-fx
::new-from-template
[(rf/inject-cofx ::effects/guid)]
(fn [{:keys [db guid]} [_ size]]
{:db (-> (create db guid size)
{:db (-> (document.handlers/create db guid size)
(history.handlers/finalize #(t [::create-doc-from-template
"Create document from template"])))}))

21 changes: 17 additions & 4 deletions src/renderer/document/handlers.cljs
Original file line number Diff line number Diff line change
@@ -4,11 +4,13 @@
[malli.error :as m.error]
[malli.transform :as m.transform]
[renderer.app.db :refer [App]]
[renderer.document.db :as db :refer [Document PersistedDocument]]
[renderer.document.db :as document.db :refer [Document PersistedDocument]]
[renderer.element.handlers :as element.handlers]
[renderer.frame.handlers :as frame.handlers]
[renderer.notification.handlers :as notification.handlers]
[renderer.notification.views :as notification.views]
[renderer.snap.handlers :as snap.handlers]
[renderer.utils.math :refer [Vec2]]
[renderer.utils.vec :as utils.vec]))

(m/=> path [:function
@@ -121,6 +123,17 @@
(set-active id)
(update :document-tabs #(utils.vec/add % (inc active-index) id)))))

(m/=> create [:function
[:-> map? uuid? App]
[:-> map? uuid? [:maybe Vec2] App]])
(defn create
([db guid]
(create db guid [595 842]))
([db guid size]
(-> (create-tab db (assoc document.db/default :id guid))
(element.handlers/create-default-canvas size)
(center))))

(m/=> set-hovered-ids [:-> App [:set uuid?] App])
(defn set-hovered-ids
[db ids]
@@ -150,11 +163,11 @@
(defn load
[db document]
(let [open-document-id (search-by-path db (:path document))
document (merge db/default document)
document (merge document.db/default document)
document (cond-> document
open-document-id
(assoc :id open-document-id))]
(if (db/valid? document)
(if (document.db/valid? document)
(cond-> db
(not open-document-id)
(-> (create-tab document)
@@ -166,7 +179,7 @@
:always
(set-active (:id document)))

(let [explanation (-> document db/explain m.error/humanize str)]
(let [explanation (-> document document.db/explain m.error/humanize str)]
(->> (notification.views/spec-failed "Load document" explanation)
(notification.handlers/add db))))))

5 changes: 0 additions & 5 deletions src/renderer/theme/events.cljs
Original file line number Diff line number Diff line change
@@ -7,11 +7,6 @@
[renderer.theme.effects :as-alias theme.effects]
[renderer.theme.handlers :as theme.handlers]))

(rf/reg-event-fx
::add-native-listener
(fn [_ _]
{::theme.effects/add-native-listener [::set-document-attr]}))

(rf/reg-event-fx
::set-document-attr
[(rf/inject-cofx ::theme.effects/native-mode)]
4 changes: 3 additions & 1 deletion src/renderer/tool/events.cljs
Original file line number Diff line number Diff line change
@@ -8,7 +8,9 @@
(rf/reg-event-fx
::activate
(fn [{:keys [db]} [_ tool]]
{:db (tool.handlers/activate db tool)
{:db (cond-> db
(:active-document db)
(tool.handlers/activate tool))
::effects/focus nil}))

(rf/reg-event-db
4 changes: 2 additions & 2 deletions src/renderer/utils/bounds.cljs
Original file line number Diff line number Diff line change
@@ -83,8 +83,8 @@
[bbox options]
(let [[min-x min-y max-x max-y] bbox
[cx cy] (center bbox)
bounds-corner-txt (t [::bounds-corner "bounds corner"])
bounds-center-txt (t [::bounds-center "bounds center"])
bounds-corner-txt (t [::bounds-corner "bounds corner"])
bounds-center-txt (t [::bounds-center "bounds center"])
bounds-midpoints-txt (t [::bounds-midpoint "bounds midpoint"])]
(cond-> []
(:corners options)
4 changes: 1 addition & 3 deletions src/renderer/utils/element.cljs
Original file line number Diff line number Diff line change
@@ -55,9 +55,7 @@
[el]
(let [el-bbox (:bbox el)
local-bbox (element.hierarchy/bbox el)]
(->> (matrix/sub el-bbox local-bbox)
(take 2)
(vec))))
(into [] (take 2) (matrix/sub el-bbox local-bbox))))

(m/=> snapping-points [:-> Element SnapOptions [:* Vec2]])
(defn snapping-points
17 changes: 9 additions & 8 deletions test/app_test.cljs
Original file line number Diff line number Diff line change
@@ -9,10 +9,15 @@

(defn test-fixtures
[]
(rf/reg-fx
::app.effects/get-local-db
(fn [{:keys [on-finally]}]
(rf/dispatch on-finally)))

(rf/reg-cofx
::app.effects/system-language
::app.effects/language
(fn [coeffects _]
(assoc coeffects :system-language "en-US")))
(assoc coeffects :language "en-US")))

(rf/reg-fx
::app.effects/query-local-fonts
@@ -34,15 +39,11 @@
(deftest app
(rf.test/run-test-sync
(test-fixtures)
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::app.events/initialize])

(testing "language"
(let [lang (rf/subscribe [::app.subs/lang])]
(testing "default"
(is (not @lang)))

(testing "initialization"
(rf/dispatch [::app.events/init-lang])
(is (= "en-US" @lang)))

(testing "set valid language"
@@ -70,7 +71,7 @@
(deftest fonts
(rf.test/run-test-async
(test-fixtures)
(rf/dispatch-sync [::app.events/initialize-db])
(rf/dispatch-sync [::app.events/initialize])

(testing "loading system fonts"
(let [system-fonts (rf/subscribe [::app.subs/system-fonts])
4 changes: 1 addition & 3 deletions test/benchmark.cljs
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@
[malli.instrument :as m.instrument]
[re-frame.core :as rf]
[renderer.app.events :as app.events]
[renderer.document.events :as-alias document.events]
[renderer.element.events :as-alias element.events]))

(defn bench
@@ -21,8 +20,7 @@

(deftest polygons
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(rf/dispatch [::app.events/initialize])

;; Istrumentation and db validation affects performance, so we disable it.
(m.instrument/unstrument!)
17 changes: 11 additions & 6 deletions test/document_test.cljs
Original file line number Diff line number Diff line change
@@ -3,26 +3,31 @@
[cljs.test :refer-macros [deftest is testing]]
[day8.re-frame.test :as rf.test]
[re-frame.core :as rf]
[renderer.app.effects :as-alias app.effects]
[renderer.app.events :as-alias app.events]
[renderer.document.db :as document.db]
[renderer.document.events :as-alias document.events]
[renderer.document.subs :as-alias document.subs]))

(defn test-fixtures
[]
(rf/reg-fx
::app.effects/get-local-db
(fn [{:keys [on-finally]}]
(rf/dispatch on-finally))))

(deftest document
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(test-fixtures)
(rf/dispatch [::app.events/initialize])
(rf/dispatch [::app.events/db-loaded])

(let [document-entities? (rf/subscribe [::document.subs/entities?])
active-document (rf/subscribe [::document.subs/active])
saved? (rf/subscribe [::document.subs/active-saved?])
title-bar (rf/subscribe [::document.subs/title-bar])
active-id (rf/subscribe [::document.subs/active-id])]
(testing "defaults"
(is (not @document-entities?))
(is (not @active-document)))

(testing "initialization"
(rf/dispatch [::document.events/init])
(is @document-entities?)
(is (document.db/valid? @active-document))
(is (= "• Untitled-1 - Repath Studio" @title-bar)))
126 changes: 74 additions & 52 deletions test/element_test.cljs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
[cljs.test :refer-macros [deftest are is testing]]
[day8.re-frame.test :as rf.test]
[re-frame.core :as rf]
[renderer.app.effects :as-alias app.effects]
[renderer.app.events :as-alias app.events]
[renderer.document.events :as-alias document.events]
[renderer.element.db :as element.db]
@@ -12,10 +13,17 @@

(.setup paper)

(defn test-fixtures
[]
(rf/reg-fx
::app.effects/get-local-db
(fn [{:keys [on-finally]}]
(rf/dispatch on-finally))))

(deftest init
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [root (rf/subscribe [::element.subs/root])
root-children (rf/subscribe [::element.subs/root-children])]
@@ -27,8 +35,8 @@

(deftest select
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [selected (rf/subscribe [::element.subs/selected])]
(testing "default state"
@@ -56,7 +64,8 @@

(deftest index
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(test-fixtures)
(rf/dispatch [::app.events/initialize])
(rf/dispatch [::document.events/new-from-template nil])

(rf/dispatch [::element.events/add {:tag :rect
@@ -99,7 +108,8 @@

(deftest align
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(test-fixtures)
(rf/dispatch [::app.events/initialize])
(rf/dispatch [::document.events/new-from-template [800 600]])

(rf/dispatch [::element.events/add {:tag :rect
@@ -153,8 +163,8 @@

(deftest visibility
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [selected (rf/subscribe [::element.subs/selected])]
(rf/dispatch [::element.events/add {:tag :rect
@@ -173,8 +183,8 @@

(deftest label
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [selected (rf/subscribe [::element.subs/selected])]
(rf/dispatch [::element.events/add {:tag :rect
@@ -193,8 +203,8 @@

(deftest lock
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [selected (rf/subscribe [::element.subs/selected])]
(rf/dispatch [::element.events/add {:tag :rect
@@ -213,8 +223,8 @@

(deftest attribute
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [selected (rf/subscribe [::element.subs/selected])]
(rf/dispatch [::element.events/add {:tag :rect
@@ -237,8 +247,8 @@

(deftest delete
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [selected (rf/subscribe [::element.subs/selected])]
(rf/dispatch [::element.events/add {:tag :rect
@@ -254,8 +264,9 @@

(deftest scale
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [selected (rf/subscribe [::element.subs/selected])]
(rf/dispatch [::element.events/add {:tag :rect
:attrs {:width "100"
@@ -266,8 +277,9 @@

(deftest translate
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [selected (rf/subscribe [::element.subs/selected])]
(rf/dispatch [::element.events/add {:tag :rect
:attrs {:x "100"
@@ -280,8 +292,9 @@

(deftest place
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [selected (rf/subscribe [::element.subs/selected])]
(rf/dispatch [::element.events/add {:tag :rect
:attrs {:x "100"
@@ -294,8 +307,9 @@

(deftest ->path
(rf.test/run-test-async
(rf/dispatch-sync [::app.events/initialize-db])
(rf/dispatch-sync [::document.events/init])
(test-fixtures)
(rf/dispatch-sync [::app.events/initialize])

(let [selected (rf/subscribe [::element.subs/selected])]
(rf/dispatch [::element.events/add {:tag :rect
:attrs {:x "100"
@@ -314,29 +328,33 @@

(deftest stroke->path
(rf.test/run-test-async
(rf/dispatch-sync [::app.events/initialize-db])
(rf/dispatch-sync [::document.events/init])
(let [selected (rf/subscribe [::element.subs/selected])]
(rf/dispatch [::element.events/add {:tag :rect
:attrs {:x "100"
:y "100"
:width "100"
:height "100"
:fill "red"
:stroke "black"}}])
(rf/dispatch [::element.events/stroke->path])

(rf.test/wait-for
[::element.events/finalize-stroke->path]

(is (= (-> @selected first :tag) :path))
(is (= (-> @selected first :attrs :fill) "black"))
(not (-> @selected first :attrs :stroke))))))
(test-fixtures)
(rf/dispatch-sync [::app.events/initialize])

(rf.test/wait-for
[::app.events/db-loaded]
(let [selected (rf/subscribe [::element.subs/selected])]
(rf/dispatch [::element.events/add {:tag :rect
:attrs {:x "100"
:y "100"
:width "100"
:height "100"
:fill "red"
:stroke "black"}}])
(rf/dispatch [::element.events/stroke->path])

(rf.test/wait-for
[::element.events/finalize-stroke->path]

(is (= (-> @selected first :tag) :path))
(is (= (-> @selected first :attrs :fill) "black"))
(not (-> @selected first :attrs :stroke)))))))

(deftest boolean-operation
(rf.test/run-test-async
(rf/dispatch-sync [::app.events/initialize-db])
(rf/dispatch-sync [::document.events/init])
(test-fixtures)
(rf/dispatch-sync [::app.events/initialize])

(let [selected (rf/subscribe [::element.subs/selected])]
(rf/dispatch [::element.events/add {:tag :rect
:attrs {:x "100"
@@ -361,8 +379,9 @@

(deftest import-svg
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [selected (rf/subscribe [::element.subs/selected])]
(rf/dispatch [::element.events/import-svg
{:svg "<svg x=\"100\" y=\"100\" width=\"200\" height=\"200\"></svg>"
@@ -375,8 +394,9 @@

(deftest animate
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(rf/dispatch [::element.events/add {:tag :rect
:attrs {:x "100"
:y "100"
@@ -390,8 +410,9 @@

(deftest set-parent
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(rf/dispatch [::element.events/add {:tag :rect
:attrs {:x "100"
:y "100"
@@ -407,8 +428,9 @@

(deftest group
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(rf/dispatch [::element.events/add {:tag :rect
:attrs {:x "100"
:y "100"
13 changes: 10 additions & 3 deletions test/frame_test.cljs
Original file line number Diff line number Diff line change
@@ -3,16 +3,23 @@
[cljs.test :refer-macros [deftest is testing]]
[day8.re-frame.test :as rf.test]
[re-frame.core :as rf]
[renderer.app.effects :as-alias app.effects]
[renderer.app.events :as-alias app.events]
[renderer.document.events :as-alias document.events]
[renderer.document.subs :as-alias document.subs]
[renderer.frame.events :as-alias frame.events]
[renderer.frame.subs :as-alias frame.subs]))

(defn test-fixtures
[]
(rf/reg-fx
::app.effects/get-local-db
(fn [{:keys [on-finally]}]
(rf/dispatch on-finally))))

(deftest frame
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [viewbox (rf/subscribe [::frame.subs/viewbox])
zoom (rf/subscribe [::document.subs/zoom])
13 changes: 10 additions & 3 deletions test/history_test.cljs
Original file line number Diff line number Diff line change
@@ -3,17 +3,24 @@
[cljs.test :refer-macros [deftest is testing]]
[day8.re-frame.test :as rf.test]
[re-frame.core :as rf]
[renderer.app.effects :as-alias app.effects]
[renderer.app.events :as-alias app.events]
[renderer.document.events :as-alias document.events]
[renderer.element.events :as-alias element.events]
[renderer.element.subs :as-alias element.subs]
[renderer.history.events :as-alias history.events]
[renderer.history.subs :as history.subs]))

(defn test-fixtures
[]
(rf/reg-fx
::app.effects/get-local-db
(fn [{:keys [on-finally]}]
(rf/dispatch on-finally))))

(deftest history
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [undos? (rf/subscribe [::history.subs/undos?])
redos? (rf/subscribe [::history.subs/redos?])
2 changes: 1 addition & 1 deletion test/notification_test.cljs
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@

(deftest notification
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::app.events/initialize])

(let [notifications (rf/subscribe [::notification.subs/entities])]

2 changes: 1 addition & 1 deletion test/theme_test.cljs
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@
(deftest mode
(rf.test/run-test-sync
(test-fixtures)
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::app.events/initialize])
(rf/dispatch [::theme.events/set-document-attr])

(let [theme-mode (rf/subscribe [::theme.subs/mode])]
13 changes: 10 additions & 3 deletions test/tool_test.cljs
Original file line number Diff line number Diff line change
@@ -3,17 +3,24 @@
[cljs.test :refer-macros [deftest is testing]]
[day8.re-frame.test :as rf.test]
[re-frame.core :as rf]
[renderer.app.effects :as-alias app.effects]
[renderer.app.events :as-alias app.events]
[renderer.document.events :as-alias document.events]
[renderer.element.events :as-alias element.events]
[renderer.tool.events :as-alias tool.events]
[renderer.tool.hierarchy :as tool.hierarchy]
[renderer.tool.subs :as-alias tool.subs]))

(defn test-fixtures
[]
(rf/reg-fx
::app.effects/get-local-db
(fn [{:keys [on-finally]}]
(rf/dispatch on-finally))))

(deftest tool
(rf.test/run-test-sync
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::document.events/init])
(test-fixtures)
(rf/dispatch [::app.events/initialize])

(let [active-tool (rf/subscribe [::tool.subs/active])]

2 changes: 1 addition & 1 deletion test/window_test.cljs
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@
(deftest window
(rf.test/run-test-sync
(test-fixtures)
(rf/dispatch [::app.events/initialize-db])
(rf/dispatch [::app.events/initialize])

(let [maximized (rf/subscribe [::window.subs/maximized?])
fullscreen (rf/subscribe [::window.subs/fullscreen?])