diff --git a/src/cljs/athens/events/remote.cljs b/src/cljs/athens/events/remote.cljs
index 1c6fac5185..4e65e78317 100644
--- a/src/cljs/athens/events/remote.cljs
+++ b/src/cljs/athens/events/remote.cljs
@@ -307,3 +307,11 @@
;; Remove the server event after everything is done.
true
(into [[:remote/clear-server-event event]]))]]})))
+
+;; Subs
+
+(rf/reg-sub
+ :remote/event-sync-memory-log
+ (fn [db _]
+ (when-let [event-sync (:remote/event-sync db)]
+ (event-sync/stage-log event-sync :memory))))
diff --git a/src/cljs/athens/router.cljs b/src/cljs/athens/router.cljs
index ebc2a84d9d..3943195fe3 100644
--- a/src/cljs/athens/router.cljs
+++ b/src/cljs/athens/router.cljs
@@ -156,6 +156,7 @@
(def routes
["/"
["" {:name :home}]
+ ["quick-capture" {:name :quickcapture}]
["settings" {:name :settings}]
["pages" {:name :pages}]
["page-t/:title" {:name :page-by-title}]
diff --git a/src/cljs/athens/views.cljs b/src/cljs/athens/views.cljs
index 7b7652a74f..8c1a546b9d 100644
--- a/src/cljs/athens/views.cljs
+++ b/src/cljs/athens/views.cljs
@@ -13,8 +13,10 @@
[athens.views.app-toolbar :as app-toolbar]
[athens.views.athena :refer [athena-component]]
[athens.views.help :refer [help-popup]]
+ [athens.views.hoc.perf-mon :as perf-mon]
[athens.views.left-sidebar :as left-sidebar]
[athens.views.pages.core :as pages]
+ [athens.views.pages.quick-capture :as quick-capture]
[athens.views.pages.settings :as settings]
[athens.views.right-sidebar.core :as right-sidebar]
[re-frame.core :as rf]))
@@ -34,6 +36,7 @@
[]
(let [loading (rf/subscribe [:loading?])
modal (rf/subscribe [:modal])
+ route-name (rf/subscribe [:current-route/name])
right-sidebar-open? (rf/subscribe [:right-sidebar/open])
right-sidebar-width (rf/subscribe [:right-sidebar/width])
settings-open? (rf/subscribe [:settings/open?])]
@@ -42,52 +45,57 @@
(zoom))
[:> ChakraProvider {:theme theme,
:bg "background.basement"}
- [:> ContextMenuProvider
- [:> LayoutProvider
- [help-popup]
- [alert]
- [athena-component]
- (cond
- (and @loading @modal) [db-modal/window]
+ [:> LayoutProvider
+ [:> ContextMenuProvider
+ (if
+ (= @route-name :quickcapture)
+ [perf-mon/hoc-perfmon-no-new-tx {:span-name "quick-capture"}
+ [:f> quick-capture/quick-capture]]
+ [:<>
+ [help-popup]
+ [alert]
+ [athena-component]
+ (cond
+ (and @loading @modal) [db-modal/window]
- @loading
- [:> Center {:height "100vh"}
- [:> Flex {:width 28
- :flexDirection "column"
- :gap 2
- :color "foreground.secondary"
- :borderRadius "lg"
- :placeItems "center"
- :placeContent "center"
- :height 28}
- [:> Spinner {:size "xl"}]]]
+ @loading
+ [:> Center {:height "100vh"}
+ [:> Flex {:width 28
+ :flexDirection "column"
+ :gap 2
+ :color "foreground.secondary"
+ :borderRadius "lg"
+ :placeItems "center"
+ :placeContent "center"
+ :height 28}
+ [:> Spinner {:size "xl"}]]]
- :else [:<>
- (when @modal
- [db-modal/window])
- (when @settings-open?
- [settings/page])
- [:> VStack {:overscrollBehavior "contain"
- :id "main-layout"
- :spacing 0
- :overflowY "auto"
- :height "100vh"
- :bg "background.floor"
- :transitionDuration "fast"
- :transitionProperty "background"
- :transitionTimingFunction "ease-in-out"
- :align "stretch"
- :position "relative"}
- [app-toolbar/app-toolbar]
- [:> HStack {:overscrollBehavior "contain"
- :align "stretch"
- :spacing 0
- :flex 1}
- [left-sidebar/left-sidebar]
- [:> MainContent {:rightSidebarWidth @right-sidebar-width
- :isRightSidebarOpen @right-sidebar-open?}
- [pages/view]]
- [:> RightSidebarResizeControl {:rightSidebarWidth @right-sidebar-width
- :isRightSidebarOpen @right-sidebar-open?
- :onResizeSidebar #(rf/dispatch [:right-sidebar/set-width %])}]
- [right-sidebar/right-sidebar]]]])]]]])))
+ :else [:<>
+ (when @modal
+ [db-modal/window])
+ (when @settings-open?
+ [settings/page])
+ [:> VStack {:overscrollBehavior "contain"
+ :id "main-layout"
+ :spacing 0
+ :overflowY "auto"
+ :height "100vh"
+ :bg "background.floor"
+ :transitionDuration "fast"
+ :transitionProperty "background"
+ :transitionTimingFunction "ease-in-out"
+ :align "stretch"
+ :position "relative"}
+ [app-toolbar/app-toolbar]
+ [:> HStack {:overscrollBehavior "contain"
+ :align "stretch"
+ :spacing 0
+ :flex 1}
+ [left-sidebar/left-sidebar]
+ [:> MainContent {:rightSidebarWidth @right-sidebar-width
+ :isRightSidebarOpen @right-sidebar-open?}
+ [pages/view]]
+ [:> RightSidebarResizeControl {:rightSidebarWidth @right-sidebar-width
+ :isRightSidebarOpen @right-sidebar-open?
+ :onResizeSidebar #(rf/dispatch [:right-sidebar/set-width %])}]
+ [right-sidebar/right-sidebar]]]])])]]]])))
diff --git a/src/cljs/athens/views/pages/core.cljs b/src/cljs/athens/views/pages/core.cljs
index 8c39b43c09..a0ba996607 100644
--- a/src/cljs/athens/views/pages/core.cljs
+++ b/src/cljs/athens/views/pages/core.cljs
@@ -6,6 +6,7 @@
[athens.views.pages.daily-notes :as daily-notes]
[athens.views.pages.graph :as graph]
[athens.views.pages.page :as page]
+ [athens.views.pages.quick-capture :as quick-capture]
[re-frame.core :as rf]))
@@ -20,6 +21,8 @@
:title "Reconnecting to server..."})))
[:<>
(case @route-name
+ :quickcapture [perf-mon/hoc-perfmon-no-new-tx {:span-name "quick-capture"}
+ [quick-capture/quick-capture]]
:pages [perf-mon/hoc-perfmon-no-new-tx {:span-name "pages/all-pages"}
[all-pages/page]]
:page [perf-mon/hoc-perfmon {:span-name "pages/page"}
diff --git a/src/cljs/athens/views/pages/quick_capture.cljs b/src/cljs/athens/views/pages/quick_capture.cljs
new file mode 100644
index 0000000000..2029b1e214
--- /dev/null
+++ b/src/cljs/athens/views/pages/quick_capture.cljs
@@ -0,0 +1,71 @@
+(ns athens.views.pages.quick-capture
+ (:require
+ [athens.common-events.graph.ops :as graph-ops]
+ [athens.common.utils :as utils]
+ [athens.dates :as dates]
+ [athens.electron.db-menu.core :refer [db-menu]]
+ [clojure.data :as data]
+ ["@chakra-ui/react" :refer [Button]]
+ ["/components/Quick/QuickCapture" :refer [QuickCapture]]
+ ["react" :as react]
+ [reagent.core :as r]
+ [re-frame.core :as rf]))
+
+
+(defn quick-capture
+ []
+ (let [[notes setNotes] (react/useState [])
+ [lastSyncTime, setLastSyncTime] (react/useState nil)
+ memory-log @(rf/subscribe [:remote/event-sync-memory-log])
+ unsynced-uids (->> memory-log
+ (map #(-> % second :event/op :op/consequences first :op/args :block/uid))
+ (filter some?)
+ set)
+ unsynced-uids-notes (->> notes
+ (filter #(-> % :isSaved false?))
+ (map :uid)
+ set)
+ mock-sync (fn [notes]
+ (prn notes)
+ (setNotes [])
+ (setLastSyncTime (js/Date.now)))
+ mock-add-item (fn [string]
+ ;; Send via reframe.
+ (rf/dispatch [:properties/update-in [:node/title (:title (dates/get-day))]
+ ["last-qc-message"]
+ ;; TODO: need to support first/last, and deep path positions
+ #_["Quick Capture" current-username :last]
+ (fn [db uid]
+ (let [new-note (merge {:string string :timestamp (js/Date.now) :uid uid}
+ (when-not memory-log {:isSaved true}))]
+ ;; Save on local comp state.
+ (setNotes (conj notes new-note))
+ ;; Save on graph.
+ [(graph-ops/build-block-save-op db uid string)]))]))]
+
+ (when memory-log
+ (let [[a b] (data/diff unsynced-uids unsynced-uids-notes)]
+ (when (or a b)
+ (println notes a b)
+ (setNotes (map (fn [{:keys [uid] :as x}]
+ (cond
+ (and a (a uid)) (assoc x :isSaved false)
+ (and b (b uid)) (assoc x :isSaved true)
+ :else x))
+ notes)))))
+
+ [:<> [:> QuickCapture {:dbMenu (r/as-element [db-menu])
+ :notes notes
+ :lastSyncTime lastSyncTime
+ :onAddItem mock-add-item
+ :newEventId utils/gen-event-id}]
+ [:> Button {:position "fixed"
+ :variant "text"
+ :size "xs"
+ :left "50%"
+ :top 5
+ :transform "translateX(-50%)"
+ :onClick mock-sync} "mock update"]]))
+
+;; state
+;; unsaved changes
\ No newline at end of file
diff --git a/src/js/components/Quick/QuickCapture.tsx b/src/js/components/Quick/QuickCapture.tsx
new file mode 100644
index 0000000000..5d60114214
--- /dev/null
+++ b/src/js/components/Quick/QuickCapture.tsx
@@ -0,0 +1,221 @@
+import React from 'react'
+import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Box, Button, FormControl, HStack, Text, Textarea, VStack } from "@chakra-ui/react"
+import { CheckmarkCircleFillIcon } from '@/Icons/Icons'
+import { AnimatePresence, motion } from 'framer-motion'
+
+const FloatingInput = (props) => {
+ const { onSubmit } = props
+ const [string, setString] = React.useState("")
+ const inputRef = React.useRef(null)
+
+ const handleSubmit = (e) => {
+ if (string.length) {
+ e.preventDefault()
+ onSubmit(string)
+ setString("")
+ inputRef.current.focus()
+ }
+ }
+
+ React.useEffect(() => {
+ inputRef.current.focus()
+ }, [])
+
+ return
+
+
+
+}
+
+const QueuedNote = (props) => {
+ const { string, timestamp, isSaved } = props;
+ const itemRef = React.useRef(null)
+
+ React.useLayoutEffect(() => {
+ if (itemRef.current) {
+ itemRef.current.scrollIntoView({ behavior: "smooth", block: "start" })
+ }
+ }, [])
+
+ return (
+
+
+ {new Date(timestamp).toLocaleDateString()}
+
+ {isSaved ? (
+ <>Saved >) : "Waiting to save"}
+
+ {string}
+ );
+}
+
+const Message = ({ children, ...props }) => {children};
+
+export const QuickCapture = ({ dbMenu, notes, onAddItem, lastSyncTime }) => {
+ const [isSwitchDialogOpen, setIsSwitchDialogOpen] = React.useState(false);
+ const containerRef = React.useRef(null)
+ const confirmationCancelRef = React.useRef(null)
+
+ return <>
+
+
+
+
+ {(notes.length) && Today}
+ {lastSyncTime && Last synced: {new Date(lastSyncTime).toLocaleDateString()}}
+ {notes.length && notes.map((note, index) => )}
+ {!(notes.length || lastSyncTime) && Save a message to today's Daily Notes}
+
+
+
+
+
+ {dbMenu}
+
+
+ setIsSwitchDialogOpen(false)}
+ leastDestructiveRef={confirmationCancelRef}
+ >
+
+
+ Switch to Athens?
+
+ Notes that have not been synced will be lost.
+
+
+
+
+
+
+
+
+ >
+}
\ No newline at end of file
diff --git a/src/js/theme/theme.js b/src/js/theme/theme.js
index 6d4ebf9028..83248cf1ce 100644
--- a/src/js/theme/theme.js
+++ b/src/js/theme/theme.js
@@ -524,9 +524,12 @@ const components = {
},
dialog: {
shadow: "dialog",
- border: "1px solid",
- borderColor: 'separator.divider',
- bg: 'background.upper'
+ border: "none",
+ maxWidth: 'calc(100% - 2rem)',
+ maxHeight: 'calc(100% - 2rem)',
+ overflow: 'auto',
+ margin: "auto",
+ bg: 'background.upper',
}
}
},