Browse Source

FIX #17 filter by date

isundil 1 year ago
parent
commit
f7e159ce98

+ 6 - 5
package.json

@@ -16,19 +16,20 @@
     "@dashboardcode/bsmultiselect": "^1.1.18",
     "@mdi/font": "^7.3.67",
     "@popperjs/core": "^2.11.8",
-    "whiskers": "^0.4.0",
     "bootstrap": "^5.3.2",
     "bootstrap-icons": "^1.11.2",
+    "bootstrap-slider": "^11.0.2",
     "craftlabhttpserver": "git:isundil/craftlabHttpServer",
     "crypto": "^1.0.1",
-    "ldap": "^0.7.1",
-    "ldapjs": "^3.0.4",
     "geokit": "^1.1.0",
-    "offline-geocoder": "^1.0.0",
     "imagemagick": "^0.1.3",
+    "ldap": "^0.7.1",
+    "ldapjs": "^3.0.4",
     "mime-types": "^2.1.35",
     "node-simple-router": "^0.10.2",
+    "offline-geocoder": "^1.0.0",
     "sqlite3": "^5.1.6",
-    "tmp": "^0.2.1"
+    "tmp": "^0.2.1",
+    "whiskers": "^0.4.0"
   }
 }

+ 2 - 0
router/bootstrap.js

@@ -7,5 +7,7 @@ module.exports = { register: app => {
     app.routerUtils.staticGet(app, "/public/bootstrap/bootstrap-icons.min.css", './node_modules/bootstrap-icons/font/bootstrap-icons.min.css');
     app.routerUtils.staticGet(app, "/public/bootstrap/fonts/bootstrap-icons.woff", './node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff');
     app.routerUtils.staticGet(app, "/public/bootstrap/fonts/bootstrap-icons.woff2", './node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2');
+    app.routerUtils.staticGet(app, "/public/bootstrap/bootstrap-slider.min.js", './node_modules/bootstrap-slider/dist/bootstrap-slider.min.js');
+    app.routerUtils.staticGet(app, "/public/bootstrap/bootstrap-slider.min.css", './node_modules/bootstrap-slider/dist/css/bootstrap-slider.min.css');
 }};
 

+ 13 - 0
static/public/css/style.css

@@ -273,3 +273,16 @@ body.login-visible .login-wrapper {
     margin-right: auto;
 }
 
+.slider-value {
+    display: inline;
+    width: auto;
+}
+
+.slider-col {
+    display: flex;
+}
+
+.slider-col > .slider {
+    flex: 1;
+}
+

+ 30 - 18
static/public/js/chronology.js

