diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js index 0f4b35647..e721dc59b 100644 --- a/frontend/gulpfile.js +++ b/frontend/gulpfile.js @@ -93,7 +93,7 @@ function readLocales() { return JSON.stringify(result); } -function readConfig() { +function readConfig(data) { const publicURL = process.env.UXBOX_PUBLIC_URL; const demoWarn = process.env.UXBOX_DEMO_WARNING; const deployDate = process.env.UXBOX_DEPLOY_DATE; @@ -115,6 +115,8 @@ function readConfig() { cfg.deployCommit = deployCommit; } + Object.assign(cfg, data); + return JSON.stringify(cfg); } @@ -128,7 +130,7 @@ function templatePipeline(options) { const themes = ["default"]; const locales = readLocales(); - const config = readConfig(); + const config = readConfig({themes}); const tmpl = mustache({ ts: ts, @@ -174,22 +176,21 @@ gulp.task("templates", gulp.series("template:main")); * Development ***********************************************/ -gulp.task("dev:clean", function(next) { +gulp.task("clean", function(next) { rimraf(paths.output, next); }); -gulp.task("dev:copy:images", function() { +gulp.task("copy:assets:images", function() { return gulp.src(paths.resources + "images/**/*") .pipe(gulp.dest(paths.output + "images/")); }); -gulp.task("dev:copy:fonts", function() { +gulp.task("copy:assets:fonts", function() { return gulp.src(paths.resources + "fonts/**/*") .pipe(gulp.dest(paths.output + "fonts/")); }); -gulp.task("dev:copy", gulp.parallel("dev:copy:images", - "dev:copy:fonts")); +gulp.task("copy:assets", gulp.parallel("copy:assets:images", "copy:assets:fonts")); gulp.task("dev:dirs", function(next) { mkdirp("./resources/public/css/").then(() => next()) @@ -198,18 +199,18 @@ gulp.task("dev:dirs", function(next) { gulp.task("watch:main", function() { gulp.watch(paths.scss, gulp.series("scss")); gulp.watch(paths.resources + "images/**/*", - gulp.series("svg:sprite", - "dev:copy:images")); + gulp.series("svg:sprite", "copy:assets:images")); gulp.watch([paths.resources + "templates/*.mustache", paths.resources + "locales.json"], gulp.series("templates")); }); +gulp.task("build", gulp.parallel("scss", "svg:sprite", "templates", "copy:assets")); + gulp.task("watch", gulp.series( "dev:dirs", - gulp.parallel("scss", "templates", "svg:sprite"), - "dev:copy", + "build", "watch:main" )); @@ -231,10 +232,3 @@ gulp.task("dist:gzip", function() { .pipe(gzip({gzipOptions: {level: 9}})) .pipe(gulp.dest(paths.dist)); }); - -gulp.task("dist", gulp.series( - "dev:clean", - "dist:clean", - gulp.parallel("scss", "templates", "svg:sprite", "dev:copy"), - "dist:copy" -)); diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json index 680307f9b..5257a1349 100644 --- a/frontend/resources/locales.json +++ b/frontend/resources/locales.json @@ -1,4 +1,5 @@ { + "dashboard.grid.delete" : { "used-in" : [ "src/uxbox/main/ui/dashboard/project.cljs:61", "src/uxbox/main/ui/dashboard/grid.cljs:92" ], "translations" : { diff --git a/frontend/resources/templates/index.mustache b/frontend/resources/templates/index.mustache index 4ddad2743..1b8e1a80c 100644 --- a/frontend/resources/templates/index.mustache +++ b/frontend/resources/templates/index.mustache @@ -18,8 +18,7 @@ window.uxboxTranslations = JSON.parse({{& translations }}); window.uxboxThemes = {{& themes }}; - - - + + diff --git a/frontend/scripts/build-app.sh b/frontend/scripts/build-app.sh index 9af9b85a5..02e1a5e67 100755 --- a/frontend/scripts/build-app.sh +++ b/frontend/scripts/build-app.sh @@ -7,9 +7,11 @@ npm ci export NODE_ENV=production; -npx gulp dist:clean || exit 1; -npx gulp dist || exit 1; +# Clean the output directory +npx gulp clean || exit 1; shadow-cljs release main - +npx gulp build || exit 1; +npx gulp dist:clean || exit 1; +npx gulp dist:copy || exit 1; npx gulp dist:gzip || exit 1; diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index 6a1f74e07..7113cf14b 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -13,16 +13,17 @@ :modules {:shared {:entries []} :main {:entries [uxbox.main] - :depends-on #{:shared}} + :depends-on #{:shared} + :init-fn uxbox.main/init} :worker {:entries [uxbox.worker] :web-worker true :depends-on #{:shared}}} :compiler-options {:output-feature-set :es8 + ;; :optimizations :simple :output-wrapper false} :release - {:output-dir "target/dist/js" - :compiler-options + {:compiler-options {:fn-invoke-direct true :source-map true :anon-fn-naming-policy :off diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 565051224..2a8ab79a2 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -1907,6 +1907,20 @@ (let [page-id (::page-id state)] (assoc-in state [:workspace-data page-id :objects id :hidden] hidden?))) + +(defn toggle-collapse + [id] + (ptk/reify ::toggle-collapse + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-local :expanded id] not)))) + +(def collapse-all + (ptk/reify ::collapse-all + ptk/UpdateEvent + (update [_ state] + (update state :workspace-local dissoc :expanded)))) + ;; --- Shape Blocking (declare impl-update-shape-blocked) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs index 8e595a283..e1a275618 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs @@ -11,9 +11,11 @@ (ns uxbox.main.ui.workspace.sidebar.layers (:require [rumext.alpha :as mf] + [okulary.core :as l] [uxbox.common.data :as d] [uxbox.builtins.icons :as i] [uxbox.main.data.workspace :as dw] + [uxbox.main.data.helpers :as dh] [uxbox.main.refs :as refs] [uxbox.main.store :as st] [uxbox.main.ui.hooks :as hooks] @@ -74,33 +76,27 @@ {:on-double-click on-click} (:name shape "")]))) -(defn- layer-item-memo-equals? - [nprops oprops] - (let [n-item (unchecked-get nprops "item") - o-item (unchecked-get oprops "item") - n-selc (unchecked-get nprops "selected") - o-selc (unchecked-get oprops "selected") - n-indx (unchecked-get nprops "index") - o-indx (unchecked-get oprops "index")] - ;; (js/console.log "FOR" (:name n-item) - ;; "NEW SEL" n-selc - ;; "OLD SEL" o-selc)6 - (and (identical? n-item o-item) - (identical? n-indx o-indx) - (identical? n-selc o-selc)))) - -(declare layer-item) +(defn- make-collapsed-iref + [id] + #(-> (l/in [:expanded id]) + (l/derived refs/workspace-local))) (mf/defc layer-item - ;; {::mf/wrap [#(mf/memo' % layer-item-memo-equals?)]} [{:keys [index item selected objects] :as props}] (let [selected? (contains? selected (:id item)) - collapsed? (mf/use-state false) + + expanded-iref (mf/use-memo + (mf/deps (:id item)) + (make-collapsed-iref (:id item))) + + expanded? (mf/deref expanded-iref) toggle-collapse (fn [event] (dom/stop-propagation event) - (swap! collapsed? not)) + (if (and expanded? (kbd/shift? event)) + (st/emit! dw/collapse-all) + (st/emit! (dw/toggle-collapse (:id item))))) toggle-blocking (fn [event] @@ -163,7 +159,6 @@ :index index :name (:name item)}) ] - ;; (prn "layer-item" (:name item) index) [:li {:on-context-menu on-context-menu :ref dref :data-index index @@ -190,36 +185,91 @@ (when (:shapes item) [:span.toggle-content {:on-click toggle-collapse - :class (when-not @collapsed? "inverse")} + :class (when expanded? "inverse")} i/arrow-slide])] - (when (and (:shapes item) (not @collapsed?)) + (when (and (:shapes item) expanded?) [:ul.element-children (for [[index id] (reverse (d/enumerate (:shapes item)))] (when-let [item (get objects id)] - [:& uxbox.main.ui.workspace.sidebar.layers/layer-item + [:& layer-item {:item item :selected selected :index index :objects objects :key (:id item)}]))])])) +(defn frame-wrapper-memo-equals? + [oprops nprops] + (let [new-sel (unchecked-get nprops "selected") + old-sel (unchecked-get oprops "selected") + new-itm (unchecked-get nprops "item") + old-itm (unchecked-get oprops "item") + new-idx (unchecked-get nprops "index") + old-idx (unchecked-get oprops "index") + new-obs (unchecked-get nprops "objects") + old-obs (unchecked-get oprops "objects")] + (and (= new-itm old-itm) + (identical? new-idx old-idx) + (let [childs (dh/get-children (:id new-itm) new-obs) + childs' (conj childs (:id new-itm))] + (and (or (= new-sel old-sel) + (not (or (boolean (some new-sel childs')) + (boolean (some old-sel childs'))))) + (loop [ids (rest childs) + id (first childs)] + (if (nil? id) + true + (if (= (get new-obs id) + (get old-obs id)) + (recur (rest ids) + (first ids)) + false)))))))) + +;; This components is a piece for sharding equality check between top +;; level frames and try to avoid rerender frames that are does not +;; affected by the selected set. + +(mf/defc frame-wrapper + {::mf/wrap-props false + ::mf/wrap [#(mf/memo' % frame-wrapper-memo-equals?)]} + [props] + [:> layer-item props]) + +(def ^:private layers-objects + (letfn [(strip-data [obj] + (select-keys obj [:id :name :blocked :hidden :shapes :type])) + (selector [{:keys [objects] :as data}] + (persistent! + (reduce-kv (fn [res id obj] + (assoc! res id (strip-data obj))) + (transient {}) + objects)))] + (l/derived selector refs/workspace-data =))) + (mf/defc layers-tree {::mf/wrap [mf/memo]} - [props] + [] (let [selected (mf/deref refs/selected-shapes) - data (mf/deref refs/workspace-data) - objects (:objects data) + objects (mf/deref layers-objects) root (get objects uuid/zero)] - ;; [:& perf/profiler {:label "layers-tree" :enabled false} [:ul.element-list (for [[index id] (reverse (d/enumerate (:shapes root)))] - [:& layer-item - {:item (get objects id) - :selected selected - :index index - :objects objects - :key id}])])) + (let [obj (get objects id)] + (if (= :frame (:type obj)) + [:& frame-wrapper + {:item (get objects id) + :selected selected + :index index + :objects objects + :key id}] + [:& layer-item + {:item (get objects id) + :selected selected + :index index + :objects objects + :key id}])))])) + ;; --- Layers Toolbox diff --git a/frontend/src/uxbox/main/worker.cljs b/frontend/src/uxbox/main/worker.cljs index af390d7dc..b5f56c04e 100644 --- a/frontend/src/uxbox/main/worker.cljs +++ b/frontend/src/uxbox/main/worker.cljs @@ -10,6 +10,7 @@ (ns uxbox.main.worker (:require [cljs.spec.alpha :as s] + [uxbox.config :as cfg] [uxbox.common.spec :as us] [uxbox.util.worker :as uw]))