refactor(ui): refactor all of the classical datepicker codes with the shui calendar

This commit is contained in:
charlie
2024-04-22 17:24:27 +08:00
parent d71e0163c4
commit 52bc99738a
11 changed files with 64 additions and 576 deletions

View File

@@ -219,6 +219,14 @@
&.as-heading {
@apply flex w-full;
}
&:has(.dsl-query) {
@apply flex flex-row w-full;
}
.dsl-query {
@apply w-full;
}
}
.block-control, .block-control:hover {

View File

@@ -1,214 +1,3 @@
/*----------------------------------------------------------------------------------------
Stylesheet for re-com.date Date Picker variants inline-picker & dropdown-picker
Day8 variation loosely based on:
Copyright 2013 Dan Grossman ( http://www.dangrossman.info )
Licensed under the Apache License v2.0
http://www.apache.org/licenses/LICENSE-2.0
Built for http://www.improvely.com
http://eternicode.github.io/bootstrap-datepicker
----------------------------------------------------------------------------------------*/
.noselect {
user-select: none;
}
.datepicker.single .calendar {
float: none;
}
.datepicker .calendar {
display: none;
max-width: 200px;
}
.datepicker .calendar.single .calendar-date {
border: none;
}
.datepicker .calendar th,
.datepicker .calendar td {
white-space: nowrap;
text-align: center;
min-width: 32px;
}
.datepicker .calendar-date {
border: 1px solid #ddd;
padding: 4px;
border-radius: 4px;
}
.datepicker .calendar-time {
text-align: center;
margin: 8px auto 0 auto;
line-height: 30px;
}
.datepicker {
position: absolute;
top: 100px;
left: 20px;
padding: 10px;
line-height: 16px;
border-radius: 4px;
}
.datepicker table {
width: 100%;
margin: 0;
border-collapse: separate;
border-spacing: 0;
background: transparent;
border: none;
}
.datepicker td,
.datepicker th {
text-align: center;
width: 27px;
height: 26px;
max-width: 27px;
max-height: 26px;
min-width: 27px;
min-height: 26px;
padding: 4px;
cursor: default;
white-space: nowrap;
font-weight: normal;
}
.datepicker td.off {
padding: 4px;
color: #999;
}
.datepicker td.disabled {
color: #999;
}
.datepicker th.disabled {
color: #999;
}
.datepicker td.available:hover,
.datepicker th.available:hover {
@apply rounded bg-gray-07 cursor-pointer;
}
.datepicker td.in-range {
background: #ebf4f8;
border-radius: 0;
}
.datepicker td.start-date {
border-radius: 4px 0 0 4px;
}
.datepicker td.end-date {
border-radius: 0 4px 4px 0;
}
.datepicker td.start-date.end-date {
border-radius: 4px;
}
.datepicker td.active,
.datepicker td.active:hover {
@apply bg-accent/90 border border-accent text-accent-foreground;
}
/* Introduced by Day8 from http://eternicode.github.io/bootstrap-datepicker */
.datepicker td.today,
.datepicker td.today:hover {
background-color: #ffcd70;
border-color: #f59e00;
border-radius: 18px;
color: #fff;
}
.datepicker th.day-enabled,
label.day-enabled {
font-weight: normal;
font-size: 10px;
color: #333;
}
.datepicker th.selectable {
@apply font-normal text-accent;
}
.datepicker th.day-disabled {
font-weight: normal;
font-size: 10px;
color: #999;
}
.datepicker td.week,
.datepicker th.week {
font-size: 80%;
color: #ccc;
}
.datepicker th.month {
width: auto;
font-size: 14px;
color: var(--ls-title-text-color);
}
.dropdown-button {
cursor: pointer;
height: 32px;
font-size: 13px;
font-weight: normal;
}
.dropdown-button.activator {
width: 40px;
color: #777;
/* background-color: #F7F7F7 */
}
.table-condensed > thead > tr > th,
.table-condensed > tbody > tr > th,
.table-condensed > tfoot > tr > th,
.table-condensed > thead > tr > td,
.table-condensed > tbody > tr > td,
.table-condensed > tfoot > tr > td {
padding: 5px;
}
.dark-theme .datepicker {
background: var(--ls-secondary-background-color);
}
.dark-theme .datepicker th.day-disabled,
.dark-theme .datepicker th.disabled,
.dark-theme .datepicker td.disabled,
.dark-theme .datepicker td.off {
color: #666;
}
.dark-theme .datepicker th.day-enabled,
.dark-theme label.day-enabled {
color: currentColor;
}
.datepicker tr:nth-child(odd),
.datepicker tr:nth-child(even),
.dark-theme .datepicker tr:nth-child(odd),
.dark-theme .datepicker tr:nth-child(even) {
background: transparent;
}
.datepicker th,
.datepicker tr,
.datepicker td,
.dark-theme .datepicker th,
.dark-theme .datepicker tr,
.dark-theme .datepicker td {
border-bottom: none;
}
#time-repeater {
input.form-input, select.form-select {
@apply h-8 mt-0;

View File

@@ -18,13 +18,6 @@
:repeater {}})
(defonce *timestamp (atom default-timestamp-value))
(defn- date->goog-date
[d]
(cond
(some->> d (instance? js/Date))
(goog.date.Date. (.getFullYear d) (.getMonth d) (.getDate d))
:else d))
(defonce *show-time? (atom false))
(rum/defc time-input < rum/reactive
[default-value]
@@ -97,7 +90,7 @@
[e]
(when e (util/stop e))
(let [{:keys [repeater] :as timestamp} @*timestamp
date (-> (:date-picker/date @state/state) date->goog-date)
date (-> (:date-picker/date @state/state) date/js-date->goog-date)
timestamp (assoc timestamp :date (or date (t/today)))
kind (if (= "w" (:duration repeater)) "++" ".+")
timestamp (assoc-in timestamp [:repeater :kind] kind)

View File

@@ -11,6 +11,7 @@
[frontend.components.select :as component-select]
[frontend.state :as state]
[frontend.util :as util]
[logseq.shui.ui :as shui]
[frontend.search :as search]
[frontend.mixins :as mixins]
[logseq.graph-parser.db :as gp-db]
@@ -74,50 +75,58 @@
:aria-label "Full text search"
:on-change #(reset! *input-value (util/evalue %))}]))
(defonce *shown-datepicker (atom nil))
(defonce *between-dates (atom {}))
(rum/defcs datepicker < rum/reactive
(rum/local nil ::input-value)
{:init (fn [state]
(when (:auto-focus (last (:rum/args state)))
(reset! *shown-datepicker (first (:rum/args state))))
state)
:will-unmount (fn [state]
(swap! *between-dates dissoc (first (:rum/args state)))
state)}
(rum/local nil ::input-value)
{:will-unmount (fn [state]
(swap! *between-dates dissoc (first (:rum/args state)))
state)}
[state id placeholder {:keys [auto-focus]}]
(let [*input-value (::input-value state)
show? (= id (rum/react *shown-datepicker))]
(let [*input-value (::input-value state)]
[:div.ml-4
[:input.query-builder-datepicker.form-input.block.sm:text-sm.sm:leading-5
{:auto-focus (or auto-focus false)
:placeholder placeholder
:aria-label placeholder
:value @*input-value
:on-click #(reset! *shown-datepicker id)}]
(when show?
(ui/datepicker nil {:on-change (fn [_e date]
(let [journal-date (date/journal-name date)]
(reset! *input-value journal-date)
(reset! *shown-datepicker nil)
(swap! *between-dates assoc id journal-date)))}))]))
:value (some-> @*input-value (first))
:on-focus (fn [^js e]
(js/setTimeout
#(shui/popup-show! (.-target e)
(let [select-handle! (fn [^js d]
(let [gd (date/js-date->goog-date d)
journal-date (date/js-date->journal-title gd)]
(reset! *input-value [journal-date d])
(swap! *between-dates assoc id journal-date))
(shui/popup-hide!))]
(shui/calendar
{:mode "single"
:initial-focus true
:selected (some-> @*input-value (second))
:on-select select-handle!
:on-day-key-down (fn [^js d _ ^js e]
(when (= "Enter" (.-key e))
(select-handle! d)
(util/stop e)))}))
{:id :query-datepicker
:align :start}) 16))}]]))
(rum/defcs between <
(rum/local nil ::start)
(rum/local nil ::end)
[state {:keys [tree loc] :as opts}]
[:div.between-date {:on-pointer-down (fn [e] (util/stop-propagation e))}
[:div.between-date.p-4 {:on-pointer-down (fn [e] (util/stop-propagation e))}
[:div.flex.flex-row
[:div.font-medium.mt-2 "Between: "]
(datepicker :start "Start date" (merge opts {:auto-focus true}))
(datepicker :end "End date" opts)]
(ui/button "Submit"
:on-click (fn []
(let [{:keys [start end]} @*between-dates]
(when (and start end)
(let [clause [:between [:page-ref start] [:page-ref end]]]
(append-tree! tree opts loc clause)
(reset! *between-dates {}))))))])
[:p.pt-2
(ui/button "Submit"
:on-click (fn []
(let [{:keys [start end]} @*between-dates]
(when (and start end)
(let [clause [:between [:page-ref start] [:page-ref end]]]
(append-tree! tree opts loc clause)
(reset! *between-dates {}))))))]])
(rum/defc property-select
[*mode *property]
@@ -266,21 +275,19 @@
(append-tree! *tree opts loc [(keyword value)])
:else
(do (reset! *mode value)
((:toggle-fn opts)))))
(reset! *mode value)))
{:input-default-placeholder "Add filter/operator"})])]))
(rum/defc add-filter
[*find *tree loc clause]
(ui/dropdown
(fn [{:keys [toggle-fn]}]
[:a.flex.add-filter {:title "Add clause"
:on-click toggle-fn}
(ui/icon "plus" {:style {:font-size 20}})])
(fn [{:keys [toggle-fn]}]
(picker *find *tree loc clause {:toggle-fn toggle-fn}))
{:modal-class (util/hiccup->class
"origin-top-right.absolute.left-0.mt-2.ml-2.rounded-md.shadow-lg")}))
[:a.flex.add-filter
{:title "Add clause"
:on-click (fn [^js e]
(shui/popup-show! (.-target e)
(fn [{:keys [id]}]
(picker *find *tree loc clause {:toggle-fn #(shui/popup-hide! id)}))
{}))}
(ui/icon "plus" {:style {:font-size 20}})])
(declare clauses-group)

View File

@@ -172,6 +172,13 @@
[date]
(journal-name (tc/to-local-date date)))
(defn js-date->goog-date
[d]
(cond
(some->> d (instance? js/Date))
(goog.date.Date. (.getFullYear d) (.getMonth d) (.getDate d))
:else d))
(comment
(def default-formatter (tf/formatter "MMM do, yyyy"))
(def zh-formatter (tf/formatter "YYYY年MM月dd日"))

View File

@@ -238,10 +238,6 @@
@current-idx))
(on-chosen-open-link (nth matched @current-idx) false))))))
;; date-picker
;; TODO: find a better way
(def *internal-model (rum/cursor state/state :date-picker/date))
(defn- non-edit-input?
[]
(when-let [elem js/document.activeElement]
@@ -255,42 +251,6 @@
(or (non-edit-input?)
(util/select? elem))))
(defn- inc-date [date n] (plus date (days n)))
(defn- inc-week [date n] (plus date (weeks n)))
(defn shortcut-complete
[state e]
(let [{:keys [on-change deadline-or-schedule?]} (last (:rum/args state))]
(when (and on-change
(not (input-or-select?)))
(when-not deadline-or-schedule?
(on-change e @*internal-model)))))
(defn shortcut-prev-day
[_state e]
(when-not (input-or-select?)
(util/stop e)
(swap! *internal-model inc-date -1)))
(defn shortcut-next-day
[_state e]
(when-not (input-or-select?)
(util/stop e)
(swap! *internal-model inc-date 1)))
(defn shortcut-prev-week
[_state e]
(when-not (input-or-select?)
(util/stop e)
(swap! *internal-model inc-week -1)))
(defn shortcut-next-week
[_state e]
(when-not (input-or-select?)
(util/stop e)
(swap! *internal-model inc-week 1)))
(defn toggle-cards!
[]
(if (and (= :srs (:modal/id @state/state)) (:modal/show? @state/state))

View File

@@ -51,25 +51,7 @@
;; * :file-graph? - Optional boolean to identify a command to only be run in file graphs
;; and warned gracefully in db graphs
(def ^:large-vars/data-var all-built-in-keyboard-shortcuts
;; BUG: Actually, "enter" is registered by mixin behind a "when inputing" guard
;; So this setting item does not cover all cases.
;; See-also: frontend.components.datetime/time-repeater
{:date-picker/complete {:binding "enter"
:fn ui-handler/shortcut-complete}
:date-picker/prev-day {:binding "left"
:fn ui-handler/shortcut-prev-day}
:date-picker/next-day {:binding "right"
:fn ui-handler/shortcut-next-day}
:date-picker/prev-week {:binding ["up" "ctrl+p"]
:fn ui-handler/shortcut-prev-week}
:date-picker/next-week {:binding ["down" "ctrl+n"]
:fn ui-handler/shortcut-next-week}
:pdf/previous-page {:binding "alt+p"
{:pdf/previous-page {:binding "alt+p"
:fn pdf-utils/prev-page}
:pdf/next-page {:binding "alt+n"
@@ -953,11 +935,6 @@
:auto-complete/shift-complete
:auto-complete/meta-complete
:auto-complete/open-link
:date-picker/prev-day
:date-picker/next-day
:date-picker/prev-week
:date-picker/next-week
:date-picker/complete
:git/commit
:dev/show-block-data
:dev/show-block-ast

View File

@@ -27,7 +27,6 @@
[frontend.rum :as r]
[frontend.state :as state]
[frontend.storage :as storage]
[frontend.ui.date-picker]
[frontend.util :as util]
[frontend.util.cursor :as cursor]
[goog.dom :as gdom]
@@ -583,8 +582,6 @@
(when empty-placeholder
empty-placeholder))]))
(def datepicker frontend.ui.date-picker/date-picker)
(defn toggle
([on? on-click] (toggle on? on-click false))
([on? on-click small?]

View File

@@ -1,189 +0,0 @@
(ns ^:no-doc frontend.ui.date-picker
(:require [cljs-time.core :refer [after? before? day day-of-week days first-day-of-the-month minus month months plus year]]
[cljs-time.format :refer [formatter unparse]]
[frontend.modules.shortcut.core :as shortcut]
[frontend.state :as state]
[frontend.util :as util :refer [deref-or-value now->utc]]
[rum.core :as rum]))
;; Adapted from re-com date-picker
;; TODO: add left, right, up, down, enter bindings
;; Loosely based on ideas: https://github.com/dangrossman/bootstrap-daterangepicker
;; --- cljs-time facades ------------------------------------------------------
(def ^:const month-format (formatter "MMMM yyyy"))
(def ^:const week-format (formatter "ww"))
(defn- month-label [date] (unparse month-format date))
(defn- dec-month [date] (minus date (months 1)))
(defn- inc-month [date] (plus date (months 1)))
(defn- inc-date [date n] (plus date (days n)))
(defn previous
"If date fails pred, subtract period until true, otherwise answer date"
;; date - a date object that satisfies cljs-time.core/DateTimeProtocol.
;; If omitted, use now->utc, which returns a goog.date.UtcDateTime version of now with time removed.
;; pred - can be one of cljs-time.predicate e.g. sunday? but any valid pred is supported.
;; period - a period which will be subtracted see cljs-time.core periods
;; Note: If period and pred do not represent same granularity, some steps may be skipped
; e.g Pass a Wed date, specify sunday? as pred and a period (days 2) will skip one Sunday.
([pred]
(previous pred (now->utc)))
([pred date]
(previous pred date (days 1)))
([pred date period]
(if (pred date)
date
(recur pred (minus date period) period))))
(defn- =date [date1 date2]
(and
(= (year date1) (year date2))
(= (month date1) (month date2))
(= (day date1) (day date2))))
(defn- <=date [date1 date2]
(or (=date date1 date2) (before? date1 date2)))
(defn- >=date [date1 date2]
(or (=date date1 date2) (after? date1 date2)))
(def ^:private days-vector
[{:key :Mo :short-name "M" :name "MON"}
{:key :Tu :short-name "T" :name "TUE"}
{:key :We :short-name "W" :name "WED"}
{:key :Th :short-name "T" :name "THU"}
{:key :Fr :short-name "F" :name "FRI"}
{:key :Sa :short-name "S" :name "SAT"}
{:key :Su :short-name "S" :name "SUN"}])
(defn- rotate
[n coll]
(let [c (count coll)]
(take c (drop (mod n c) (cycle coll)))))
(defn- is-day-pred [d]
#(= (day-of-week %) (inc d)))
;; ----------------------------------------------------------------------------
(def *internal-model (rum/cursor state/state :date-picker/date))
(defn- main-div-with
[table-div class style attr]
[:div.rc-datepicker-wrapper
[:div {:style {:border-radius 4}}
[:div (merge
{:class (str "rc-datepicker datepicker noselect " class)
:style (merge {:font-size "13px"
:position "static"}
style)}
attr)
table-div]]])
(rum/defc table-thead
"Answer 2 x rows showing month with nav buttons and days NOTE: not internationalized"
[display-month {show-weeks? :show-weeks? minimum :minimum maximum :maximum start-of-week :start-of-week}]
(let [prev-date (dec-month display-month)
minimum (deref-or-value minimum)
maximum (deref-or-value maximum)
prev-enabled? (if minimum (after? prev-date (dec-month minimum)) true)
next-date (inc-month display-month)
next-enabled? (if maximum (before? next-date maximum) true)
template-row (if show-weeks? [:tr [:th]] [:tr])]
[:thead
(conj template-row
[:th {:class (str "prev " (if prev-enabled? "available selectable" "disabled"))
:style {:padding "0px"}
:on-click #(when prev-enabled? (reset! *internal-model prev-date))}
[:span.font-bold "<"]]
[:th {:class "month" :col-span "5"} (month-label display-month)]
[:th {:class (str "next " (if next-enabled? "available selectable" "disabled"))
:style {:padding "0px"}
:on-click #(when next-enabled? (reset! *internal-model next-date))}
[:span.font-bold ">"]])
(conj template-row
(for [day (rotate start-of-week days-vector)]
^{:key (:key day)} [:th {:class "day-enabled"} (str (:name day))]))]))
(defn- table-td
[date focus-month selected today {minimum :minimum maximum :maximum :as attributes} disabled? on-change]
;;following can be simplified and terse
(let [minimum (deref-or-value minimum)
maximum (deref-or-value maximum)
enabled-min (if minimum (>=date date minimum) true)
enabled-max (if maximum (<=date date maximum) true)
enabled-day (and enabled-min enabled-max)
disabled-day? (if enabled-day
(not ((:selectable-fn attributes) date))
true)
classes (cond disabled? "off"
disabled-day? "off"
(= focus-month (month date)) "available"
:else "available off")
classes (cond (and selected (=date selected date)) (str classes " active start-date end-date")
(and today (=date date today)) (str classes " today")
:else classes)
on-click (fn [e]
(when-not (or disabled? disabled-day?)
(reset! *internal-model date)
(on-change e date)))]
[:td {:class classes
:on-click on-click} (day date)]))
(defn- week-td [date]
[:td {:class "week"} (unparse week-format date)])
(defn- table-tr
"Return 7 columns of date cells from date inclusive"
[date focus-month selected attributes disabled? on-change]
; {:pre [(sunday? date)]}
(let [table-row (if (:show-weeks? attributes) [:tr (week-td date)] [:tr])
row-dates (map #(inc-date date %) (range 7))
today (when (:show-today? attributes) (now->utc))]
(into table-row (map #(table-td % focus-month selected today attributes disabled? on-change) row-dates))))
(rum/defc table-tbody
"Return matrix of 6 rows x 7 cols table cells representing 41 days from start-date inclusive"
[display-month selected attributes disabled? on-change]
(let [start-of-week (:start-of-week attributes)
current-start (previous (is-day-pred start-of-week) display-month)
focus-month (month display-month)
row-start-dates (map #(inc-date current-start (* 7 %)) (range 6))]
(into [:tbody] (map #(table-tr % focus-month selected attributes disabled? on-change) row-start-dates))))
(defn- configure
"Augment passed attributes with extra info/defaults"
[attributes]
(let [selectable-fn (if (-> attributes :selectable-fn fn?)
(:selectable-fn attributes)
(constantly true))]
(merge attributes {:selectable-fn selectable-fn})))
(rum/defc date-picker < rum/reactive
{:init (fn [state]
(reset! *internal-model (first (:rum/args state)))
state)}
(shortcut/mixin :shortcut.handler/date-picker false)
[_model {:keys [on-change disabled? start-of-week class style attr]
:or {start-of-week (state/get-start-of-week)} ;; Default to Sunday
:as args}]
(let [internal-model (util/react *internal-model)
display-month (first-day-of-the-month (or internal-model (now->utc)))
props-with-defaults (merge args {:start-of-week start-of-week})
configuration (configure props-with-defaults)]
(main-div-with
[:table.table-auto {:class "table-condensed"}
(table-thead display-month configuration)
(table-tbody display-month internal-model configuration disabled? on-change)]
class
style
attr)))

View File

@@ -650,12 +650,7 @@
;; Commands are nested for now to stay in sync with the shortcuts system.
;; Other languages should not nest keys under :commands
:commands
{:date-picker/complete "Date picker: Choose selected day"
:date-picker/prev-day "Date picker: Select previous day"
:date-picker/next-day "Date picker: Select next day"
:date-picker/prev-week "Date picker: Select previous week"
:date-picker/next-week "Date picker: Select next week"
:pdf/previous-page "Pdf: Previous page of current pdf doc"
{:pdf/previous-page "Pdf: Previous page of current pdf doc"
:pdf/next-page "Pdf: Next page of current pdf doc"
:pdf/close "Pdf: Close current pdf doc"
:pdf/find "Pdf: Search text of current pdf doc"

View File

@@ -76,63 +76,7 @@
(is (= (m+ 3 5) 8))
(is (= @actual-ops 4))
(is (= (m+ 3 5) 8))
(is (= @actual-ops 4))))
(testing "memoize-last nested mapping test"
(let [actual-ops (atom 0)
flatten-f (util/memoize-last (fn [& args]
(swap! actual-ops inc) ;; side effect for counting
(apply #'shortcut-data-helper/flatten-bindings-by-id (conj (vec args) nil true))))
target (atom {:part1 {:date-picker/complete {:binding "enter"
:fn "ui-handler/shortcut-complete"}
:date-picker/prev-day {:binding "left"
:fn "ui-handler/shortcut-prev-day"}}
:part2 {:date-picker/next-day {:binding "right"
:fn "ui-handler/shortcut-next-day"}
:date-picker/prev-week {:binding ["up" "ctrl+p"]
:fn "ui-handler/shortcut-prev-week"}}})]
(is (= (flatten-f @target) {:date-picker/complete "enter"
:date-picker/prev-day "left"
:date-picker/next-day "right"
:date-picker/prev-week ["up" "ctrl+p"]}))
(is (= @actual-ops 1))
(is (= (flatten-f @target) {:date-picker/complete "enter"
:date-picker/prev-day "left"
:date-picker/next-day "right"
:date-picker/prev-week ["up" "ctrl+p"]}))
(is (= @actual-ops 1))
;; edit value
(swap! target assoc-in [:part1 :date-picker/complete :binding] "tab")
(is (= (flatten-f @target) {:date-picker/complete "tab"
:date-picker/prev-day "left"
:date-picker/next-day "right"
:date-picker/prev-week ["up" "ctrl+p"]}))
(is (= @actual-ops 2))
(is (= (flatten-f @target) {:date-picker/complete "tab"
:date-picker/prev-day "left"
:date-picker/next-day "right"
:date-picker/prev-week ["up" "ctrl+p"]}))
(is (= @actual-ops 2))
(is (= (flatten-f @target) {:date-picker/complete "tab"
:date-picker/prev-day "left"
:date-picker/next-day "right"
:date-picker/prev-week ["up" "ctrl+p"]}))
(is (= @actual-ops 2))
;; edit key
(swap! target assoc :part3 {:date-picker/next-week {:binding "down"
:fn "ui-handler/shortcut-next-week"}})
(is (= (flatten-f @target) {:date-picker/complete "tab"
:date-picker/prev-day "left"
:date-picker/next-day "right"
:date-picker/prev-week ["up" "ctrl+p"]
:date-picker/next-week "down"}))
(is (= @actual-ops 3))
(is (= (flatten-f @target) {:date-picker/complete "tab"
:date-picker/prev-day "left"
:date-picker/next-day "right"
:date-picker/prev-week ["up" "ctrl+p"]
:date-picker/next-week "down"}))
(is (= @actual-ops 3)))))
(is (= @actual-ops 4)))))
(deftest test-media-format-from-input
(testing "predicate file type from ext (html5 supported)"