feat: switch to use Excalidraw api

pull/1437/head
Tienson Qin 2021-03-09 23:24:45 +08:00
parent 76b33f2d8f
commit 92ad0296e6
24 changed files with 258 additions and 694 deletions

View File

@ -789,3 +789,12 @@ a {
font-weight: 600;
font-size: 13px;
}
/* excalidraw */
.Island > div > div > div {
width: 44px;
}
.excalidraw hr {
margin: 0;
}

File diff suppressed because one or more lines are too long

13
resources/css/fonts.css Normal file
View File

@ -0,0 +1,13 @@
/* http://www.eaglefonts.com/fg-virgil-ttf-131249.htm */
@font-face {
font-family: "Virgil";
src: url("../fonts/Virgil.woff2");
font-display: swap;
}
/* https://github.com/microsoft/cascadia-code */
@font-face {
font-family: "Cascadia";
src: url("../fonts/Cascadia.woff2");
font-display: swap;
}

View File

@ -2,6 +2,7 @@
@import "./inter.css";
@import "./reveal.min.css";
@import "./reveal_black.min.css";
@import "./fonts.css";
@import "./excalidraw.min.css";
@import "./katex.min.css";
@import "./codemirror.min.css";

View File

@ -2,6 +2,7 @@
@import "./inter.css";
@import "./reveal.min.css";
@import "./reveal_black.min.css";
@import "./fonts.css";
@import "./excalidraw.min.css";
@import "./katex.min.css";
@import "./codemirror.min.css";
@ -10,4 +11,4 @@
@import "./datepicker.css";
@import "./highlight.css";
@import "./tailwind.core.css"; /* Build by gulp. Check `_buildTailwind` for more detail */
@import "./common.css";
@import "./common.css";

Binary file not shown.

View File

