;; This Source Code Form is subject to the terms of the Mozilla Public ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; ;; Copyright (c) 2015-2016 Andrey Antukh ;; Copyright (c) 2015-2016 Juan de la Cruz (ns uxbox.main.data.workspace (:require [beicon.core :as rx] [uxbox.util.uuid :as uuid] [uxbox.main.constants :as c] [uxbox.util.rstore :as rs] [uxbox.util.schema :as sc] [uxbox.util.geom.point :as gpt] [uxbox.util.workers :as uw] [uxbox.main.state :as st] [uxbox.main.state.shapes :as stsh] [uxbox.main.data.core :refer (worker)] [uxbox.main.data.projects :as dp] [uxbox.main.data.pages :as udp] [uxbox.main.data.shapes :as uds] [uxbox.main.data.forms :as udf] [uxbox.main.data.lightbox :as udl] [uxbox.main.data.history :as udh] [uxbox.util.datetime :as dt] [uxbox.util.math :as mth] [uxbox.util.data :refer (index-of)])) ;; --- Constants (def zoom-levels [0.20 0.21 0.22 0.23 0.24 0.25 0.27 0.28 0.30 0.32 0.34 0.36 0.38 0.40 0.42 0.44 0.46 0.48 0.51 0.54 0.57 0.60 0.63 0.66 0.69 0.73 0.77 0.81 0.85 0.90 0.95 1.00 1.05 1.10 1.15 1.21 1.27 1.33 1.40 1.47 1.54 1.62 1.70 1.78 1.87 1.96 2.06 2.16 2.27 2.38 2.50 2.62 2.75 2.88 3.00]) ;; --- Initialize Workspace (declare initialize-alignment-index) (defrecord InitializeWorkspace [project page] rs/UpdateEvent (-apply-update [_ state] (if (:workspace state) (update state :workspace merge {:project project :page page :selected #{} :drawing nil}) (assoc state :workspace {:project project :zoom 1 :page page :flags #{:sitemap :drawtools :layers} :selected #{} :drawing nil}))) rs/WatchEvent (-apply-watch [_ state s] (let [page-id page page (get-in state [:pages-by-id page-id])] ;; Activate loaded if page is not fetched. (when-not page (reset! st/loader true)) (rx/merge (if page (rx/of (initialize-alignment-index page-id)) (rx/merge (rx/of (udp/fetch-pages project)) (->> (rx/filter udp/pages-fetched? s) (rx/take 1) (rx/do #(reset! st/loader false)) (rx/map #(initialize-alignment-index page-id))))) ;; Initial history loading (rx/of (udh/fetch-page-history page-id) (udh/fetch-pinned-page-history page-id))))) rs/EffectEvent (-apply-effect [_ state] ;; Optimistic prefetch of projects if them are not already fetched (when-not (seq (:projects-by-id state)) (rs/emit! (dp/fetch-projects))))) (defn initialize "Initialize the workspace state." [project page] (InitializeWorkspace. project page)) ;; --- Toggle Flag (defn toggle-flag "Toggle the enabled flag of the specified tool." [key] (reify rs/UpdateEvent (-apply-update [_ state] (let [flags (get-in state [:workspace :flags])] (if (contains? flags key) (assoc-in state [:workspace :flags] (disj flags key)) (assoc-in state [:workspace :flags] (conj flags key))))))) (defn select-for-drawing "Mark a shape selected for drawing in the canvas." [shape] (reify rs/UpdateEvent (-apply-update [_ state] (if (or (nil? shape) (= (get-in state [:workspace :drawing]) shape)) (update-in state [:workspace] dissoc :drawing) (assoc-in state [:workspace :drawing] shape))))) ;; --- Activate Workspace Flag (defrecord ActivateFlag [flag] rs/UpdateEvent (-apply-update [_ state] (update-in state [:workspace :flags] conj flag))) (defn activate-flag [flag] (ActivateFlag. flag)) ;; --- Copy to Clipboard (defrecord CopyToClipboard [] rs/UpdateEvent (-apply-update [_ state] (let [selected (get-in state [:workspace :selected]) item {:id (uuid/random) :created-at (dt/now) :items selected} clipboard (-> (:clipboard state) (conj item))] (assoc state :clipboard (if (> (count clipboard) 5) (pop clipboard) clipboard))))) (defn copy-to-clipboard "Copy selected shapes to clipboard." [] (CopyToClipboard.)) ;; --- Paste from Clipboard (defrecord PasteFromClipboard [id] rs/UpdateEvent (-apply-update [_ state] (let [page (get-in state [:workspace :page]) selected (if (nil? id) (first (:clipboard state)) (->> (:clipboard state) (filter #(= id (:id %))) (first)))] (stsh/duplicate-shapes state (:items selected) page)))) (defn paste-from-clipboard "Copy selected shapes to clipboard." ([] (PasteFromClipboard. nil)) ([id] (PasteFromClipboard. id))) ;; --- Increase Zoom (defrecord IncreaseZoom [] rs/UpdateEvent (-apply-update [_ state] (let [increase #(nth zoom-levels (+ (index-of zoom-levels %) 1) (last zoom-levels))] (update-in state [:workspace :zoom] (fnil increase 1))))) (defn increase-zoom [] (IncreaseZoom.)) ;; --- Decrease Zoom (defrecord DecreaseZoom [] rs/UpdateEvent (-apply-update [_ state] (let [decrease #(nth zoom-levels (- (index-of zoom-levels %) 1) (first zoom-levels))] (update-in state [:workspace :zoom] (fnil decrease 1))))) (defn decrease-zoom [] (DecreaseZoom.)) ;; --- Reset Zoom (defrecord ResetZoom [] rs/UpdateEvent (-apply-update [_ state] (assoc-in state [:workspace :zoom] 1))) (defn reset-zoom [] (ResetZoom.)) ;; --- Initialize Alignment Index (defrecord InitializeAlignmentIndex [id] rs/WatchEvent (-apply-watch [_ state s] (let [page (get-in state [:pages-by-id id]) opts (:options page) message {:cmd :grid/init :width c/viewport-width :height c/viewport-height :x-axis (:grid/x-axis opts c/grid-x-axis) :y-axis (:grid/y-axis opts c/grid-y-axis)}] (rx/merge (->> (uw/send! worker message) (rx/map #(activate-flag :grid/indexed))) (when (:grid/alignment opts) (rx/of (activate-flag :grid/alignment))))))) (defn initialize-alignment-index [id] (InitializeAlignmentIndex. id)) ;; --- Update Workspace Settings (Form) (defrecord SubmitWorkspaceSettings [id options] rs/WatchEvent (-apply-watch [_ state s] (rx/of (udp/update-page-options id options) (initialize-alignment-index id) (udf/clean :workspace/settings))) rs/EffectEvent (-apply-effect [_ state] (udl/close!))) (def submit-workspace-settings-schema {:grid/y-axis [sc/required sc/integer [sc/in-range 2 100]] :grid/x-axis [sc/required sc/integer [sc/in-range 2 100]] :grid/alignment [sc/boolean] :grid/color [sc/required sc/color]}) (defn submit-workspace-settings [id data] (let [schema submit-workspace-settings-schema [errors data] (sc/validate data schema)] (if errors (udf/assign-errors :workspace/settings errors) (SubmitWorkspaceSettings. id data))))