Add home page

pull/645/head
Tienson Qin 2020-03-06 16:13:02 +08:00
parent c3419afbd4
commit 877f36d63f
19 changed files with 727 additions and 864 deletions

View File

@ -1,24 +1,24 @@
{:deps
{org.clojure/clojure {:mvn/version "RELEASE"}
org.clojure/clojurescript {:mvn/version "RELEASE"}
cljs-bean {:mvn/version "1.5.0"}
uix.core {:git/url "https://github.com/roman01la/uix.git"
:deps/root "core"
:sha "46011c8a2f1a05025971af8e26b8b6704520383c"}
uix.dom {:git/url "https://github.com/roman01la/uix.git"
:deps/root "dom"
:sha "46011c8a2f1a05025971af8e26b8b6704520383c"}
{;; dev
thheller/shadow-cljs {:mvn/version "RELEASE"}
cider/cider-nrepl {:mvn/version "0.23.0-SNAPSHOT"}
binaryage/devtools {:mvn/version "0.9.10"}
rum {:mvn/version "0.11.4"}
org.clojure/clojure {:mvn/version "RELEASE"}
org.clojure/clojurescript {:mvn/version "RELEASE"}
cljs-bean {:mvn/version "1.5.0"}
uix.core {:git/url "https://github.com/tiensonqin/uix.git"
:deps/root "core"
:sha "f6d82d12d62c13ff1894ac9dc2dd894a3521cd3d"}
uix.dom {:git/url "https://github.com/roman01la/uix.git"
:deps/root "dom"
:sha "46011c8a2f1a05025971af8e26b8b6704520383c"}
datascript {:mvn/version "0.18.9"}
funcool/promesa {:mvn/version "4.0.2"}
medley {:mvn/version "1.2.0"}
}
metosin/reitit {:mvn/version "0.3.10"}
metosin/reitit-spec {:mvn/version "0.3.10"}
metosin/reitit-frontend {:mvn/version "0.3.10"}}
:paths
["src"

View File

@ -1,98 +1,99 @@
(ns frontend.components.agenda
(:require [rum.core :as rum]
[frontend.mui :as mui]
[frontend.util :as util]
[frontend.handler :as handler]
[frontend.format.org.block :as block]
[frontend.state :as state]
[clojure.string :as string]
[frontend.format.org-mode :as org]))
(rum/defc timestamps-cp
[timestamps]
[:ul
(for [[type {:keys [date time]}] timestamps]
(let [{:keys [year month day]} date
{:keys [hour min]} time]
[:li {:key type}
[:span {:style {:margin-right 6}} type]
[:span (if time
(str year "-" month "-" day " " hour ":" min)
(str year "-" month "-" day))]]))])
(rum/defc title-cp
[title]
(let [title-json (js/JSON.stringify (clj->js title))
html (org/inline-list->html title-json)]
(util/raw-html html)))
(rum/defc marker-cp
[marker]
[:span {:class (str "marker-" (string/lower-case marker))
:style {:margin-left 8}}
(if (contains? #{"DOING" "IN-PROGRESS"} marker)
(str " (" marker ")"))])
(rum/defc tags-cp
[tags]
[:span
(for [tag tags]
[:span.tag {:key tag}
[:span
tag]])])
(rum/defc agenda
[tasks]
[:span "TBD"]
;; [:div#agenda
;; (if (seq tasks)
;; (for [[section-name tasks] tasks]
;; [:div.section {:key (str "section-" section-name)}
;; [:h3 section-name]
;; (mui/list
;; (for [[idx {:keys [marker title priority level tags children timestamps meta]}] (util/indexed (block/sort-tasks tasks))]
;; (mui/list-item
;; {:key (str "task-" section-name "-" idx)
;; :style {:padding-left 8
;; :padding-right 8}}
;; [:div.column
;; [:div.row {:style {:align-items "center"}}
;; (let [marker (case marker
;; (list "DOING" "IN-PROGRESS" "TODO")
;; (mui/checkbox {:checked false
;; :on-change (fn [_]
;; ;; FIXME: Log timestamp
;; (handler/check marker (:pos meta)))
;; :color "primary"
;; :style {:padding 0}})
;; "WAIT"
;; [:span {:style {:font-weight "bold"}}
;; "WAIT"]
;; "DONE"
;; (mui/checkbox {:checked true
;; :on-change (fn [_]
;; ;; FIXME: rollback to the last state if exists.
;; ;; it must not be `TODO`
;; (handler/uncheck (:pos meta)))
;; :color "primary"
;; :style {:padding 0}})
;; nil)]
;; (if priority
;; (mui/badge {:badge-content (string/lower-case priority)
;; :overlay "circle"}
;; marker)
;; marker))
;; [:div.row {:style {:margin-left 8}}
;; (title-cp title)
;; (marker-cp marker)
;; (when (seq tags)
;; (tags-cp tags))]]
;; (when (seq timestamps)
;; (timestamps-cp timestamps))
;; ])))])
;; "Empty")]
;; (:require [rum.core :as rum]
;; [frontend.mui :as mui]
;; [frontend.util :as util]
;; [frontend.handler :as handler]
;; [frontend.format.org.block :as block]
;; [frontend.state :as state]
;; [clojure.string :as string]
;; [frontend.format.org-mode :as org])
)
;; (rum/defc timestamps-cp
;; [timestamps]
;; [:ul
;; (for [[type {:keys [date time]}] timestamps]
;; (let [{:keys [year month day]} date
;; {:keys [hour min]} time]
;; [:li {:key type}
;; [:span {:style {:margin-right 6}} type]
;; [:span (if time
;; (str year "-" month "-" day " " hour ":" min)
;; (str year "-" month "-" day))]]))])
;; (rum/defc title-cp
;; [title]
;; (let [title-json (js/JSON.stringify (clj->js title))
;; html (org/inline-list->html title-json)]
;; (util/raw-html html)))
;; (rum/defc marker-cp
;; [marker]
;; [:span {:class (str "marker-" (string/lower-case marker))
;; :style {:margin-left 8}}
;; (if (contains? #{"DOING" "IN-PROGRESS"} marker)
;; (str " (" marker ")"))])
;; (rum/defc tags-cp
;; [tags]
;; [:span
;; (for [tag tags]
;; [:span.tag {:key tag}
;; [:span
;; tag]])])
;; (rum/defc agenda
;; [tasks]
;; [:span "TBD"]
;; ;; [:div#agenda
;; ;; (if (seq tasks)
;; ;; (for [[section-name tasks] tasks]
;; ;; [:div.section {:key (str "section-" section-name)}
;; ;; [:h3 section-name]
;; ;; (mui/list
;; ;; (for [[idx {:keys [marker title priority level tags children timestamps meta]}] (util/indexed (block/sort-tasks tasks))]
;; ;; (mui/list-item
;; ;; {:key (str "task-" section-name "-" idx)
;; ;; :style {:padding-left 8
;; ;; :padding-right 8}}
;; ;; [:div.column
;; ;; [:div.row {:style {:align-items "center"}}
;; ;; (let [marker (case marker
;; ;; (list "DOING" "IN-PROGRESS" "TODO")
;; ;; (mui/checkbox {:checked false
;; ;; :on-change (fn [_]
;; ;; ;; FIXME: Log timestamp
;; ;; (handler/check marker (:pos meta)))
;; ;; :color "primary"
;; ;; :style {:padding 0}})
;; ;; "WAIT"
;; ;; [:span {:style {:font-weight "bold"}}
;; ;; "WAIT"]
;; ;; "DONE"
;; ;; (mui/checkbox {:checked true
;; ;; :on-change (fn [_]
;; ;; ;; FIXME: rollback to the last state if exists.
;; ;; ;; it must not be `TODO`
;; ;; (handler/uncheck (:pos meta)))
;; ;; :color "primary"
;; ;; :style {:padding 0}})
;; ;; nil)]
;; ;; (if priority
;; ;; (mui/badge {:badge-content (string/lower-case priority)
;; ;; :overlay "circle"}
;; ;; marker)
;; ;; marker))
;; ;; [:div.row {:style {:margin-left 8}}
;; ;; (title-cp title)
;; ;; (marker-cp marker)
;; ;; (when (seq tags)
;; ;; (tags-cp tags))]]
;; ;; (when (seq timestamps)
;; ;; (timestamps-cp timestamps))
;; ;; ])))])
;; ;; "Empty")]
;; )

View File

@ -1,66 +1,67 @@
(ns frontend.components.file
(:require [rum.core :as rum]
[frontend.mui :as mui]
["@material-ui/core/colors" :as colors]
[frontend.state :as state]
[frontend.util :as util]
[frontend.handler :as handler]
[clojure.string :as string]))
;; (:require [rum.core :as rum]
;; [frontend.mui :as mui]
;; ["@material-ui/core/colors" :as colors]
;; [frontend.state :as state]
;; [frontend.util :as util]
;; [frontend.handler :as handler]
;; [clojure.string :as string])
)
(rum/defc files-list
[current-repo files]
[:div
(if (seq files)
(let [files-set (set files)
prefix [(files-set "tasks.org")]
files (->> (remove (set prefix) files)
(concat prefix)
(remove nil?))]
(mui/list
(for [file files]
(mui/list-item
{:button true
:key file
:style {:overflow "hidden"}
:on-click (fn []
(handler/load-file current-repo file)
(handler/toggle-drawer? false))}
(mui/list-item-text file)))))
"Loading...")])
;; (rum/defc files-list
;; [current-repo files]
;; [:div
;; (if (seq files)
;; (let [files-set (set files)
;; prefix [(files-set "tasks.org")]
;; files (->> (remove (set prefix) files)
;; (concat prefix)
;; (remove nil?))]
;; (mui/list
;; (for [file files]
;; (mui/list-item
;; {:button true
;; :key file
;; :style {:overflow "hidden"}
;; :on-click (fn []
;; (handler/load-file current-repo file)
;; (handler/toggle-drawer? false))}
;; (mui/list-item-text file)))))
;; "Loading...")])
(rum/defc edit < rum/reactive
[]
(let [state (rum/react state/state)
{:keys [current-repo current-file contents]} state]
(mui/container
{:id "root-container"
:style {:display "flex"
:justify-content "center"
:margin-top 64}}
[:div.column
(let [paths [:editing-files current-file]]
(mui/textarea {:style {:margin-bottom 12
:padding 8
:min-height 300}
:auto-focus true
:on-change (fn [event]
(let [v (util/evalue event)]
(swap! state/state assoc-in paths v)))
:default-value (get contents current-file)
:value (get-in state/state paths)}))
(let [path [:commit-message current-file]]
(mui/text-field {:id "standard-basic"
:style {:margin-bottom 12}
:label "Commit message"
:auto-focus true
:on-change (fn [event]
(let [v (util/evalue event)]
(when-not (string/blank? v)
(swap! state/state assoc-in path v))))
:default-value (str "Update " current-file)
:value (get-in state/state path)}))
(mui/button {:variant "contained"
:color "primary"
:on-click (fn []
(handler/alter-file current-repo current-file))}
"Submit")])))
;; (rum/defc edit < rum/reactive
;; []
;; (let [state (rum/react state/state)
;; {:keys [current-repo current-file contents]} state]
;; (mui/container
;; {:id "root-container"
;; :style {:display "flex"
;; :justify-content "center"
;; :margin-top 64}}
;; [:div.column
;; (let [paths [:editing-files current-file]]
;; (mui/textarea {:style {:margin-bottom 12
;; :padding 8
;; :min-height 300}
;; :auto-focus true
;; :on-change (fn [event]
;; (let [v (util/evalue event)]
;; (swap! state/state assoc-in paths v)))
;; :default-value (get contents current-file)
;; :value (get-in state/state paths)}))
;; (let [path [:commit-message current-file]]
;; (mui/text-field {:id "standard-basic"
;; :style {:margin-bottom 12}
;; :label "Commit message"
;; :auto-focus true
;; :on-change (fn [event]
;; (let [v (util/evalue event)]
;; (when-not (string/blank? v)
;; (swap! state/state assoc-in path v))))
;; :default-value (str "Update " current-file)
;; :value (get-in state/state path)}))
;; (mui/button {:variant "contained"
;; :color "primary"
;; :on-click (fn []
;; (handler/alter-file current-repo current-file))}
;; "Submit")])))

View File

@ -1,94 +1,220 @@
(ns frontend.components.home
(:require [rum.core :as rum]
[frontend.mui :as mui]
["@material-ui/core/colors" :as colors]
[frontend.state :as state]
(:require [frontend.state :as state]
[frontend.util :as util]
[frontend.handler :as handler]
[frontend.components.agenda :as agenda]
[frontend.components.file :as file]
[frontend.components.settings :as settings]
[frontend.components.repo :as repo]
[frontend.ui :as ui]
[frontend.hooks :as hooks]
[uix.core.alpha :as uix]
;; [frontend.components.agenda :as agenda]
;; [frontend.components.file :as file]
;; [frontend.components.settings :as settings]
;; [frontend.components.repo :as repo]
[frontend.format :as format]
[clojure.string :as string]))
[clojure.string :as string])
)
(rum/defc content-html
< {:did-mount (fn [state]
(doseq [block (-> (js/document.querySelectorAll "pre code")
(array-seq))]
(js/hljs.highlightBlock block))
state)}
[current-file html-content]
[:div
(mui/link {:style {:float "right"}
:on-click (fn []
(handler/change-page :edit-file))}
"edit")
(util/raw-html html-content)])
(rum/defc home < rum/reactive
(defn home
[]
(let [state (rum/react state/state)
{:keys [user tokens repos repo-url github-token github-repo contents loadings current-repo current-file width drawer? tasks cloning?]} state
current-repo (or current-repo
(when-let [first-repo (first (keys repos))]
(handler/set-current-repo first-repo)
first-repo))
files (get-in state [:repos current-repo :files])
cloned? (get-in state [:repos current-repo :cloned?])
loading? (get loadings current-file)
width (or width (util/get-width))
mobile? (and width (<= width 600))]
(prn {:current-repo current-repo
:cloned? cloned?})
(mui/container
{:id "root-container"
:style {:display "flex"
:justify-content "center"
;; TODO: fewer spacing for mobile, 24px
:margin-top 64}}
(cond
(nil? user)
(mui/button {:variant "contained"
:color "primary"
:start-icon (mui/github-icon)
:href "/login/github"}
"Login with Github")
(let [ref (uix/ref nil)
open? (uix/state false)
close-fn (fn [] (reset! open? false))
open-fn (fn [] (reset! open? true))]
(prn "open: " open?)
;; effects
(hooks/setup-close-listener! ref open?)
[:div.relative.bg-white.overflow-hidden {:ref ref}
[:div.max-w-screen-xl.mx-auto
[:div.relative.z-10.pb-8.bg-white.sm:pb-16.md:pb-20.lg:max-w-2xl.lg:w-full.lg:pb-28.xl:pb-32
[:div.pt-6.px-4.sm:px-6.lg:px-8
[:nav.relative.flex.items-center.justify-between.sm:h-10.lg:justify-start
[:div.flex.items-center.flex-grow.flex-shrink-0.lg:flex-grow-0
[:div.flex.items-center.justify-between.w-full.md:w-auto
[:a
{:href "#"}
[:img.h-8.w-auto.sm:h-10
{:alt "", :src "https://tailwindui.com/img/logos/workflow-mark-on-white.svg"}]]
[:div.-mr-2.flex.items-center.md:hidden
[:button.inline-flex.items-center.justify-center.p-2.rounded-md.text-gray-400.hover:text-gray-500.hover:bg-gray-100.focus:outline-none.focus:bg-gray-100.focus:text-gray-500.transition.duration-150.ease-in-out
{:type "button",
:on-click open-fn}
[:svg.h-6.w-6
{:viewbox "0 0 24 24",
:fill "none",
:stroke "currentColor"}
[:path
{:d "M4 6h16M4 12h16M4 18h16",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]]]]]
[:div.hidden.md:block.md:ml-10.md:pr-4
[:a.font-medium.text-gray-500.hover:text-gray-900.focus:outline-none.focus:text-gray-900.transition.duration-150.ease-in-out
{:href "#"}
"Product"]
[:a.ml-8.font-medium.text-gray-500.hover:text-gray-900.focus:outline-none.focus:text-gray-900.transition.duration-150.ease-in-out
{:href "#"}
"Features"]
[:a.ml-8.font-medium.text-gray-500.hover:text-gray-900.focus:outline-none.focus:text-gray-900.transition.duration-150.ease-in-out
{:href "#"}
"Marketplace"]
[:a.ml-8.font-medium.text-gray-500.hover:text-gray-900.focus:outline-none.focus:text-gray-900.transition.duration-150.ease-in-out
{:href "#"}
"Company"]
[:a.ml-8.font-medium.text-indigo-600.hover:text-indigo-900.focus:outline-none.focus:text-indigo-700.transition.duration-150.ease-in-out
{:href "#"}
"Log in"]]]]
(ui/css-transition @open? 0
(fn [state]
[:div.absolute.top-0.inset-x-0.p-2.transition.transform.origin-top-right.md:hidden
{:class (case state
"entering" "duration-150 ease-out opacity-0 scale-95"
"entered" "duration-150 ease-out opacity-100 scale-100"
"exiting" "duration-100 ease-in opacity-100 scale-100"
"exited" "duration-100 ease-in opacity-0 scale-95")}
[:div.rounded-lg.shadow-md
[:div.rounded-lg.bg-white.shadow-xs.overflow-hidden
[:div.px-5.pt-4.flex.items-center.justify-between
[:div
[:img.h-8.w-auto
{:alt "", :src "https://tailwindui.com/img/logos/workflow-mark-on-white.svg"}]]
[:div.-mr-2
[:button.inline-flex.items-center.justify-center.p-2.rounded-md.text-gray-400.hover:text-gray-500.hover:bg-gray-100.focus:outline-none.focus:bg-gray-100.focus:text-gray-500.transition.duration-150.ease-in-out
{:type "button",
:on-click close-fn}
[:svg.h-6.w-6
{:viewbox "0 0 24 24",
:fill "none",
:stroke "currentColor"}
[:path
{:d "M6 18L18 6M6 6l12 12",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]]]]
[:div.px-2.pt-2.pb-3
[:a.block.px-3.py-2.rounded-md.text-base.font-medium.text-gray-700.hover:text-gray-900.hover:bg-gray-50.focus:outline-none.focus:text-gray-900.focus:bg-gray-50.transition.duration-150.ease-in-out
{:href "#"}
"Product"]
[:a.mt-1.block.px-3.py-2.rounded-md.text-base.font-medium.text-gray-700.hover:text-gray-900.hover:bg-gray-50.focus:outline-none.focus:text-gray-900.focus:bg-gray-50.transition.duration-150.ease-in-out
{:href "#"}
"Features"]
[:a.mt-1.block.px-3.py-2.rounded-md.text-base.font-medium.text-gray-700.hover:text-gray-900.hover:bg-gray-50.focus:outline-none.focus:text-gray-900.focus:bg-gray-50.transition.duration-150.ease-in-out
{:href "#"}
"Marketplace"]
[:a.mt-1.block.px-3.py-2.rounded-md.text-base.font-medium.text-gray-700.hover:text-gray-900.hover:bg-gray-50.focus:outline-none.focus:text-gray-900.focus:bg-gray-50.transition.duration-150.ease-in-out
{:href "#"}
"Company"]]
[:div
[:a.block.w-full.px-5.py-3.text-center.font-medium.text-indigo-600.bg-gray-50.hover:bg-gray-100.hover:text-indigo-700.focus:outline-none.focus:bg-gray-100.focus:text-indigo-700.transition.duration-150.ease-in-out
{:href "#"}
"\n Log in\n "]]]]]))
(empty? repos)
(repo/add-repo repo-url)
[:div.mt-10.mx-auto.max-w-screen-xl.px-4.sm:mt-12.sm:px-6.md:mt-16.lg:mt-20.lg:px-8.xl:mt-28
[:div.sm:text-center.lg:text-left
[:h2.text-4xl.tracking-tight.leading-10.font-extrabold.text-gray-900.sm:text-5xl.sm:leading-none.md:text-6xl
"\n Data to enrich your\n "
[:br.xl:hidden]
[:span.text-indigo-600 "online business"]]
[:p.mt-3.text-base.text-gray-500.sm:mt-5.sm:text-lg.sm:max-w-xl.sm:mx-auto.md:mt-5.md:text-xl.lg:mx-0
"\n Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem cupidatat commodo. Elit sunt amet fugiat veniam occaecat fugiat aliqua.\n "]
[:div.mt-5.sm:mt-8.sm:flex.sm:justify-center.lg:justify-start
[:div.rounded-md.shadow
[:a.w-full.flex.items-center.justify-center.px-8.py-3.border.border-transparent.text-base.leading-6.font-medium.rounded-md.text-white.bg-indigo-600.hover:bg-indigo-500.focus:outline-none.focus:shadow-outline.transition.duration-150.ease-in-out.md:py-4.md:text-lg.md:px-10
{:href "#"}
"Login with Github"]]
[:div.mt-3.sm:mt-0.sm:ml-3
[:a.w-full.flex.items-center.justify-center.px-8.py-3.border.border-transparent.text-base.leading-6.font-medium.rounded-md.text-indigo-700.bg-indigo-100.hover:text-indigo-600.hover:bg-indigo-50.focus:outline-none.focus:shadow-outline.focus:border-indigo-300.transition.duration-150.ease-in-out.md:py-4.md:text-lg.md:px-10
{:href "#"}
"Live demo"]]]]]
[:svg
{:class (util/hiccup->class ".hidden.lg:block.absolute.right-0.inset-y-0.h-full.w-48.text-white.transform.translate-x-1/2")
:preserveaspectratio "none",
:viewbox "0 0 100 100",
:fill "currentColor"}
[:polygon {:points "50,0 100,0 50,100 0,100"}]]]]
[:div
{:class (util/hiccup->class ".lg:absolute.lg:inset-y-0.lg:right-0.lg:w-1/2")}
[:img.h-56.w-full.object-cover.sm:h-72.md:h-96.lg:w-full.lg:h-full
{:alt "",
:src
"https://images.unsplash.com/photo-1551434678-e076c223a692?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2850&q=80"}]]])
)
cloned?
(mui/grid
{:container true
:spacing 3}
(when-not mobile?
(mui/grid {:xs 2}
(file/files-list current-repo files)))
;; (rum/defc content-html
;; < {:did-mount (fn [state]
;; (doseq [block (-> (js/document.querySelectorAll "pre code")
;; (array-seq))]
;; (js/hljs.highlightBlock block))
;; state)}
;; [current-file html-content]
;; [:div
;; (mui/link {:style {:float "right"}
;; :on-click (fn []
;; (handler/change-page :edit-file))}
;; "edit")
;; (util/raw-html html-content)])
(if (and (not mobile?)
(not drawer?))
(mui/divider {:orientation "vertical"
:style {:margin "0 24px"}}))
(mui/grid {:xs 9
:style {:margin-left (if (or mobile? drawer?) 24 0)}}
(cond
(nil? current-file)
(agenda/agenda tasks)
;; (rum/defc home < rum/reactive
;; []
;; (let [state (rum/react state/state)
;; {:keys [user tokens repos repo-url github-token github-repo contents loadings current-repo current-file width drawer? tasks cloning?]} state
;; current-repo (or current-repo
;; (when-let [first-repo (first (keys repos))]
;; (handler/set-current-repo first-repo)
;; first-repo))
;; files (get-in state [:repos current-repo :files])
;; cloned? (get-in state [:repos current-repo :cloned?])
;; loading? (get loadings current-file)
;; width (or width (util/get-width))
;; mobile? (and width (<= width 600))]
;; (prn {:current-repo current-repo
;; :cloned? cloned?})
;; (mui/container
;; {:id "root-container"
;; :style {:display "flex"
;; :justify-content "center"
;; ;; TODO: fewer spacing for mobile, 24px
;; :margin-top 64}}
;; (cond
;; (nil? user)
;; (mui/button {:variant "contained"
;; :color "primary"
;; :start-icon (mui/github-icon)
;; :href "/login/github"}
;; "Login with Github")
loading?
[:div "Loading ..."]
;; (empty? repos)
;; (repo/add-repo repo-url)
:else
(let [content (get contents current-file)
suffix (last (string/split current-file #"\."))]
(if (and suffix (contains? #{"md" "markdown" "org"} suffix))
(content-html current-file (format/to-html content suffix))
[:div "File " suffix " is not supported."])))))
cloning?
[:div "Cloning..."]
;; cloned?
;; (mui/grid
;; {:container true
;; :spacing 3}
;; (when-not mobile?
;; (mui/grid {:xs 2}
;; (file/files-list current-repo files)))
:else
[:div "TBC"]
;; (settings/settings-form github-token github-repo)
))))
;; (if (and (not mobile?)
;; (not drawer?))
;; (mui/divider {:orientation "vertical"
;; :style {:margin "0 24px"}}))
;; (mui/grid {:xs 9
;; :style {:margin-left (if (or mobile? drawer?) 24 0)}}
;; (cond
;; (nil? current-file)
;; (agenda/agenda tasks)
;; loading?
;; [:div "Loading ..."]
;; :else
;; (let [content (get contents current-file)
;; suffix (last (string/split current-file #"\."))]
;; (if (and suffix (contains? #{"md" "markdown" "org"} suffix))
;; (content-html current-file (format/to-html content suffix))
;; [:div "File " suffix " is not supported."])))))
;; cloning?
;; [:div "Cloning..."]
;; :else
;; [:div "TBC"]
;; ;; (settings/settings-form github-token github-repo)
;; ))))

View File

@ -1,38 +1,39 @@
(ns frontend.components.repo
(:require [rum.core :as rum]
[frontend.mui :as mui]
[frontend.util :as util]
[frontend.state :as state]
[frontend.handler :as handler]
[clojure.string :as string]))
;; (:require [rum.core :as rum]
;; [frontend.mui :as mui]
;; [frontend.util :as util]
;; [frontend.state :as state]
;; [frontend.handler :as handler]
;; [clojure.string :as string])
)
(defn repos
[repos]
(when (seq repos)
[:div#repos
[:ul
(for [{:keys [url id]} (vals repos)]
[:li {:key id}
[:button {:on-click (fn []
(handler/set-current-repo url))}
(string/replace url "https://github.com/" "")]])]]))
;; (defn repos
;; [repos]
;; (when (seq repos)
;; [:div#repos
;; [:ul
;; (for [{:keys [url id]} (vals repos)]
;; [:li {:key id}
;; [:button {:on-click (fn []
;; (handler/set-current-repo url))}
;; (string/replace url "https://github.com/" "")]])]]))
(defn add-repo
[repo-url]
[:form {:style {:min-width 300}}
(mui/grid
{:container true
:direction "column"}
(mui/text-field {:id "standard-basic"
:style {:margin-bottom 12}
:label "Repo url"
:on-change (fn [event]
(let [v (util/evalue event)]
(swap! state/state assoc :repo-url v)))
:value repo-url
})
(mui/button {:variant "contained"
:color "primary"
:on-click (fn []
(handler/add-repo-and-clone repo-url))}
"Sync"))])
;; (defn add-repo
;; [repo-url]
;; [:form {:style {:min-width 300}}
;; (mui/grid
;; {:container true
;; :direction "column"}
;; (mui/text-field {:id "standard-basic"
;; :style {:margin-bottom 12}
;; :label "Repo url"
;; :on-change (fn [event]
;; (let [v (util/evalue event)]
;; (swap! state/state assoc :repo-url v)))
;; :value repo-url
;; })
;; (mui/button {:variant "contained"
;; :color "primary"
;; :on-click (fn []
;; (handler/add-repo-and-clone repo-url))}
;; "Sync"))])

View File

@ -1,50 +1,51 @@
(ns frontend.components.settings
(:require [rum.core :as rum]
[frontend.mui :as mui]
[frontend.util :as util]
[frontend.state :as state]
[frontend.handler :as handler]
[clojure.string :as string]))
;; (:require [rum.core :as rum]
;; [frontend.mui :as mui]
;; [frontend.util :as util]
;; [frontend.state :as state]
;; [frontend.handler :as handler]
;; [clojure.string :as string])
)
(defn settings-form
[github-token github-repo]
[:form {:style {:min-width 300}}
(mui/grid
{:container true
:direction "column"}
(mui/text-field {:id "standard-basic"
:style {:margin-bottom 12}
:label "Github repo"
:on-change (fn [event]
(let [v (util/evalue event)]
(swap! state/state assoc :github-repo v)))
:value github-repo
})
(mui/button {:variant "contained"
:color "primary"
:on-click (fn []
(when (and github-token github-repo)
(handler/clone github-token github-repo)))}
"Sync"))])
;; (defn settings-form
;; [github-token github-repo]
;; [:form {:style {:min-width 300}}
;; (mui/grid
;; {:container true
;; :direction "column"}
;; (mui/text-field {:id "standard-basic"
;; :style {:margin-bottom 12}
;; :label "Github repo"
;; :on-change (fn [event]
;; (let [v (util/evalue event)]
;; (swap! state/state assoc :github-repo v)))
;; :value github-repo
;; })
;; (mui/button {:variant "contained"
;; :color "primary"
;; :on-click (fn []
;; (when (and github-token github-repo)
;; (handler/clone github-token github-repo)))}
;; "Sync"))])
(rum/defc settings < rum/reactive
[]
;; Change repo and basic token
(let [state (rum/react state/state)
{:keys [github-token github-repo]} state]
(mui/container
{:id "root-container"
:style {:display "flex"
:justify-content "center"
:margin-top 64}}
;; (rum/defc settings < rum/reactive
;; []
;; ;; Change repo and basic token
;; (let [state (rum/react state/state)
;; {:keys [github-token github-repo]} state]
;; (mui/container
;; {:id "root-container"
;; :style {:display "flex"
;; :justify-content "center"
;; :margin-top 64}}
[:div
;; [:div
(settings-form github-token github-repo)
;; (settings-form github-token github-repo)
(mui/divider {:style {:margin "24px 0"}})
;; (mui/divider {:style {:margin "24px 0"}})
;; clear storage
(mui/button {:on-click handler/clear-storage
:color "primary"}
"Clear storage and clone")])))
;; ;; clear storage
;; (mui/button {:on-click handler/clear-storage
;; :color "primary"}
;; "Clear storage and clone")])))

View File

@ -1,18 +1,47 @@
(ns frontend.components.sidebar
(:require [rum.core :as rum]
(:require [uix.core.alpha :as uix :refer [defui]]
[xframe.core.alpha :as xf :refer [<sub]]
[frontend.ui :as ui]
[frontend.mixins :as mixins]))
[frontend.hooks :as hooks]))
(rum/defcs sidebar
<
(rum/local false ::open?)
(mixins/event-mixin #(mixins/simple-close-listener % ::open?))
[state]
(let [open? (get state ::open?)
(defonce active-button :a.group.flex.items-center.px-2.py-2.text-base.leading-6.font-medium.rounded-md.text-white.bg-gray-900.focus:outline-none.focus:bg-gray-700.transition.ease-in-out.duration-150)
(defonce inactive-button :a.mt-1.group.flex.items-center.px-2.py-2.text-base.leading-6.font-medium.rounded-md.text-gray-300.hover:text-white.hover:bg-gray-700.focus:outline-none.focus:text-white.focus:bg-gray-700.transition.ease-in-out.duration-150)
(defn nav-item
([title href svg-d]
(nav-item title href svg-d false))
([title href svg-d active?]
(let [a (if active? active-button inactive-button)]
[a {:href href}
[:svg.mr-4.h-6.w-6.text-gray-400.group-hover:text-gray-300.group-focus:text-gray-300.transition.ease-in-out.duration-150
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d svg-d
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]
title])))
(defn sidebar-nav
[]
[:nav.flex-1.px-2.py-4.bg-gray-800
(nav-item "Journal" "#"
"M3 12l9-9 9 9M5 10v10a1 1 0 001 1h3a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1h3a1 1 0 001-1V10M9 21h6"
true)
(nav-item "Repos" "#"
"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z")
(nav-item "Agenda" "#"
"M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z")])
(defn sidebar
[]
(let [ref (uix/ref nil)
open? (uix/state false)
close-fn (fn [] (reset! open? false))
open-fn (fn [] (reset! open? true))]
(prn "open: " @open?)
[:div.h-screen.flex.overflow-hidden.bg-gray-100
;; effects
(hooks/setup-close-listener! ref open?)
[:div.h-screen.flex.overflow-hidden.bg-gray-100 {:ref ref}
[:div.md:hidden
[:div.fixed.inset-0.z-30.bg-gray-600.opacity-0.pointer-events-none.transition-opacity.ease-linear.duration-300
{:class (if @open?
@ -25,167 +54,35 @@
"-translate-x-full")}
(if @open?
[:div.absolute.top-0.right-0.-mr-14.p-1
[:button.flex.items-center.justify-center.h-12.w-12.rounded-full.focus:outline-none.focus:bg-gray-600
{:on-click close-fn}
[:svg.h-6.w-6.text-white
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d "M6 18L18 6M6 6l12 12",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]]])
[:button.flex.items-center.justify-center.h-12.w-12.rounded-full.focus:outline-none.focus:bg-gray-600
{:on-click close-fn}
[:svg.h-6.w-6.text-white
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d "M6 18L18 6M6 6l12 12",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]]])
[:div.flex-shrink-0.flex.items-center.h-16.px-4.bg-gray-900
[:img.h-8.w-auto
{:alt "Workflow",
:src "/img/logos/workflow-logo-on-dark.svg"}]]
{:alt "Gitnotes",
:src "https://tailwindui.com/img/logos/workflow-logo-on-dark.svg"}]]
[:div.flex-1.h-0.overflow-y-auto
[:nav.px-2.py-4
[:a.group.flex.items-center.px-2.py-2.text-base.leading-6.font-medium.rounded-md.text-white.bg-gray-900.focus:outline-none.focus:bg-gray-700.transition.ease-in-out.duration-150
{:href "#"}
[:svg.mr-4.h-6.w-6.text-gray-300.group-hover:text-gray-300.group-focus:text-gray-300.transition.ease-in-out.duration-150
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d
"M3 12l9-9 9 9M5 10v10a1 1 0 001 1h3a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1h3a1 1 0 001-1V10M9 21h6",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]
"\n Dashboard\n "]
[:a.mt-1.group.flex.items-center.px-2.py-2.text-base.leading-6.font-medium.rounded-md.text-gray-300.hover:text-white.hover:bg-gray-700.focus:outline-none.focus:text-white.focus:bg-gray-700.transition.ease-in-out.duration-150
{:href "#"}
[:svg.mr-4.h-6.w-6.text-gray-400.group-hover:text-gray-300.group-focus:text-gray-300.transition.ease-in-out.duration-150
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d
"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]
"\n Team\n "]
[:a.mt-1.group.flex.items-center.px-2.py-2.text-base.leading-6.font-medium.rounded-md.text-gray-300.hover:text-white.hover:bg-gray-700.focus:outline-none.focus:text-white.focus:bg-gray-700.transition.ease-in-out.duration-150
{:href "#"}
[:svg.mr-4.h-6.w-6.text-gray-400.group-hover:text-gray-300.group-focus:text-gray-300.transition.ease-in-out.duration-150
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d
"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]
"\n Projects\n "]
[:a.mt-1.group.flex.items-center.px-2.py-2.text-base.leading-6.font-medium.rounded-md.text-gray-300.hover:text-white.hover:bg-gray-700.focus:outline-none.focus:text-white.focus:bg-gray-700.transition.ease-in-out.duration-150
{:href "#"}
[:svg.mr-4.h-6.w-6.text-gray-400.group-hover:text-gray-300.group-focus:text-gray-300.transition.ease-in-out.duration-150
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d
"M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]
"\n Calendar\n "]
[:a.mt-1.group.flex.items-center.px-2.py-2.text-base.leading-6.font-medium.rounded-md.text-gray-300.hover:text-white.hover:bg-gray-700.focus:outline-none.focus:text-white.focus:bg-gray-700.transition.ease-in-out.duration-150
{:href "#"}
[:svg.mr-4.h-6.w-6.text-gray-400.group-hover:text-gray-300.group-focus:text-gray-300.transition.ease-in-out.duration-150
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d
"M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]
"\n Documents\n "]
[:a.mt-1.group.flex.items-center.px-2.py-2.text-base.leading-6.font-medium.rounded-md.text-gray-300.hover:text-white.hover:bg-gray-700.focus:outline-none.focus:text-white.focus:bg-gray-700.transition.ease-in-out.duration-150
{:href "#"}
[:svg.mr-4.h-6.w-6.text-gray-400.group-hover:text-gray-300.group-focus:text-gray-300.transition.ease-in-out.duration-150
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d
"M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]
"\n Reports\n "]]]]]
[sidebar-nav]]]]
[:div.hidden.md:flex.md:flex-shrink-0
[:div.flex.flex-col.w-64
[:div.flex.items-center.h-16.flex-shrink-0.px-4.bg-gray-900
[:img.h-8.w-auto
{:alt "Workflow",
:src "/img/logos/workflow-logo-on-dark.svg"}]]
{:alt "Gitnotes",
:src "https://tailwindui.com/img/logos/workflow-logo-on-dark.svg"}]]
[:div.h-0.flex-1.flex.flex-col.overflow-y-auto
[:nav.flex-1.px-2.py-4.bg-gray-800
[:a.group.flex.items-center.px-2.py-2.text-sm.leading-5.font-medium.text-white.rounded-md.bg-gray-900.focus:outline-none.focus:bg-gray-700.transition.ease-in-out.duration-150
{:href "#"}
[:svg.mr-3.h-6.w-6.text-gray-300.group-hover:text-gray-300.group-focus:text-gray-300.transition.ease-in-out.duration-150
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d
"M3 12l9-9 9 9M5 10v10a1 1 0 001 1h3a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1h3a1 1 0 001-1V10M9 21h6",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]
"\n Dashboard\n "]
[:a.mt-1.group.flex.items-center.px-2.py-2.text-sm.leading-5.font-medium.text-gray-300.rounded-md.hover:text-white.hover:bg-gray-700.focus:outline-none.focus:text-white.focus:bg-gray-700.transition.ease-in-out.duration-150
{:href "#"}
[:svg.mr-3.h-6.w-6.text-gray-400.group-hover:text-gray-300.group-focus:text-gray-300.transition.ease-in-out.duration-150
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d
"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]
"\n Team\n "]
[:a.mt-1.group.flex.items-center.px-2.py-2.text-sm.leading-5.font-medium.text-gray-300.rounded-md.hover:text-white.hover:bg-gray-700.focus:outline-none.focus:text-white.focus:bg-gray-700.transition.ease-in-out.duration-150
{:href "#"}
[:svg.mr-3.h-6.w-6.text-gray-400.group-hover:text-gray-300.group-focus:text-gray-300.transition.ease-in-out.duration-150
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d
"M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]
"\n Projects\n "]
[:a.mt-1.group.flex.items-center.px-2.py-2.text-sm.leading-5.font-medium.text-gray-300.rounded-md.hover:text-white.hover:bg-gray-700.focus:outline-none.focus:text-white.focus:bg-gray-700.transition.ease-in-out.duration-150
{:href "#"}
[:svg.mr-3.h-6.w-6.text-gray-400.group-hover:text-gray-300.group-focus:text-gray-300.transition.ease-in-out.duration-150
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d
"M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]
"\n Calendar\n "]
[:a.mt-1.group.flex.items-center.px-2.py-2.text-sm.leading-5.font-medium.text-gray-300.rounded-md.hover:text-white.hover:bg-gray-700.focus:outline-none.focus:text-white.focus:bg-gray-700.transition.ease-in-out.duration-150
{:href "#"}
[:svg.mr-3.h-6.w-6.text-gray-400.group-hover:text-gray-300.group-focus:text-gray-300.transition.ease-in-out.duration-150
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d
"M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]
"\n Documents\n "]
[:a.mt-1.group.flex.items-center.px-2.py-2.text-sm.leading-5.font-medium.text-gray-300.rounded-md.hover:text-white.hover:bg-gray-700.focus:outline-none.focus:text-white.focus:bg-gray-700.transition.ease-in-out.duration-150
{:href "#"}
[:svg.mr-3.h-6.w-6.text-gray-400.group-hover:text-gray-300.group-focus:text-gray-300.transition.ease-in-out.duration-150
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d
"M16 8v8m-4-5v5m-4-2v2m-2 4h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z",
:stroke-width "2",
:stroke-linejoin "round",
:stroke-linecap "round"}]]
"\n Reports\n "]]]]]
[sidebar-nav]]]]
[:div.flex.flex-col.w-0.flex-1.overflow-hidden
[:div.relative.z-10.flex-shrink-0.flex.h-16.bg-white.shadow
[:button.px-4.border-r.border-gray-200.text-gray-500.focus:outline-none.focus:bg-gray-100.focus:text-gray-600.md:hidden
{:on-click open-fn}
[:svg.h-6.w-6
{:viewbox "0 0 24 24", :fill "none", :stroke "currentColor"}
{:viewBox "0 0 24 24", :fill "none", :stroke "currentColor"}
[:path
{:d "M4 6h16M4 12h16M4 18h7",
:stroke-width "2",
@ -230,5 +127,4 @@
[:h1.text-2xl.font-semibold.text-gray-900 "Dashboard"]]
[:div.max-w-7xl.mx-auto.px-4.sm:px-6.md:px-8
[:div.py-4
[:div.border-4.border-dashed.border-gray-200.rounded-lg.h-96]]
]]]]))
[:div.border-4.border-dashed.border-gray-200.rounded-lg.h-96]]]]]]))

View File

@ -1,25 +1,31 @@
(ns frontend.core
(:require [rum.core :as rum]
[frontend.git :as git]
[frontend.util :as util]
[frontend.state :as state]
(:require [uix.dom.alpha :as dom]
[frontend.handler :as handler]
[frontend.routes :as routes]
[frontend.page :as page]
[frontend.api :as api]))
[frontend.routes :as routes]
[reitit.frontend :as rf]
[reitit.frontend.easy :as rfe]
[reitit.coercion :as rc]
[reitit.coercion.spec :as rss]))
(defn start []
(rum/mount (page/current-page)
(.getElementById js/document "root")))
(dom/render [page/current-page] js/root))
(defn ^:export init []
;; init is called ONCE when the page loads
;; this is called in the index.html and must be exported
;; so it is available even in :advanced release builds
(rfe/start!
(rf/router routes/routes {:data {:coercion rss/coercion}})
handler/set-route-match!
;; set to false to enable HistoryAPI
{:use-fragment false})
(handler/get-me)
(handler/listen-to-resize)
;; popup to notify user, could be toggled in settings
;; (handler/request-notifications-if-not-asked)
;; (handler/run-notify-worker!)

View File

@ -406,3 +406,7 @@
(defn set-current-repo
[repo-url]
(swap! state/state assoc :current-repo repo-url))
(defn set-route-match!
[route]
(swap! state/state assoc :route-match route))

View File

@ -0,0 +1,79 @@
(ns frontend.hooks
(:require [goog.dom :as dom]
[uix.core.alpha :as uix]
[uix.dom.alpha :as uix-dom])
(:import [goog.events EventHandler]))
(defn detach-listeners
"Detach all event listeners."
[state]
(some-> state ::event-handler .removeAll))
(defn listen
"Register an event `handler` for events of `type` on `target`."
[^EventHandler event-handler target type handler & [opts]]
(.listen event-handler target (name type) handler (clj->js opts)))
;; (defn timeout-mixin
;; "The setTimeout mixin."
;; [name t f]
;; {:will-mount
;; (fn [state]
;; (assoc state name (util/set-timeout t f)))
;; :will-unmount
;; (fn [state]
;; (let [timeout (get state name)]
;; (util/clear-timeout timeout)
;; (dissoc state name)))})
;; (defn interval-mixin
;; "The setInterval mixin."
;; [name t f]
;; {:will-mount
;; (fn [state]
;; (assoc state name (util/set-interval t f)))
;; :will-unmount
;; (fn [state]
;; (when-let [interval (get state name)]
;; (util/clear-interval interval))
;; (dissoc state name))})
(defn event-hook
[attach-listeners]
(let [event-handler (uix/state (EventHandler.))]
(uix/effect!
(fn []
;; did mount
(attach-listeners @event-handler)
(fn []
;; will-unmount
(detach-listeners @event-handler)))
[])))
(defn close-when-esc-or-outside
[ref event-handler open? & {:keys [on-close]}]
(let [node (uix-dom/find-dom-node @ref)]
(when open?
(when node
(listen event-handler js/window "click"
(fn [e]
;; If the click target is outside of current node
(when-not (dom/contains node (.. e -target))
(on-close e)))))
(listen event-handler js/window "keydown"
(fn [e]
(case (.-keyCode e)
;; Esc
27 (on-close e)
nil))))))
(defn setup-close-listener!
[ref open?]
(event-hook (fn [event-handler]
(close-when-esc-or-outside
ref
event-handler
open?
:on-close (fn []
(reset! open? false))))))

View File

@ -1,66 +1,66 @@
(ns frontend.layout
(:require [frontend.mui :as mui]
[frontend.handler :as handler]
[frontend.state :as state]
[frontend.components.file :as file]
[frontend.components.repo :as repo]
[rum.core :as rum]
[clojure.string :as string]))
;; (:require [frontend.handler :as handler]
;; [frontend.state :as state]
;; [frontend.components.file :as file]
;; [frontend.components.repo :as repo]
;; [rum.core :as rum]
;; [clojure.string :as string])
)
(rum/defc frame < rum/reactive
[content width]
(let [state (rum/react state/state)
{:keys [files drawer? snackbar? snackbar-message current-repo]} state
mobile? (and width (<= width 600))]
(mui/theme-provider
{:theme (mui/custom-theme)}
[:div {:class "root"
:style {:padding-bottom 100}}
(mui/css-baseline)
(mui/app-bar
{:position "static"}
(mui/tool-bar
{}
(if mobile?
(mui/icon-button {:edge "start"
:class "menuButton"
:color "inherit"
:on-click (fn []
(handler/toggle-drawer? true))}
(mui/menu-icon)))
(mui/typography {:class "grow"
:variant "h6"
:color "inherit"
:no-wrap true
:on-click (fn []
(handler/change-page :home)
(handler/reset-current-file))}
"Gitnotes")
;; (rum/defc frame < rum/reactive
;; [content width]
;; (let [state (rum/react state/state)
;; {:keys [files drawer? snackbar? snackbar-message current-repo]} state
;; mobile? (and width (<= width 600))]
;; (mui/theme-provider
;; {:theme (mui/custom-theme)}
;; [:div {:class "root"
;; :style {:padding-bottom 100}}
;; (mui/css-baseline)
;; (mui/app-bar
;; {:position "static"}
;; (mui/tool-bar
;; {}
;; (if mobile?
;; (mui/icon-button {:edge "start"
;; :class "menuButton"
;; :color "inherit"
;; :on-click (fn []
;; (handler/toggle-drawer? true))}
;; (mui/menu-icon)))
;; (mui/typography {:class "grow"
;; :variant "h6"
;; :color "inherit"
;; :no-wrap true
;; :on-click (fn []
;; (handler/change-page :home)
;; (handler/reset-current-file))}
;; "Gitnotes")
(mui/button {:color "inherit"
:on-click (fn []
(handler/sync))}
"Sync")
;; (mui/button {:color "inherit"
;; :on-click (fn []
;; (handler/sync))}
;; "Sync")
(mui/button {:color "inherit"
:on-click (fn []
(handler/change-page :settings))}
"Settings")))
;; (mui/button {:color "inherit"
;; :on-click (fn []
;; (handler/change-page :settings))}
;; "Settings")))
(repo/repos (:repos state))
;; (repo/repos (:repos state))
content
;; content
(if mobile?
(mui/drawer {:open drawer?
:disableBackdropTransition true
:on-open (fn []
(handler/toggle-drawer? true))
:on-close (fn []
(handler/toggle-drawer? false))}
[:div {:style {:width 240}}
(file/files-list current-repo files)]))
;; (if mobile?
;; (mui/drawer {:open drawer?
;; :disableBackdropTransition true
;; :on-open (fn []
;; (handler/toggle-drawer? true))
;; :on-close (fn []
;; (handler/toggle-drawer? false))}
;; [:div {:style {:width 240}}
;; (file/files-list current-repo files)]))
(mui/snackbar {:open snackbar?
:auto-hide-duration 3000
:message snackbar-message})])))
;; (mui/snackbar {:open snackbar?
;; :auto-hide-duration 3000
;; :message snackbar-message})])))

View File

@ -1,86 +0,0 @@
(ns frontend.mixins
(:require [rum.core :as rum]
[goog.dom :as dom])
(:import [goog.events EventHandler]))
(defn detach
"Detach all event listeners."
[state]
(some-> state ::event-handler .removeAll))
(defn listen
"Register an event `handler` for events of `type` on `target`."
[state target type handler & [opts]]
(when-let [event-handler (::event-handler state)]
(.listen event-handler target (name type) handler (clj->js opts))))
(def event-handler-mixin
"The event handler mixin."
{:will-mount
(fn [state]
(assoc state ::event-handler (EventHandler.)))
:will-unmount
(fn [state]
(detach state)
(dissoc state ::event-handler))})
;; (defn timeout-mixin
;; "The setTimeout mixin."
;; [name t f]
;; {:will-mount
;; (fn [state]
;; (assoc state name (util/set-timeout t f)))
;; :will-unmount
;; (fn [state]
;; (let [timeout (get state name)]
;; (util/clear-timeout timeout)
;; (dissoc state name)))})
;; (defn interval-mixin
;; "The setInterval mixin."
;; [name t f]
;; {:will-mount
;; (fn [state]
;; (assoc state name (util/set-interval t f)))
;; :will-unmount
;; (fn [state]
;; (when-let [interval (get state name)]
;; (util/clear-interval interval))
;; (dissoc state name))})
(defn close-when-esc-or-outside
[state open? & {:keys [on-close]}]
(let [node (rum/dom-node state)]
(when open?
(listen state js/window "click"
(fn [e]
;; If the click target is outside of current node
(when-not (dom/contains node (.. e -target))
(on-close e))))
(listen state js/window "keydown"
(fn [e]
(case (.-keyCode e)
;; Esc
27 (on-close e)
nil))))))
(defn simple-close-listener
[state key]
(let [open? (get state key)]
(close-when-esc-or-outside state
open?
:on-close (fn []
(reset! open? false)))))
(defn event-mixin
[attach-listeners]
(merge
event-handler-mixin
{:did-mount (fn [state]
(attach-listeners state)
state)
:did-remount (fn [old-state new-state]
(detach old-state)
(attach-listeners new-state)
new-state)}))

View File

@ -1,114 +0,0 @@
(ns frontend.mui
(:refer-clojure :exclude [list stepper])
(:require [rum.core]
[frontend.rum :as r]
["@material-ui/core" :refer [MuiThemeProvider]]
["@material-ui/core/styles" :refer [createMuiTheme withStyles makeStyles]]
["@material-ui/core/colors" :as colors]
["@material-ui/core/CssBaseline" :default CssBaseline]
["@material-ui/core/Typography" :default Typography]
["@material-ui/core/Avatar" :default mui-avatar]
["@material-ui/icons/Android" :default AndroidIcon]
["@material-ui/core/AppBar" :default AppBar]
["@material-ui/core/Divider" :default Divider]
["@material-ui/core/Paper" :default Paper]
["@material-ui/core/Toolbar" :default ToolBar]
["@material-ui/core/IconButton" :default IconButton]
["@material-ui/icons/Menu" :default MenuIcon]
["@material-ui/core/Button" :default Button]
["@material-ui/core/SwipeableDrawer" :default SwipeableDrawer]
["@material-ui/core/Chip" :default Chip]
["@material-ui/core/Fab" :default Fab]
["@material-ui/core/List" :default List]
["@material-ui/core/ListItem" :default ListItem]
["@material-ui/core/ListItemText" :default ListItemText]
["@material-ui/core/Container" :default Container]
["@material-ui/core/Box" :default Box]
["@material-ui/core/Snackbar" :default Snackbar]
["@material-ui/core/Link" :default Link]
["@material-ui/core/Checkbox" :default Checkbox]
["@material-ui/core/Grid" :default Grid]
["@material-ui/core/GridList" :default GridList]
["@material-ui/core/Hidden" :default Hidden]
;; ["@material-ui/core/Form" :default Form]
["@material-ui/core/TextField" :default TextField]
["@material-ui/core/TextareaAutosize" :default TextareaAutosize]
["@material-ui/core/Card" :default Card]
["@material-ui/core/CardActions" :default CardActions]
["@material-ui/core/CardContent" :default CardContent]
["@material-ui/core/CardHeader" :default CardHeader]
["@material-ui/core/CardMedia" :default CardMedia]
["@material-ui/core/Collapse" :default Collapse]
["@material-ui/core/Avatar" :default Avatar]
["@material-ui/core/CircularProgress" :default CircularProgress]
["@material-ui/core/Badge" :default Badge]
["@material-ui/core/Tooltip" :default Tooltip]
["@material-ui/core/Dialog" :default Dialog]
["@material-ui/core/DialogTitle" :default DialogTitle]
["@material-ui/core/DialogContent" :default DialogContent]
["@material-ui/core/DialogActions" :default DialogActions]
["@material-ui/icons/Favorite" :default FavoriteIcon]
["@material-ui/icons/Add" :default AddIcon]
["@material-ui/icons/GitHub" :default GithubIcon]
["@material-ui/icons/Share" :default ShareIcon]
["@material-ui/icons/MoreVert" :default MoreVertIcon]
))
(defn custom-theme []
(createMuiTheme
(clj->js
{:palette
{:type "light"
;; :primary (.-purple colors)
;; :secondary (.-green colors)
}
:typography
{:useNextVariants true}})))
(defonce theme-provider (r/adapt-class MuiThemeProvider))
(defonce css-baseline (r/adapt-class CssBaseline))
(defonce app-bar (r/adapt-class AppBar))
(defonce divider (r/adapt-class Divider))
(defonce tool-bar (r/adapt-class ToolBar))
(defonce button (r/adapt-class Button))
(defonce icon-button (r/adapt-class IconButton))
(defonce typography (r/adapt-class Typography))
(defonce container (r/adapt-class Container))
(defonce box (r/adapt-class Box))
(defonce snackbar (r/adapt-class Snackbar))
(defonce link (r/adapt-class Link))
(defonce checkbox (r/adapt-class Checkbox))
(defonce grid (r/adapt-class Grid))
(defonce grid-list (r/adapt-class GridList))
(defonce paper (r/adapt-class Paper))
(defonce collapse (r/adapt-class Collapse))
(defonce avatar (r/adapt-class Avatar))
(defonce favorite-icon (r/adapt-class FavoriteIcon))
(defonce github-icon (r/adapt-class GithubIcon))
(defonce add-icon (r/adapt-class AddIcon))
(defonce fab (r/adapt-class Fab))
(defonce share-icon (r/adapt-class ShareIcon))
(defonce more-vert-icon (r/adapt-class MoreVertIcon))
(defonce circular-progress (r/adapt-class CircularProgress))
(defonce badge (r/adapt-class Badge))
(defonce text-field (r/adapt-class TextField))
(defonce textarea (r/adapt-class TextareaAutosize))
(defonce tooltip (r/adapt-class Tooltip))
(defonce dialog (r/adapt-class Dialog))
(defonce dialog-title (r/adapt-class DialogTitle))
(defonce dialog-content (r/adapt-class DialogContent))
(defonce dialog-actions (r/adapt-class DialogActions))
(defonce menu-icon (r/adapt-class MenuIcon))
(defonce drawer (r/adapt-class SwipeableDrawer))
(defonce chip (r/adapt-class Chip))
(defonce list (r/adapt-class List))
(defonce list-item (r/adapt-class ListItem))
(defonce list-item-text (r/adapt-class ListItemText))
;; card
(defonce card (r/adapt-class Card))
(defonce card-actions (r/adapt-class CardActions))
(defonce card-content (r/adapt-class CardContent))
(defonce card-actions (r/adapt-class CardActions))
(defonce card-header (r/adapt-class CardHeader))
(defonce card-media (r/adapt-class CardMedia))

View File

@ -1,15 +1,11 @@
(ns frontend.page
(:require [rum.core :as rum]
[frontend.layout :as layout]
[frontend.routes :as routes]
[frontend.state :as state]
[frontend.components.sidebar :as sidebar]))
(:require [uix.core.alpha :as uix]
[frontend.state :as state]))
(rum/defc current-page < rum/reactive
(defn current-page
[]
(let [state (rum/react state/state)
current-page (get state :current-page :home)]
(sidebar/sidebar)
;; (when-let [view (get routes/routes current-page)]
;; (layout/frame (view) (:width state)))
))
(let [route-match @(uix/state (:route-match @state/state))]
(prn "route-match: " route-match)
(if route-match
(when-let [view (:view (:data route-match))]
(view route-match)))))

View File

@ -1,10 +1,19 @@
(ns frontend.routes
(:require [frontend.components.home :as home]
[frontend.components.settings :as settings]
[frontend.components.file :as file]
))
[frontend.components.sidebar :as sidebar]))
(def routes
{:home home/home
:settings settings/settings
:edit-file file/edit})
[["/"
{:name :home
:view home/home
;; :view sidebar/sidebar
}]
;; TODO: edit file
;; Settings
;; ["/item/:id"
;; {:name ::item
;; :view item-page
;; :parameters {:path {:id int?}
;; :query {(ds/opt :foo) keyword?}}}]
])

View File

@ -1,59 +0,0 @@
(ns frontend.rum
(:require [clojure.string :as s]
[clojure.set :as set]
[clojure.walk :as w]))
;; copy from https://github.com/priornix/antizer/blob/35ba264cf48b84e6597743e28b3570d8aa473e74/src/antizer/core.cljs
(defn kebab-case->camel-case
"Converts from kebab case to camel case, eg: on-click to onClick"
[input]
(let [words (s/split input #"-")
capitalize (->> (rest words)
(map #(apply str (s/upper-case (first %)) (rest %))))]
(apply str (first words) capitalize)))
(defn map-keys->camel-case
"Stringifys all the keys of a cljs hashmap and converts them
from kebab case to camel case. If :html-props option is specified,
then rename the html properties values to their dom equivalent
before conversion"
[data & {:keys [html-props]}]
(let [convert-to-camel (fn [[key value]]
[(kebab-case->camel-case (name key)) value])]
(w/postwalk (fn [x]
(if (map? x)
(let [new-map (if html-props
(set/rename-keys x {:class :className :for :htmlFor})
x)]
(into {} (map convert-to-camel new-map)))
x))
data)))
;; adapted from https://github.com/tonsky/rum/issues/20
(defn adapt-class [react-class]
(fn [& args]
(let [[opts children] (if (map? (first args))
[(first args) (rest args)]
[{} args])
type# (first children)
;; we have to make sure to check if the children is sequential
;; as a list can be returned, eg: from a (for)
new-children (if (sequential? type#)
(let [result (sablono.interpreter/interpret children)]
(if (sequential? result)
result
[result]))
children)
;; convert any options key value to a react element, if
;; a valid html element tag is used, using sablono
vector->react-elems (fn [[key val]]
(if (sequential? val)
[key (sablono.interpreter/interpret val)]
[key val]))
new-options (into {} (map vector->react-elems opts))]
;; (.dir js/console new-children)
(apply js/React.createElement react-class
;; sablono html-to-dom-attrs does not work for nested hashmaps
(clj->js (map-keys->camel-case new-options :html-props true))
new-children))))

View File

@ -22,4 +22,5 @@
:width nil
:drawer? false
:tasks {}
:route-match nil
}))

View File

@ -1,23 +1,17 @@
(ns frontend.ui
(:require [rum.core :as rum]
[frontend.rum :as r]
["react-transition-group" :refer [TransitionGroup CSSTransition]]
(:require ["react-transition-group" :refer [CSSTransition]]
[frontend.util :as util]
[frontend.mixins :as mixins]))
[frontend.hooks :as hooks]
[uix.core.alpha :as uix]))
(defonce transition-group (r/adapt-class TransitionGroup))
(defonce css-transition (r/adapt-class CSSTransition))
(defn css-transition
[open? timeout state-fn]
[:> CSSTransition
{:in open? :timeout timeout}
(fn [state]
(uix/as-element (state-fn state)))])
(defn css-transition-group
[css-options items]
(when (seq items)
(transition-group
(for [item items]
(css-transition
(merge {:key (cljs.core/random-uuid)} css-options)
item)))))
(rum/defc dropdown-content-wrapper [state content]
(defn dropdown-content-wrapper [state content]
[:div.origin-top-right.absolute.right-0.mt-2.w-48.rounded-md.shadow-lg
{:class (case state
"entering" "transition ease-out duration-100 transform opacity-0 scale-95"
@ -27,23 +21,22 @@
content])
;; public exports
(rum/defcs dropdown <
(rum/local false ::show-dropdown?)
(mixins/event-mixin #(mixins/simple-close-listener % ::show-dropdown?))
[state content]
(let [show-dropdown? (get state ::show-dropdown?)]
[:div.ml-3.relative
(defn dropdown
[content]
(let [ref (uix/ref nil)
open? (uix/state false)]
(hooks/setup-close-listener! ref open?)
[:div.ml-3.relative {:ref ref}
[:div
[:button.max-w-xs.flex.items-center.text-sm.rounded-full.focus:outline-none.focus:shadow-outline
{:on-click (fn []
(swap! show-dropdown? not))}
(swap! open? not))}
[:img.h-8.w-8.rounded-full
{:src
"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"}]]]
(css-transition
{:in @show-dropdown? :timeout 0}
(fn [state]
(dropdown-content-wrapper state content)))]))
(css-transition @open? 0
(fn [state]
(dropdown-content-wrapper state content)))]))
(defn dropdown-with-links
[links]

View File

@ -1,7 +1,8 @@
(ns frontend.util
(:require [goog.object :as gobj]
[promesa.core :as p]
[clojure.walk :as walk]))
[clojure.walk :as walk]
[clojure.string :as string]))
(defn evalue
[event]
@ -92,3 +93,10 @@
(->> (map (fn [entry] [(get entry k) entry])
col)
(into {})))
;; ".lg:absolute.lg:inset-y-0.lg:right-0.lg:w-1/2"
(defn hiccup->class
[class]
(some->> (string/split class #"\.")
(string/join " ")
(string/trim)))