@ -12,6 +12,9 @@
:depends-on #{:main}}
:age-encryption
{:entries [frontend.extensions.age-encryption]
:depends-on #{:main}}
:excalidraw
{:entries [frontend.extensions.excalidraw]
:depends-on #{:main}}}
:output-dir "./static/js"

View File

@ -8,7 +8,9 @@
[goog.dom :as gdom]
[goog.object :as gobj]
[frontend.format :as format]
[frontend.handler.common :as common-handler]))
[frontend.handler.common :as common-handler]
[frontend.handler.draw :as draw]
[promesa.core :as p]))
;; TODO: move to frontend.handler.editor.commands
@ -102,10 +104,13 @@
["Scheduled" [[:editor/clear-current-slash]
[:editor/show-date-picker]]]
["Query" [[:editor/input "{{query }}" {:backward-pos 2}]]]
["Draw" [[:editor/input "/draw "]
[:editor/show-input [{:command :draw
:id :title
:placeholder "Draw title"}]]]]
["Draw" (fn []
(let [file (draw/file-name)
path (str config/default-draw-directory "/" file)
text (util/format "[[%s]]" path)]
(p/let [_ (draw/create-draw-with-default-content path)]
(println "draw file created, " path))
text))]
["WAITING" (->marker "WAITING")]
["CANCELED" (->marker "CANCELED")]
["Tomorrow" #(get-page-ref-text (date/tomorrow))]

View File

@ -16,7 +16,6 @@
[goog.dom :as gdom]
[frontend.handler.expand :as expand]
[frontend.components.svg :as svg]
[frontend.components.draw :as draw]
[frontend.components.datetime :as datetime-comp]
[frontend.ui :as ui]
[frontend.handler.editor :as editor-handler]
@ -48,7 +47,8 @@
[frontend.commands :as commands]
[lambdaisland.glogi :as log]
[frontend.context.i18n :as i18n]
[frontend.template :as template]))
[frontend.template :as template]
[shadow.loader :as loader]))
;; TODO: remove rum/with-context because it'll make reactive queries not working
@ -385,32 +385,41 @@
full-path (.. util/node-path (join repo-path (config/get-local-asset-absolute-path path)))]
[:a.asset-ref {:target "_blank" :href full-path} (or title path)]))
(defonce excalidraw-loaded? (atom false))
(rum/defc excalidraw < rum/reactive
{:init (fn [state]
(p/let [_ (loader/load :excalidraw)]
(reset! excalidraw-loaded? true))
state)}
[file]
(let [loaded? (rum/react excalidraw-loaded?)
draw-component (if loaded?
(resolve 'frontend.extensions.excalidraw/draw))]
(when draw-component
(draw-component {:file file}))))
(rum/defc page-reference < rum/reactive
[html-export? s config label]
(let [show-brackets? (state/show-brackets?)
nested-link? (:nested-link? config)
contents-page? (= "contents" (string/lower-case (str (:id config))))]
[:span.page-reference
(when (and (or show-brackets? nested-link?)
(not html-export?)
(not contents-page?))
[:span.text-gray-500.bracket "[["])
(if (string/ends-with? s ".excalidraw")
[:a.page-ref
{:on-click (fn [e]
(util/stop e)
(set! (.-href js/window.location)
(rfe/href :draw nil {:file (string/replace s (str config/default-draw-directory "/") "")})))}
[:span
(svg/excalidraw-logo)
(string/capitalize (draw/get-file-title s))]]
contents-page? (= "contents" (string/lower-case (str (:id config))))
draw? (string/ends-with? s ".excalidraw")]
(if (string/ends-with? s ".excalidraw")
[:div.draw {:on-click (fn [e]
(.stopPropagation e))}
(excalidraw s)]
[:span.page-reference
(when (and (or show-brackets? nested-link?)
(not html-export?)
(not contents-page?))
[:span.text-gray-500.bracket "[["])
(page-cp (assoc config
:label (mldoc/plain->text label)
:contents-page? contents-page?) {:page/name s}))
(when (and (or show-brackets? nested-link?)
(not html-export?)
(not contents-page?))
[:span.text-gray-500.bracket "]]"])]))
:contents-page? contents-page?) {:page/name s})
(when (and (or show-brackets? nested-link?)
(not html-export?)
(not contents-page?))
[:span.text-gray-500.bracket "]]"])])))
(defn- latex-environment-content
[name option content]

View File

@ -1,497 +0,0 @@
(ns frontend.components.draw
(:require [rum.core :as rum]
[goog.object :as gobj]
[frontend.rum :as r]
[frontend.util :as util :refer-macros [profile]]
[frontend.mixins :as mixins]
[frontend.storage :as storage]
[frontend.components.svg :as svg]
[cljs-bean.core :as bean]
[dommy.core :as d]
[clojure.string :as string]
[frontend.handler.notification :as notification]
[frontend.handler.draw :as draw :refer
[*files
*current-file
*current-title
*file-loading?
*elements
*unsaved?
*search-files
*saving-title
*excalidraw]]
[frontend.handler.file :as file]
[frontend.ui :as ui]
[frontend.loader :as loader]
[frontend.config :as config]
[frontend.state :as state]
[frontend.search :as search]
[frontend.components.repo :as repo]
[promesa.core :as p]
[reitit.frontend.easy :as rfe]))
(defn loaded? []
js/window.Excalidraw)
(defonce *loaded? (atom false))
(defonce draw-state :draw-state)
(defn get-draw-state []
(storage/get draw-state))
(defn set-draw-state! [value]
(storage/set draw-state value))
(defn get-k
([k]
(get-k k (state/get-current-repo)))
([repo k]
(when repo
(get-in (get-draw-state) [repo k]))))
(defn set-k
[k v]
(when-let [repo (state/get-current-repo)]
(let [state (get-draw-state)]
(let [new-state (assoc-in state [repo k] v)]
(set-draw-state! new-state)))))
(defn get-last-file
([]
(get-k :last-file))
([repo]
(get-k repo :last-file)))
(defn get-last-title
([]
(get-k :last-title))
([repo]
(get-k repo :last-title)))
(defn set-last-file!
[value]
(set-k :last-file value))
(defn set-last-title!
[value]
(set-k :last-title value))
(defn get-last-elements
[]
(storage/get-json (str (state/get-current-repo) "-" "last-elements")))
(defn get-last-app-state
[]
(storage/get-json (str (state/get-current-repo) "-" "last-app-state")))
(defn set-last-elements!
[value]
(storage/set-json (str (state/get-current-repo) "-" "last-elements") value))
(defn set-last-app-state!
[value]
(storage/set-json (str (state/get-current-repo) "-" "last-app-state") value))
(defn set-excalidraw-component!
[]
(reset! *excalidraw (r/adapt-class
(gobj/get js/window.Excalidraw "default"))))
(defn serialize-as-json
[elements app-state]
(when (loaded?)
(when-let [f (gobj/get js/window.Excalidraw "serializeAsJSON")]
(f elements app-state))))
;; api restore
(defn from-json
[text]
(when-not (string/blank? text)
(try
(when-let [data (js/JSON.parse text)]
(if (not= "excalidraw" (gobj/get data "type"))
(notification/show!
(util/format "Could not load this invalid excalidraw file")
:error)
{:elements (gobj/get data "elements")
:app-state (gobj/get data "appState")}))
(catch js/Error e
(prn "from json error:")
(js/console.dir e)
(notification/show!
(util/format "Could not load this invalid excalidraw file")
:error)))))
(defn get-file-title
[file]
(when file
(let [s (subs file 20)
title (string/replace s ".excalidraw" "")]
(string/replace title "-" " "))))
(defn save-excalidraw!
[state _event file ok-handler]
(let [title @*current-title]
(cond
(string/blank? title)
(do
(reset! *saving-title nil)
(notification/show!
"Please specify a title first!"
:error)
;; TODO: focus the title input
)
(= title @*saving-title)
nil
:else
(when-let [elements (get-last-elements)]
(reset! *saving-title title)
(let [app-state (get-last-app-state)
[option] (:rum/args state)
file (util/trim-safe
(or
file
@*current-file
(:file option)
(draw/title->file-name title)))
data (serialize-as-json elements app-state)]
(when file
(draw/save-excalidraw! file data
(fn [file]
(reset! *files
(distinct (conj @*files file)))
(reset! *current-file file)
(reset! *unsaved? false)
(set-last-file! file)
(when ok-handler (ok-handler file))
(reset! *saving-title nil)))))))))
(defn- clear-canvas!
[]
(when-let [canvas (d/by-id "canvas")]
(let [context (.getContext canvas "2d")]
(.clearRect context 0 0 (gobj/get canvas "width") (gobj/get canvas "height"))
(set! (.-fillStyle context) "#FFF")
(.fillRect context 0 0 (gobj/get canvas "width") (gobj/get canvas "height")))))
(defn- new-file!
[]
;; TODO: save current firstly
(clear-canvas!)
(reset! *current-title "")
(reset! *current-file nil)
(reset! *elements nil)
(set-last-elements! nil)
(set-last-title! nil)
(set-last-file! nil)
(set-last-app-state! nil))
(defn- rename-file!
[file new-title]
(when-not (string/blank? new-title)
(let [new-file (draw/title->file-name new-title)]
(when-not (= (string/trim file) (string/trim new-file))
(save-excalidraw!
{} {} new-file
(fn []
(set-last-file! new-file)
(util/p-handle
(file/remove-file!
(state/get-current-repo)
(str config/default-draw-directory "/" file))
(fn [_]
(reset! *files (->> (conj @*files new-file)
(remove #(= file %))
distinct
(vec)))
(reset! *current-file new-file)
(notification/show!
"File was renamed successfully!"
:success))
(fn [error]
(println "Rename file failed, reason: ")
(js/console.dir error)))))))))
(rum/defc draw-title < rum/reactive
(mixins/event-mixin
(fn [state]
(let [old-title @*current-title]
(mixins/hide-when-esc-or-outside
state
:on-hide (fn [state e event]
(let [title (and @*current-title (string/trim @*current-title))
file @*current-file]
(when (or
(string/blank? old-title)
(not= (string/trim old-title) title))
(cond
(and file (not (string/blank? title)))
(rename-file! file title)
(and (not file)
(not (string/blank? title))
(seq @*elements)) ; new file
(save-excalidraw! {} {} nil nil)
:else
nil))))))
state))
[]
(let [current-title (rum/react *current-title)]
[:input#draw-title.font-medium.w-48.px-2.py-1.ml-2
{:on-click (fn [e]
(util/stop e))
:placeholder "Untitled"
:auto-complete "off"
:default-value (or (and current-title (string/capitalize current-title)) "")
:on-change (fn [e]
(when-let [value (util/evalue e)]
(set-last-title! value)
(reset! *current-title value)))}]))
(rum/defc files-search < rum/reactive
[state]
[:div#search-wrapper.relative.w-full.text-gray-400.focus-within:text-gray-600
[:div.absolute.inset-y-0.flex.items-center.pointer-events-none.left-3
[:svg.h-4.w-4
{:view-box "0 0 20 20", :fill "currentColor"}
[:path
{:d
"M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z",
:clip-rule "evenodd",
:fill-rule "evenodd"}]]]
[:input.block.w-full.pl-2.sm:text-sm.sm:leading-3.mb-2.mt-2.border-none.outline-none.focus:outline-none
{:style {:padding-left "2rem"
:border-radius 0}
:placeholder "Search"
:auto-complete "off"
:on-change (fn [e]
(let [value (util/evalue e)
files @*files]
(reset! *search-files
(if (string/blank? value)
files
(search/fuzzy-search files value :limit 10)))))}]])
(rum/defcs save-button < rum/reactive
[state]
(let [unsaved? (rum/react *unsaved?)]
[:a.ml-2 {:title (if unsaved? "Save changes" "Save")
:on-click (fn [e]
(save-excalidraw! state e nil nil))}
[:div.ToolIcon__icon {:class (if unsaved? "bg-orange-400" "bg-gray-200")
:style {:width "2rem"
:height "2rem"}}
svg/save]]))
(rum/defcs files < rum/reactive
[state]
(let [all-files (rum/react *files)
search-files (rum/react *search-files)
files (if (seq search-files) search-files all-files)
current-file (rum/react *current-file)
unsaved? (rum/react *unsaved?)]
[:div.flex-row.flex.items-center
[:a.ml-2 {:title "New file"
:on-click new-file!}
[:div.ToolIcon__icon.bg-gray-200 {:style {:width "2rem"
:height "2rem"}}
svg/plus]]
(ui/dropdown-with-links
(fn [{:keys [toggle-fn]}]
[:div.ToolIcon__icon.ml-2.cursor.bg-gray-200 {:title "List files"
:on-click toggle-fn
:style {:width "2rem"
:height "2rem"}}
svg/folder])
(mapv
(fn [file]
{:title (get-file-title file)
:options {:title file
:on-click
(fn [e]
(util/stop e)
(set-last-file! file)
(reset! *current-file file)
(reset! *current-title (get-file-title file))
(reset! *search-files []))}})
files)
{:modal-class (util/hiccup->class
"origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.bg-white.w-48.dropdown-overflow-auto")
:links-header (when (>= (count all-files) 5)
(files-search))})
(save-button)
(let [links (->> [(when @*current-file
{:title "Delete"
:options {:style {:color "#db1111"}
:on-click (fn [e]
(util/stop e)
(when-let [current-file @*current-file]
(p/let [_ (file/remove-file! (state/get-current-repo)
(str config/default-draw-directory "/" current-file))]
(reset! *files (remove #(= current-file %) @*files))
(new-file!))))}})]
(remove nil?))]
(when (seq links)
(ui/dropdown-with-links
(fn [{:keys [toggle-fn]}]
[:div.ToolIcon__icon.ml-2.cursor.bg-gray-200
{:title "More options"
:on-click toggle-fn
:style {:width "2rem"
:height "2rem"}}
(svg/vertical-dots nil)])
links
{:modal-class (util/hiccup->class
"origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.bg-white.w-48.dropdown-overflow-auto")})))
(draw-title)]))
(defn- set-canvas-actions-style!
[state]
(when-let [section (first (d/by-tag "section"))]
(when (= "canvasActions-title" (d/attr section "aria-labelledby"))
(d/set-style! section "margin-top" "48px")))
state)
(rum/defcs draw-inner < rum/reactive
(mixins/keyboard-mixin (util/->system-modifier "ctrl+s")
(fn [state e]
(save-excalidraw! state e nil nil)))
(mixins/keyboard-mixin "alt+z" set-canvas-actions-style!)
{:init (fn [state]
(reset! *elements nil)
(let [[option] (:rum/args state)
file (or @*current-file
(:file option))]
(do
(reset! *current-title (get-file-title file))
(set-last-file! file))
(cond
file
(do
(reset! *file-loading? true)
(draw/load-excalidraw-file
file
(fn [data]
(let [{:keys [elements app-state]} (from-json data)]
(reset! *elements elements)
(reset! *file-loading? false)))))
:else
(when-let [elements (get-last-elements)]
;; TODO: keep this for history undo
(reset! *elements (remove #(gobj/get % "isDeleted") elements))))
(assoc state
::layout (atom [js/window.innerWidth js/window.innerHeight]))))
:did-mount set-canvas-actions-style!
:did-update set-canvas-actions-style!}
[state option]
(let [current-repo (state/sub :git/current-repo)
elements (rum/react *elements)
loading? (rum/react *file-loading?)
file (rum/react *current-file)
layout (get state ::layout)
[width height] (rum/react layout)
options (bean/->js {:zenModeEnabled true
:viewBackgroundColor "#FFF"})
excalidraw-component @*excalidraw]
[:div.draw.white-theme {:style {:background "#FFF"}}
(when (and (or (and file elements)
(nil? file))
excalidraw-component)
(excalidraw-component
{:width (get option :width width)
:height (get option :height height)
:on-resize (fn []
(reset! layout [js/window.innerWidth js/window.innerHeight]))
:on-change (or (:on-change option)
(fn [elements state]
(when (not= (bean/->clj elements)
(bean/->clj @*elements))
(reset! *unsaved? true))
(set-last-elements! elements)
(set-last-app-state! state)
(reset! *elements elements)))
:options options
:user (bean/->js {:name (or (:user-name option)
(:name (state/get-me))
(util/unique-id))})
:on-username-change (fn [])
:initial-data (or elements #js [])}))
[:div.absolute.top-4.left-4.hidden.md:block
[:div.flex.flex-row.items-center
[:a.mr-3.opacity-70.hover:opacity-100 {:href (rfe/href :home)
:title "Back to logseq"}
(svg/logo false)]
(files)
(when loading?
svg/loading)]]
(ui/notification)
(when current-repo
[:div.absolute.top-4.right-4.hidden.md:block
[:div.flex.flex-row.items-center
(repo/sync-status current-repo)
(repo/repos-dropdown true
(fn [repo]
(reset! *current-file (get-last-file repo))))]])]))
(rum/defcs draw-2 < rum/reactive
{:init (fn [state]
(let [repo (storage/get :git/current-repo)]
(let [current-title (get-last-title repo)]
(reset! *current-title current-title))
(let [current-file (or
(get-in (first (:rum/args state))
[:query-params :file])
(get-last-file repo))]
(reset! *current-file current-file)
(reset! *current-title (get-file-title current-file))))
(if (loaded?)
(set-excalidraw-component!)
(loader/load
(config/asset-uri "/static/js/excalidraw.min.js")
(fn []
(reset! *loaded? true)
(set-excalidraw-component!))))
(draw/get-all-excalidraw-files
(fn [files]
(reset! *files (distinct files))))
(state/set-draw! true)
state)
:will-unmount (fn [state]
(state/set-draw! false)
state)}
[state option]
(let [loaded? (or (loaded?)
(rum/react *loaded?))
current-repo (state/sub :git/current-repo)
component (rum/react *excalidraw)]
(if component
(let [current-file (rum/react *current-file)
current-file (or current-file
(and current-repo
(get-last-file current-repo)))]
(let [key (if current-repo
(str current-repo "-"
(or (and current-file (str "draw-" current-file))
"draw-with-no-file"))
"draw-with-no-file")]
(rum/with-key (draw-inner option) key)))
[:div.center svg/loading])))
(rum/defc draw < rum/reactive
[option]
(let [db-restoring? (state/sub :db/restoring?)]
(if db-restoring?
[:div.ls-center
(ui/loading "Loading")]
(draw-2 option))))

View File

@ -1,23 +0,0 @@
#draw {
-webkit-app-region: no-drag;
overflow: hidden;
}
#draw iframe {
width: 100%;
height: 100%;
border: none;
}
.draw {
display: flex;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.excalidraw-embed .draw {
position: relative;
}

View File

@ -131,7 +131,7 @@
(ui/loading (t :loading))]]
:else
[:div {:style {:margin-bottom (if global-graph-pages? 0 120)}}
[:div.max-w-7xl.mx-auto {:style {:margin-bottom (if global-graph-pages? 0 120)}}
main-content])]]
(right-sidebar/sidebar)]))
@ -176,7 +176,7 @@
preferred-format (state/sub [:me :preferred_format])
logged? (:name me)]
(rum/with-context [[t] i18n/*tongue-context*]
[:div.max-w-7xl.mx-auto
[:div
(cond
(and default-home
(= :home (state/get-current-route))

View File

@ -14,8 +14,9 @@
(defn content-encrypted?
[content]
(or (str/starts-with? content age-pem-header-line)
(str/starts-with? content age-version-line)))
(when content
(or (str/starts-with? content age-pem-header-line)
(str/starts-with? content age-version-line))))
(defn encrypted-db?
[repo-url]
@ -100,4 +101,4 @@
lazy-decrypt-with-user-passphrase (resolve 'frontend.extensions.age-encryption/decrypt-with-user-passphrase)
content (utf8/encode content)
decrypted (lazy-decrypt-with-user-passphrase passphrase content)]
(utf8/decode decrypted)))
(utf8/decode decrypted)))

View File

@ -0,0 +1,136 @@
(ns frontend.extensions.excalidraw
(:require [rum.core :as rum]
[goog.object :as gobj]
[frontend.rum :as r]
[frontend.util :as util :refer-macros [profile]]
[frontend.mixins :as mixins]
[frontend.storage :as storage]
[frontend.components.svg :as svg]
[cljs-bean.core :as bean]
[dommy.core :as d]
[clojure.string :as string]
[frontend.handler.notification :as notification]
[frontend.handler.draw :as draw]
[frontend.handler.file :as file]
[frontend.handler.ui :as ui-handler]
[frontend.ui :as ui]
[frontend.loader :as loader]
[frontend.config :as config]
[frontend.state :as state]
[frontend.search :as search]
[frontend.components.repo :as repo]
[promesa.core :as p]
[reitit.frontend.easy :as rfe]
["@excalidraw/excalidraw" :as Excalidraw]))
(def excalidraw (r/adapt-class (gobj/get Excalidraw "default")))
(defn from-json
[text]
(when-not (string/blank? text)
(try
(js/JSON.parse text)
(catch js/Error e
(println "from json error:")
(js/console.dir e)
(notification/show!
(util/format "Could not load this invalid excalidraw file")
:error)))))
(defonce *bounding-width (atom nil))
(defn- get-bounding-width
[ref]
(when ref
(when-let [current (gobj/get ref "current")]
(-> current
(.getBoundingClientRect)
(gobj/get "width")))))
(rum/defcs draw-inner < rum/reactive
(rum/local true ::zen-mode?)
(rum/local false ::view-mode?)
(rum/local nil ::elements)
[state data option]
(let [current-repo (state/sub :git/current-repo)
bounding-width (rum/react *bounding-width)
*zen-mode? (get state ::zen-mode?)
*view-mode? (get state ::view-mode?)
wide-mode? (state/sub :ui/wide-mode?)
*elements (get state ::elements)
file (:file option)]
(when data
[:div.overflow-hidden
[:div.my-1 {:style {:font-size 10}}
[:a.mr-2 {:on-click ui-handler/toggle-wide-mode!}
(util/format "Wide Mode (%s)" (if wide-mode? "ON" "OFF"))]
[:a.mr-2 {:on-click #(swap! *zen-mode? not)}
(util/format "Zen Mode (%s)" (if @*zen-mode? "ON" "OFF"))]
[:a.mr-2 {:on-click #(swap! *view-mode? not)}
(util/format "View Mode (%s)" (if @*view-mode? "ON" "OFF"))]]
[:div
(excalidraw
(merge
{:on-change (fn [elements state]
(let [elements->clj (bean/->clj elements)]
(when (and (seq elements->clj)
(not= elements @*elements))
(let [state (bean/->clj state)]
(draw/save-excalidraw!
file
(-> {:type "excalidraw"
:version 2
:source config/website
:elements elements
:appState (select-keys state [:gridSize :viewBackgroundColor])}
bean/->js
(js/JSON.stringify)))
(reset! *elements elements)))))
:zen-mode-enabled @*zen-mode?
:view-mode-enabled @*view-mode?
:grid-mode-enabled false
:initial-data data}
(if wide-mode?
{:height 650}
{:width 800
:height 500})))]])))
(rum/defcs draw-container < rum/reactive
{:init (fn [state]
(let [[option] (:rum/args state)
file (:file option)
*data (atom nil)
*loading? (atom true)]
(when file
(draw/load-excalidraw-file
file
(fn [data]
(let [data (from-json data)]
(reset! *data data)
(reset! *loading? false)))))
(assoc state
::data *data
::loading? *loading?)))}
[state option]
(let [*data (get state ::data)
*loading? (get state ::loading?)
loading? (rum/react *loading?)
data (rum/react *data)
db-restoring? (state/sub :db/restoring?)]
(when (:file option)
(cond
db-restoring?
[:div.ls-center
(ui/loading "Loading")]
(false? loading?)
(draw-inner data option)
:else ; loading
nil))))
(rum/defc draw < rum/reactive
[option]
(let [repo (state/get-current-repo)
granted? (state/sub [:nfs/user-granted? repo])]
(when-not (and (config/local-db? repo) (not granted?))
(draw-container option))))

View File

@ -133,11 +133,13 @@
not-changed? (= last-modified-at local-last-modified-at)
format (-> (util/get-file-ext path)
(config/get-file-format))
pending-writes (state/get-write-chan-length)]
pending-writes (state/get-write-chan-length)
draw? (and path (string/ends-with? path ".excalidraw"))]
(if (and local-content (or old-content
;; temporally fix
(and path (string/ends-with? path ".excalidraw"))) new?
draw?) new?
(or
draw?
;; Writing not finished
(> pending-writes 0)
;; not changed by other editors

View File

@ -14,37 +14,6 @@
[cljs-time.core :as t]
[cljs-time.coerce :as tc]))
;; state
(defonce *files (atom nil))
(defonce *current-file (atom nil))
(defonce *current-title (atom ""))
(defonce *file-loading? (atom nil))
(defonce *elements (atom nil))
(defonce *unsaved? (atom false))
(defonce *search-files (atom []))
(defonce *saving-title (atom nil))
(defonce *excalidraw (atom nil))
;; TODO: refactor
(defonce draw-state :draw-state)
(defn get-draw-state []
(storage/get draw-state))
(defn set-draw-state! [value]
(storage/set draw-state value))
(defn set-k
[k v]
(when-let [repo (state/get-current-repo)]
(let [state (get-draw-state)]
(let [new-state (assoc-in state [repo k] v)]
(set-draw-state! new-state)))))
(defn set-last-file!
[value]
(set-k :last-file value))
;; excalidraw
(defn create-draws-directory!
[repo]
(when repo
@ -55,8 +24,8 @@
(fn [_error] nil)))))
(defn save-excalidraw!
[file data ok-handler]
(let [path (str config/default-draw-directory "/" file)
[file data]
(let [path file
repo (state/get-current-repo)]
(when repo
(let [repo-dir (config/get-repo-dir repo)]
@ -65,7 +34,6 @@
(create-draws-directory! repo)
(fs/write-file! repo repo-dir path data nil)
(git-handler/git-add repo path)
(ok-handler file)
(db/transact! repo
[{:file/path path
:page/name file
@ -75,58 +43,30 @@
(prn "Write file failed, path: " path ", data: " data)
(js/console.dir error))))))))
(defn get-all-excalidraw-files
[ok-handler]
(when-let [repo (state/get-current-repo)]
(p/let [_ (create-draws-directory! repo)]
(let [dir (str (config/get-repo-dir repo)
"/"
config/default-draw-directory)]
(util/p-handle
(fs/readdir dir)
(fn [files]
(let [files (-> (filter #(string/ends-with? % ".excalidraw") files)
(distinct)
(sort)
(reverse))]
(ok-handler files)))
(fn [error]
(js/console.dir error)))))))
(defn load-excalidraw-file
[file ok-handler]
(when-let [repo (state/get-current-repo)]
(util/p-handle
(file-handler/load-file repo (str config/default-draw-directory "/" file))
(file-handler/load-file repo file)
(fn [content]
(ok-handler content))
(fn [error]
(prn "Error loading " file ": "
error)))))
(println "Error loading " file ": "
error)))))
(defonce default-content
(util/format
"{\n \"type\": \"excalidraw\",\n \"version\": 2,\n \"source\": \"%s\",\n \"elements\": [],\n \"appState\": {\n \"viewBackgroundColor\": \"#FFF\",\n \"gridSize\": null\n }\n}"
config/website))
(defn title->file-name
[title]
(when (not (string/blank? title))
(let [title (string/lower-case (string/replace title " " "-"))]
(str (date/get-date-time-string-2) "-" title ".excalidraw"))))
(defn file-name
[]
(str (date/get-date-time-string-2) ".excalidraw"))
(defn create-draw-with-default-content
[current-file ok-handler]
[current-file]
(when-let [repo (state/get-current-repo)]
(p/let [exists? (fs/file-exists? (config/get-repo-dir repo)
(str config/default-draw-directory current-file))]
(when-not exists?
(save-excalidraw! current-file default-content
(fn [file]
(reset! *files
(distinct (conj @*files file)))
(reset! *current-file file)
(reset! *unsaved? false)
(set-last-file! file)
(reset! *saving-title nil)
(ok-handler)))))))
(save-excalidraw! current-file default-content)))))

View File

@ -10,7 +10,6 @@
[frontend.handler.repo :as repo-handler]
[frontend.handler.file :as file-handler]
[frontend.handler.notification :as notification]
[frontend.handler.draw :as draw]
[frontend.handler.expand :as expand]
[frontend.handler.block :as block-handler]
[frontend.format.mldoc :as mldoc]
@ -2112,22 +2111,6 @@
(get-link format link label)
format
{:last-pattern (str commands/slash "link")})))
:draw
(when-not (string/blank? (:title m))
(let [file (draw/title->file-name (:title m))
value (util/format
"[[%s]]\n<iframe class=\"draw-iframe\" src=\"/#/draw?file=%s\" width=\"100%\" height=\"400\" frameborder=\"0\" allowfullscreen></iframe>"
file
file)]
(insert-command! id
value
format
{:last-pattern (str commands/slash "draw ")})
(draw/create-draw-with-default-content
file
(fn []
(let [input (gdom/getElement "download")]
(.click input))))))
nil)
(state/set-editor-show-input! nil)

View File

@ -92,3 +92,12 @@
;; (state/get-custom-css-link)
)]
(util/add-style! style)))
(defn toggle-wide-mode!
[]
(let [wide? (state/get-wide-mode?)
elements (array-seq (js/document.getElementsByClassName "cp__sidebar-main-content"))
max-width (if wide? "var(--ls-main-content-max-width)" "100%")]
(when-let [element (first elements)]
(dom/set-style! element :max-width max-width))
(state/toggle-wide-mode!)))

View File

@ -51,6 +51,8 @@
(enable-when-not-editing-mode! ui-handler/toggle-contents!)
(or (shortcut :editor/toggle-settings) "t s")
(enable-when-not-editing-mode! ui-handler/toggle-settings-modal!)
(or (shortcut :ui/toggle-wide-mode) "t w")
(enable-when-not-editing-mode! ui-handler/toggle-wide-mode!)
(or (shortcut :ui/toggle-between-page-and-file) "s")
(enable-when-not-editing-mode! route-handler/toggle-between-page-and-file!)
(or (shortcut :git/commit) "c")

View File

@ -4,7 +4,6 @@
[frontend.components.file :as file]
[frontend.components.page :as page]
[frontend.components.diff :as diff]
[frontend.components.draw :as draw]
[frontend.components.journal :as journal]
[frontend.components.settings :as settings]
[frontend.components.external :as external]
@ -52,10 +51,6 @@
{:name :diff
:view diff/diff}]
["/draw"
{:name :draw
:view draw/draw}]
["/settings"
{:name :settings
:view settings/settings}]

View File

@ -54,6 +54,7 @@
:ui/sidebar-open? false
:ui/left-sidebar-open? false
:ui/theme (or (storage/get :ui/theme) "dark")
:ui/wide-mode? false
;; :show-all, :hide-block-body, :hide-block-children
:ui/cycle-collapse :show-all
:ui/collapsed-blocks {}
@ -899,10 +900,13 @@
(set-state! :indexeddb/support? value))
(defn set-modal!
[modal-panel-content]
(swap! state assoc
:modal/show? (boolean modal-panel-content)
:modal/panel-content modal-panel-content))
([modal-panel-content]
(set-modal! modal-panel-content false))
([modal-panel-content fullscreen?]
(swap! state assoc
:modal/show? (boolean modal-panel-content)
:modal/panel-content modal-panel-content
:modal/fullscreen? fullscreen?)))
(defn close-modal!
[]
@ -992,6 +996,14 @@
[]
(get-in @state [:repo/changed-files (get-current-repo)]))
(defn get-wide-mode?
[]
(:ui/wide-mode? @state))
(defn toggle-wide-mode!
[]
(update-state! :ui/wide-mode? not))
(defn set-online!
[value]
(set-state! :network/online? value))

View File

@ -424,7 +424,7 @@
[:div.absolute.inset-0.opacity-75]])
(rum/defc modal-panel
[panel-content transition-state close-fn]
[panel-content transition-state close-fn fullscreen?]
[:div.ui__modal-panel.transform.transition-all.sm:min-w-lg.sm
{:class (case transition-state
"entering" "ease-out duration-300 opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
@ -444,7 +444,7 @@
:stroke-linejoin "round"
:stroke-linecap "round"}]]]]
[:div.panel-content
[:div {:class (if fullscreen? "" "panel-content")}
(panel-content close-fn)]])
(rum/defc modal < rum/reactive
@ -458,6 +458,7 @@
:outside? false)))
[]
(let [modal-panel-content (state/sub :modal/panel-content)
fullscreen? (state/sub :modal/fullscreen?)
show? (boolean modal-panel-content)
close-fn #(state/close-modal!)
modal-panel-content (or modal-panel-content (fn [close] [:div]))]
@ -470,7 +471,7 @@
(css-transition
{:in show? :timeout 0}
(fn [state]
(modal-panel modal-panel-content state close-fn)))]))
(modal-panel modal-panel-content state close-fn fullscreen?)))]))
(defn make-confirm-modal
[{:keys [tag title sub-title sub-checkbox? on-cancel on-confirm] :as opts}]