@@ -1,29 +1,41 @@
 
 $(() => {
-    var _MIN = _MAX = null;
-    window.chronology = {};
+    class Chronology
+    {
+        #min = null;
+        #max = null;
 
-    function onChronologyUpdated() {
-        console.log("Chronology update with ", _MIN, _MAX);
-    }
+        #onChronologyUpdated() {
+            console.log("Chronology update with ", this.#min, this.#max);
+            window.ReloadFilters(MediaStorage.Instance);
+        }
 
-    window.chronology.rebuildRange = function(_minTs, _maxTs) {
-        let updated = false;
+        rebuildRange(_minTs, _maxTs) {
+            let updated = false;
 
-        if (!_MIN || _MIN.getTime() !== _minTs) {
-            _MIN = new Date(_minTs);
-            updated = true;
+            if (!this.#min || this.#min.getTime() !== _minTs) {
+                this.#min = new Date(_minTs);
+                updated = true;
+            }
+            if (!this.#max || this.#max.getTime() !== _maxTs) {
+                this.#max = new Date(_maxTs);
+                updated = true;
+            }
+            if (updated)
+                this.#onChronologyUpdated();
         }
-        if (!_MAX || _MAX.getTime() !== _maxTs) {
-            _MAX = new Date(_maxTs);
-            updated = true;
+
+        getRange() {
+            return {
+                min: new Date(this.#min),
+                max: new Date(this.#max)
+            };
         }
-        if (updated)
-            onChronologyUpdated();
-    }
 
-    window.chronology.isInitialized = function() {
-        return _MIN && _MAX;
+        isInitialized() {
+            return this.#min && this.#max;
+        }
     }
 
+    window.chronology = new Chronology();
 });

+ 19 - 0
static/public/js/filters.js

@@ -23,6 +23,20 @@ window.FilterManager = (() => {
             this.#updateFilter();
         }
 
+        setChronologyRange(min, max) {
+            let updated = false;
+
+            if (this.#minDate != min) {
+                this.#minDate = min;
+                updated = true;
+            }
+            if (this.#maxDate != max) {
+                this.#maxDate = max;
+                updated = true;
+            }
+            updated && this.#updateFilter();
+        }
+
         isFiltering() {
             for (let i in this.#filters) {
                 if (this.#filters[i]?.length)
@@ -40,6 +54,9 @@ window.FilterManager = (() => {
         }
 
         match(mediaItem) {
+            const dateTime = mediaItem.date.getTime();
+            if (dateTime < this.#minDate || dateTime > this.#maxDate)
+                return false;
             for (let i in this.#filters) {
                 if (!this.#filters[i] || !this.#filters[i].length)
                     continue;
@@ -58,6 +75,8 @@ window.FilterManager = (() => {
         }
 
         #filters = {};
+        #minDate = 0;
+        #maxDate = Infinity;
     }
 
     return new FilterManager();

+ 62 - 1
static/public/js/uiFilter.js

@@ -1,12 +1,66 @@
 
 $(() => {
+    const INPUT_CONTAINER_CLASSES = "col-12 col-xl-3 col-md-4 col-sm-6";
+
+    function makeDoubleSlider(minValue, maxValue, serialize) {
+        if (!serialize)
+            serialize = i => i;
+        let resultContainer = document.createElement("div");
+        resultContainer.className = `slider-container ${INPUT_CONTAINER_CLASSES}`;
+        let sliderDiv = document.createElement("div");
+        sliderDiv.className = "container";
+        resultContainer.appendChild(sliderDiv);
+        let labelRow = document.createElement("div");
+        labelRow.className = "row justify-content-between";
+        let minValueLabel = document.createElement("div");
+        minValueLabel.className = "slider-value slider-min-value";
+        minValueLabel.textContent = serialize(minValue);
+        labelRow.appendChild(minValueLabel);
+        let maxValueLabel = document.createElement("div");
+        maxValueLabel.className = "slider-value slider-max-value";
+        maxValueLabel.textContent = serialize(maxValue);
+        labelRow.appendChild(maxValueLabel);
+        sliderDiv.appendChild(labelRow);
+        let inputContainer = document.createElement("div");
+        inputContainer.className = "container";
+        sliderDiv.appendChild(inputContainer);
+        let sliderCol = document.createElement("div");
+        sliderCol.className = "col slider-col";
+        inputContainer.appendChild(sliderCol);
+        let input = document.createElement("input");
+        input.type = "text";
+        input.className = "col";
+        input.dataset.sliderMin = minValue ?? 0;
+        input.dataset.sliderMax = maxValue ?? 100;
+        input.dataset.sliderValue = `[${input.dataset.sliderMin},${input.dataset.sliderMax}]`;
+        input.dataset.sliderTooltip = "hide";
+        sliderCol.appendChild(input);
+        let jQueryInput = $(input);
+        jQueryInput.slider();
+        let onChange = () => {};
+        jQueryInput.on('slideStop', evt => onChange({ min: evt.value[0], max: evt.value[1] }));
+        jQueryInput.on('slide', evt => {
+            minValueLabel.textContent = serialize(evt.value[0]);
+            maxValueLabel.textContent = serialize(evt.value[1]);
+        });
+        return {
+            container: resultContainer,
+            minValueLabel: minValueLabel,
+            maxValueLabel: maxValueLabel,
+            input: jQueryInput,
+            getInputValue: () => { const val = jQueryInput.slider('getValue'); return { min: val[0], max: val[1] }; },
+            onChange: (fnc) => { onChange = fnc; }
+        };
+    }
+
     window.ReloadFilters = function(mediaManager) {
         let buildFilterBar = (labelText, canBeEmpty, possibleValues) => {
             let result = document.createElement("div");
-            result.className = "col-12 col-xl-4 col-md-6"
+            result.className = INPUT_CONTAINER_CLASSES;
             let label = document.createElement('label');
             result.appendChild(label);
             label.textContent = labelText;
+            label.className = "container";
             let select = document.createElement('select');
             select.multiple = 'multiple';
             label.appendChild(select);
@@ -39,6 +93,13 @@ $(() => {
 
         let filterUi = buildFilterBar("Tags", true, Array.from(mediaManager.allTags).sort());
         container.appendChild(filterUi.content);
+
+        if (window.chronology.isInitialized()) {
+            let range = window.chronology.getRange();
+            let timeSlider = makeDoubleSlider(range.min.getTime(), range.max.getTime(), i => (new Date(i)).toLocaleDateString());
+            timeSlider.onChange(val => window.FilterManager.setChronologyRange(val.min, val.max));
+            container.appendChild(timeSlider.container);
+        }
     }
 
     document.getElementById('pch-navbar-filterToggle').addEventListener('click', e => {

+ 1 - 0
templates/_footer.js

@@ -2,6 +2,7 @@
 module.exports = `
 <script src="/public/js/jquery-3.6.1.min.js"></script>
 <script src="/public/bootstrap/bootstrap.bundle.min.js"></script>
+<script src="/public/bootstrap/bootstrap-slider.min.js"></script>
 <script src="/public/js/popper.min.js"></script>
 <script src="/public/js/BsMultiSelect.min.js"></script>
 <script src="/public/js/taskQueue.js"></script>

+ 1 - 0
templates/_header.js

@@ -7,6 +7,7 @@ module.exports = `
 <link type="text/css" rel="stylesheet" href="/public/mdi/materialdesignicons.min.css"/>
 <link type="text/css" rel="stylesheet" href="/public/bootstrap/bootstrap.min.css"/>
 <link type="text/css" rel="stylesheet" href="/public/bootstrap/bootstrap-icons.min.css"/>
+<link type="text/css" rel="stylesheet" href="/public/bootstrap/bootstrap-slider.min.css"/>
 <link type="text/css" rel="stylesheet" href="/public/css/BsMultiSelect.min.css"/>
 <link type="text/css" rel="stylesheet" href="/public/css/style.css"/>
 </head>