mirror of https://github.com/penpot/penpot.git
Merge pull request #7044 from penpot/niwinz-develop-refactor-versions-sidebar
♻️ Refactor versions sidebar
This commit is contained in:
commit
b6ecb4368e
|
|
@ -0,0 +1,23 @@
|
|||
;; 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) KALEIDOS INC
|
||||
|
||||
(ns app.common.types.profile
|
||||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as cm]))
|
||||
|
||||
(def schema:profile
|
||||
[:map {:title "Profile"}
|
||||
[:id ::sm/uuid]
|
||||
[:created-at {:optional true} ::cm/inst]
|
||||
[:fullname {:optional true} :string]
|
||||
[:email {:optional true} :string]
|
||||
[:lang {:optional true} :string]
|
||||
[:theme {:optional true} :string]
|
||||
[:photo-id {:optional true} ::sm/uuid]
|
||||
;; Only present on resolved profile objects, the resolve process
|
||||
;; takes the photo-id or geneates an image from the name
|
||||
[:photo-url {:optional true} :string]])
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.spec :as us]
|
||||
[app.common.types.profile :refer [schema:profile]]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.main.data.event :as ev]
|
||||
|
|
@ -27,16 +28,6 @@
|
|||
|
||||
;; --- SCHEMAS
|
||||
|
||||
(def ^:private
|
||||
schema:profile
|
||||
[:map {:title "Profile"}
|
||||
[:id ::sm/uuid]
|
||||
[:created-at {:optional true} :any]
|
||||
[:fullname {:optional true} :string]
|
||||
[:email {:optional true} :string]
|
||||
[:lang {:optional true} :string]
|
||||
[:theme {:optional true} :string]])
|
||||
|
||||
(def check-profile
|
||||
(sm/check-fn schema:profile))
|
||||
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@
|
|||
|
||||
(declare fetch-versions)
|
||||
|
||||
(defn init-version-state
|
||||
(defn init-versions-state
|
||||
[]
|
||||
(ptk/reify ::init-version-state
|
||||
(ptk/reify ::init-versions-state
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :workspace-versions default-state))
|
||||
|
|
@ -38,9 +38,9 @@
|
|||
(watch [_ _ _]
|
||||
(rx/of (fetch-versions)))))
|
||||
|
||||
(defn update-version-state
|
||||
(defn update-versions-state
|
||||
[version-state]
|
||||
(ptk/reify ::update-version-state
|
||||
(ptk/reify ::update-versions-state
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-versions merge version-state))))
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
(watch [_ state _]
|
||||
(when-let [file-id (:current-file-id state)]
|
||||
(->> (rp/cmd! :get-file-snapshots {:file-id file-id})
|
||||
(rx/map #(update-version-state {:status :loaded :data %})))))))
|
||||
(rx/map #(update-versions-state {:status :loaded :data %})))))))
|
||||
|
||||
(defn create-version
|
||||
[]
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
(rx/mapcat #(rp/cmd! :create-file-snapshot {:file-id file-id :label label}))
|
||||
(rx/mapcat
|
||||
(fn [{:keys [id]}]
|
||||
(rx/of (update-version-state {:editing id})
|
||||
(rx/of (update-versions-state {:editing id})
|
||||
(fetch-versions))))))))))
|
||||
|
||||
(defn rename-version
|
||||
|
|
@ -86,7 +86,7 @@
|
|||
(watch [_ state _]
|
||||
(let [file-id (:current-file-id state)]
|
||||
(rx/merge
|
||||
(rx/of (update-version-state {:editing false})
|
||||
(rx/of (update-versions-state {:editing nil})
|
||||
(ptk/event ::ev/event {::ev/name "rename-version"
|
||||
:file-id file-id}))
|
||||
(->> (rp/cmd! :update-file-snapshot {:id id :label label})
|
||||
|
|
@ -144,7 +144,7 @@
|
|||
|
||||
(->> (rp/cmd! :update-file-snapshot params)
|
||||
(rx/mapcat (fn [_]
|
||||
(rx/of (update-version-state {:editing id})
|
||||
(rx/of (update-versions-state {:editing id})
|
||||
(fetch-versions)
|
||||
(ptk/event ::ev/event {::ev/name "pin-version"})))))))))
|
||||
|
||||
|
|
|
|||
|
|
@ -27,13 +27,13 @@
|
|||
[app.main.ui.ds.notifications.context-notification :refer [context-notification*]]
|
||||
[app.main.ui.ds.notifications.shared.notification-pill :refer [notification-pill*]]
|
||||
[app.main.ui.ds.notifications.toast :refer [toast*]]
|
||||
[app.main.ui.ds.product.autosaved-milestone :refer [autosaved-milestone*]]
|
||||
[app.main.ui.ds.product.avatar :refer [avatar*]]
|
||||
[app.main.ui.ds.product.cta :refer [cta*]]
|
||||
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
|
||||
[app.main.ui.ds.product.input-with-meta :refer [input-with-meta*]]
|
||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||
[app.main.ui.ds.product.user-milestone :refer [user-milestone*]]
|
||||
[app.main.ui.ds.product.milestone :refer [milestone*]]
|
||||
[app.main.ui.ds.product.milestone-group :refer [milestone-group*]]
|
||||
[app.main.ui.ds.storybook :as sb]
|
||||
[app.main.ui.ds.tooltip.tooltip :refer [tooltip*]]
|
||||
[app.main.ui.ds.utilities.date :refer [date*]]
|
||||
|
|
@ -41,7 +41,6 @@
|
|||
[app.util.i18n :as i18n]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
||||
(i18n/init! cf/translations)
|
||||
|
||||
(def default
|
||||
|
|
@ -72,8 +71,8 @@
|
|||
:Swatch swatch*
|
||||
:Cta cta*
|
||||
:Avatar avatar*
|
||||
:AutosavedMilestone autosaved-milestone*
|
||||
:UserMilestone user-milestone*
|
||||
:Milestone milestone*
|
||||
:MilestoneGroup milestone-group*
|
||||
:Date date*
|
||||
;; meta / misc
|
||||
:meta
|
||||
|
|
|
|||
|
|
@ -9,37 +9,44 @@
|
|||
[app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.profile :refer [schema:profile]]
|
||||
[app.config :as cfg]
|
||||
[app.util.avatars :as avatars]
|
||||
[rumext.v2 :as mf]))
|
||||
[rumext.v2 :as mf]
|
||||
[rumext.v2.util :as mfu]))
|
||||
|
||||
(def ^:private schema:avatar
|
||||
[:map
|
||||
[:class {:optional true} :string]
|
||||
[:tag {:optional true} :string]
|
||||
[:name {:optional true} [:maybe :string]]
|
||||
[:url {:optional true} [:maybe :string]]
|
||||
[:color {:optional true} [:maybe :string]]
|
||||
[:profile schema:profile]
|
||||
[:selected {:optional true} :boolean]
|
||||
[:variant {:optional true}
|
||||
[:maybe [:enum "S" "M" "L"]]]])
|
||||
|
||||
(mf/defc avatar*
|
||||
{::mf/schema schema:avatar}
|
||||
(defn- get-url
|
||||
[{:keys [photo-url photo-id fullname]}]
|
||||
(or photo-url
|
||||
(some-> photo-id cfg/resolve-media)
|
||||
(avatars/generate {:name fullname})))
|
||||
|
||||
[{:keys [tag class name color url selected variant] :rest props}]
|
||||
(let [variant (or variant "S")
|
||||
url (if (and (some? url) (d/not-empty? url))
|
||||
url
|
||||
(avatars/generate {:name name :color color}))]
|
||||
[:> (or tag "div")
|
||||
{:class (d/append-class
|
||||
class
|
||||
(stl/css-case :avatar true
|
||||
:avatar-small (= variant "S")
|
||||
:avatar-medium (= variant "M")
|
||||
:avatar-large (= variant "L")
|
||||
:is-selected selected))
|
||||
:style {"--avatar-color" color}
|
||||
:title name}
|
||||
(mf/defc avatar*
|
||||
{::mf/schema (sm/schema schema:avatar)}
|
||||
[{:keys [tag class profile selected variant]}]
|
||||
(let [variant (d/nilv variant "S")
|
||||
profile (if (object? profile)
|
||||
(mfu/bean profile)
|
||||
profile)
|
||||
href (mf/with-memo [profile]
|
||||
(get-url profile))
|
||||
class' (stl/css-case :avatar true
|
||||
:avatar-small (= variant "S")
|
||||
:avatar-medium (= variant "M")
|
||||
:avatar-large (= variant "L")
|
||||
:is-selected selected)]
|
||||
[:> (d/nilv tag "div")
|
||||
{:class [class class']
|
||||
:title (:fullname profile)}
|
||||
[:div {:class (stl/css :avatar-image)}
|
||||
[:img {:alt name :src url}]]]))
|
||||
[:img {:alt (:fullname profile) :src href}]]]))
|
||||
|
|
|
|||
|
|
@ -13,9 +13,6 @@ export default {
|
|||
url: {
|
||||
control: { type: "text" },
|
||||
},
|
||||
color: {
|
||||
control: { type: "color" },
|
||||
},
|
||||
variant: {
|
||||
options: ["S", "M", "L"],
|
||||
control: { type: "select" },
|
||||
|
|
@ -27,11 +24,20 @@ export default {
|
|||
args: {
|
||||
name: "Ada Lovelace",
|
||||
url: "/images/avatar-blue.jpg",
|
||||
color: "#79d4ff",
|
||||
variant: "S",
|
||||
selected: false,
|
||||
},
|
||||
render: ({ ...args }) => <Avatar profile={{ fullname: "TEST" }} {...args} />,
|
||||
render: ({name, url, ...args }) => {
|
||||
const profile = {
|
||||
id: "00000000-0000-0000-0000-000000000000",
|
||||
fullname: name
|
||||
};
|
||||
if (url) {
|
||||
profile.photoUrl = url;
|
||||
};
|
||||
|
||||
return <Avatar profile={profile} {...args} />;
|
||||
}
|
||||
};
|
||||
|
||||
export const Default = {};
|
||||
|
|
|
|||
|
|
@ -4,83 +4,84 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.ds.product.user-milestone
|
||||
(ns app.main.ui.ds.product.milestone
|
||||
(:require-macros
|
||||
[app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as ct]
|
||||
[app.common.types.profile :refer [schema:profile]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.controls.input :refer [input*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.foundations.typography :as t]
|
||||
[app.main.ui.ds.foundations.typography.text :refer [text*]]
|
||||
[app.main.ui.ds.product.avatar :refer [avatar*]]
|
||||
[app.main.ui.ds.utilities.date :refer [valid-date?]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.object :as obj]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private schema:callback
|
||||
[:maybe [:fn fn?]])
|
||||
|
||||
(def ^:private schema:milestone
|
||||
[:map
|
||||
[:class {:optional true} :string]
|
||||
[:active {:optional true} :boolean]
|
||||
[:editing {:optional true} :boolean]
|
||||
[:locked {:optional true} :boolean]
|
||||
[:user
|
||||
[:map
|
||||
[:name {:optional true} [:maybe :string]]
|
||||
[:avatar {:optional true} [:maybe :string]]
|
||||
[:color {:optional true} [:maybe :string]]]]
|
||||
[:profile {:optional true} schema:profile]
|
||||
[:label :string]
|
||||
[:date [:fn valid-date?]]
|
||||
[:onOpenMenu {:optional true} [:maybe [:fn fn?]]]
|
||||
[:onFocusInput {:optional true} [:maybe [:fn fn?]]]
|
||||
[:onBlurInput {:optional true} [:maybe [:fn fn?]]]
|
||||
[:onKeyDownInput {:optional true} [:maybe [:fn fn?]]]])
|
||||
[:created-at ::ct/inst]
|
||||
[:on-open-menu {:optional true} schema:callback]
|
||||
[:on-focus-menu {:optional true} schema:callback]
|
||||
[:on-blur-menu {:optional true} schema:callback]
|
||||
[:on-key-down-input {:optional true} schema:callback]])
|
||||
|
||||
(mf/defc user-milestone*
|
||||
{::mf/schema schema:milestone}
|
||||
[{:keys [class active editing locked user label date
|
||||
onOpenMenu onFocusInput onBlurInput onKeyDownInput] :rest props}]
|
||||
(let [class' (stl/css-case :milestone true
|
||||
:is-selected active)
|
||||
props (mf/spread-props props {:class [class class']
|
||||
:data-testid "milestone"})
|
||||
date (if (ct/inst? date)
|
||||
date
|
||||
(ct/inst date))]
|
||||
(mf/defc milestone*
|
||||
{::mf/schema (sm/schema schema:milestone)}
|
||||
[{:keys [class active editing locked label created-at profile
|
||||
on-open-menu on-focus-input on-blur-input on-key-down-input] :rest props}]
|
||||
(let [class'
|
||||
(stl/css-case :milestone true
|
||||
:is-selected active)
|
||||
props
|
||||
(mf/spread-props props
|
||||
{:class [class class']
|
||||
:data-testid "milestone"})
|
||||
created-at
|
||||
(if (ct/inst? created-at)
|
||||
created-at
|
||||
(ct/inst created-at))]
|
||||
|
||||
[:> :div props
|
||||
[:> avatar* {:name (obj/get user "name")
|
||||
:url (obj/get user "avatar")
|
||||
:color (obj/get user "color")
|
||||
[:> avatar* {:profile profile
|
||||
:variant "S"
|
||||
:class (stl/css :avatar)}]
|
||||
|
||||
(if editing
|
||||
(if ^boolean editing
|
||||
[:> input*
|
||||
{:class (stl/css :name-input)
|
||||
:variant "seamless"
|
||||
:default-value label
|
||||
:auto-focus true
|
||||
:on-focus onFocusInput
|
||||
:on-blur onBlurInput
|
||||
:on-key-down onKeyDownInput}]
|
||||
:on-focus on-focus-input
|
||||
:on-blur on-blur-input
|
||||
:on-key-down on-key-down-input}]
|
||||
[:div {:class (stl/css :name-wrapper)}
|
||||
[:> text* {:as "span" :typography t/body-small :class (stl/css :name)} label]
|
||||
(when locked
|
||||
[:> i/icon* {:icon-id i/lock :class (stl/css :lock-icon)}])])
|
||||
|
||||
[:*
|
||||
[:time {:date-time (ct/format-inst date :iso)
|
||||
[:time {:date-time (ct/format-inst created-at :iso)
|
||||
:class (stl/css :date)}
|
||||
(ct/timeago date)]
|
||||
(ct/timeago created-at)]
|
||||
|
||||
[:div {:class (stl/css :milestone-buttons)}
|
||||
[:> icon-button* {:class (stl/css :menu-button)
|
||||
:variant "ghost"
|
||||
:icon "menu"
|
||||
:aria-label (tr "workspace.versions.version-menu")
|
||||
:on-click onOpenMenu}]]]]))
|
||||
:on-click on-open-menu}]]]]))
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import * as React from "react";
|
||||
import Components from "@target/components";
|
||||
|
||||
const { Milestone } = Components;
|
||||
|
||||
export default {
|
||||
title: "Product/Milestones/Milestone",
|
||||
component: Milestone,
|
||||
|
||||
argTypes: {
|
||||
profileName: {
|
||||
control: { type: "text" },
|
||||
},
|
||||
profileAvatar: {
|
||||
control: { type: "text" },
|
||||
},
|
||||
label: {
|
||||
control: { type: "text" },
|
||||
},
|
||||
createdAt: {
|
||||
control: { type: "date" },
|
||||
},
|
||||
active: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
editing: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
locked: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
},
|
||||
args: {
|
||||
label: "Milestone 1",
|
||||
profileName: "Ada Lovelace",
|
||||
profileAvatar: "/images/avatar-blue.jpg",
|
||||
createdAt: 1735686000000,
|
||||
active: false,
|
||||
editing: false,
|
||||
},
|
||||
render: ({ profileName, profileAvatar, profileColor, createdAt, ...args }) => {
|
||||
const profile = {
|
||||
id: "00000000-0000-0000-0000-000000000000",
|
||||
fullname: profileName
|
||||
};
|
||||
|
||||
if (profileAvatar) {
|
||||
profile.photoUrl = profileAvatar;
|
||||
}
|
||||
|
||||
if (createdAt instanceof Number) {
|
||||
createdAt = new Date(createdAt);
|
||||
}
|
||||
|
||||
return <Milestone profile={profile} createdAt={createdAt} {...args} />;
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = {};
|
||||
|
|
@ -4,58 +4,78 @@
|
|||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.ds.product.autosaved-milestone
|
||||
(ns app.main.ui.ds.product.milestone-group
|
||||
(:require-macros
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as cm]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.foundations.typography :as t]
|
||||
[app.main.ui.ds.foundations.typography.text :refer [text*]]
|
||||
[app.main.ui.ds.utilities.date :refer [date* valid-date?]]
|
||||
[app.main.ui.ds.utilities.date :refer [date*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def ^:private schema:milestone
|
||||
(def ^:private schema:milestone-group
|
||||
[:map
|
||||
[:class {:optional true} :string]
|
||||
[:active {:optional true} :boolean]
|
||||
[:versionToggled {:optional true} :boolean]
|
||||
[:label :string]
|
||||
[:autosavedMessage :string]
|
||||
[:snapshots [:vector [:fn valid-date?]]]])
|
||||
[:snapshots [:vector ::cm/inst]]])
|
||||
|
||||
(mf/defc autosaved-milestone*
|
||||
{::mf/schema schema:milestone}
|
||||
[{:keys [class active versionToggled label autosavedMessage snapshots
|
||||
onClickSnapshotMenu onToggleExpandSnapshots] :rest props}]
|
||||
(let [class (d/append-class class (stl/css-case :milestone true :is-selected active))
|
||||
props (mf/spread-props props {:class class :data-testid "milestone"})
|
||||
(mf/defc milestone-group*
|
||||
{::mf/schema (sm/schema schema:milestone-group)}
|
||||
[{:keys [class active label snapshots on-menu-click] :rest props}]
|
||||
(let [class'
|
||||
(stl/css-case :milestone true
|
||||
:is-selected active)
|
||||
|
||||
handle-click-menu
|
||||
props
|
||||
(mf/spread-props props
|
||||
{:class [class class']
|
||||
:data-testid "milestone"})
|
||||
|
||||
open*
|
||||
(mf/use-state false)
|
||||
|
||||
open?
|
||||
(deref open*)
|
||||
|
||||
on-toggle-visibility
|
||||
(mf/use-fn (fn [] (swap! open* not)))
|
||||
|
||||
on-menu-click
|
||||
(mf/use-fn
|
||||
(mf/deps onClickSnapshotMenu)
|
||||
(mf/deps on-menu-click)
|
||||
(fn [event]
|
||||
(let [index (-> (dom/get-current-target event)
|
||||
(dom/get-data "index")
|
||||
(d/parse-integer))]
|
||||
(when onClickSnapshotMenu
|
||||
(onClickSnapshotMenu event index)))))]
|
||||
[:> "div" props
|
||||
(when (fn? on-menu-click)
|
||||
(on-menu-click index event)))))]
|
||||
|
||||
[:> :div props
|
||||
[:> text* {:as "span" :typography t/body-small :class (stl/css :name)} label]
|
||||
|
||||
[:div {:class (stl/css :snapshots)}
|
||||
[:button {:class (stl/css :toggle-snapshots)
|
||||
:aria-label (tr "workspace.versions.expand-snapshot")
|
||||
:on-click onToggleExpandSnapshots}
|
||||
:on-click on-toggle-visibility}
|
||||
[:> i/icon* {:icon-id i/clock :class (stl/css :icon-clock)}]
|
||||
[:> text* {:as "span" :typography t/body-medium :class (stl/css :toggle-message)} autosavedMessage]
|
||||
[:> i/icon* {:icon-id i/arrow :class (stl/css-case :icon-arrow true :icon-arrow-toggled versionToggled)}]]
|
||||
[:> text* {:as "span"
|
||||
:typography t/body-medium
|
||||
:class (stl/css :toggle-message)}
|
||||
(tr "workspace.versions.autosaved.entry" (count snapshots))]
|
||||
[:> i/icon* {:icon-id i/arrow
|
||||
:class (stl/css-case :icon-arrow true
|
||||
:icon-arrow-toggled open?)}]]
|
||||
|
||||
(when versionToggled
|
||||
(when ^boolean open?
|
||||
(for [[idx d] (d/enumerate snapshots)]
|
||||
[:div {:key (dm/str "entry-" idx)
|
||||
:class (stl/css :version-entry)}
|
||||
|
|
@ -65,5 +85,5 @@
|
|||
:icon "menu"
|
||||
:aria-label (tr "workspace.versions.version-menu")
|
||||
:data-index idx
|
||||
:on-click handle-click-menu}]]))]]))
|
||||
:on-click on-menu-click}]]))]]))
|
||||
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import * as React from "react";
|
||||
import Components from "@target/components";
|
||||
|
||||
const { AutosavedMilestone } = Components;
|
||||
const { MilestoneGroup } = Components;
|
||||
|
||||
export default {
|
||||
title: "Product/Milestones/Autosaved",
|
||||
component: AutosavedMilestone,
|
||||
title: "Product/Milestones/MilestoneGroup",
|
||||
component: MilestoneGroup,
|
||||
|
||||
argTypes: {
|
||||
label: {
|
||||
|
|
@ -27,17 +27,10 @@ export default {
|
|||
args: {
|
||||
label: "Milestone 1",
|
||||
active: false,
|
||||
versionToggled: false,
|
||||
snapshots: [1737452413841, 1737452422063, 1737452431603],
|
||||
autosavedMessage: "3 autosave versions",
|
||||
snapshots: [1737452413841, 1737452422063, 1737452431603]
|
||||
},
|
||||
render: ({ ...args }) => {
|
||||
const user = {
|
||||
name: args.userName,
|
||||
avatar: args.userAvatar,
|
||||
color: args.userColor,
|
||||
};
|
||||
return <AutosavedMilestone user={user} {...args} />;
|
||||
return <MilestoneGroup {...args} />;
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
import * as React from "react";
|
||||
import Components from "@target/components";
|
||||
|
||||
const { UserMilestone } = Components;
|
||||
|
||||
export default {
|
||||
title: "Product/Milestones/User",
|
||||
component: UserMilestone,
|
||||
|
||||
argTypes: {
|
||||
userName: {
|
||||
control: { type: "text" },
|
||||
},
|
||||
userAvatar: {
|
||||
control: { type: "text" },
|
||||
},
|
||||
userColor: {
|
||||
control: { type: "color" },
|
||||
},
|
||||
label: {
|
||||
control: { type: "text" },
|
||||
},
|
||||
date: {
|
||||
control: { type: "date" },
|
||||
},
|
||||
active: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
editing: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
autosaved: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
versionToggled: {
|
||||
control: { type: "boolean" },
|
||||
},
|
||||
snapshots: {
|
||||
control: { type: "object" },
|
||||
},
|
||||
},
|
||||
args: {
|
||||
label: "Milestone 1",
|
||||
userName: "Ada Lovelace",
|
||||
userAvatar: "/images/avatar-blue.jpg",
|
||||
userColor: "#79d4ff",
|
||||
date: 1735686000000,
|
||||
active: false,
|
||||
editing: false,
|
||||
},
|
||||
render: ({ ...args }) => {
|
||||
const user = {
|
||||
name: args.userName,
|
||||
avatar: args.userAvatar,
|
||||
color: args.userColor,
|
||||
};
|
||||
return <UserMilestone user={user} {...args} />;
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = {};
|
||||
|
|
@ -20,9 +20,9 @@
|
|||
[app.main.ui.dashboard.subscription :refer [get-subscription-type]]
|
||||
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
|
||||
[app.main.ui.ds.foundations.assets.icon :as i]
|
||||
[app.main.ui.ds.product.autosaved-milestone :refer [autosaved-milestone*]]
|
||||
[app.main.ui.ds.product.cta :refer [cta*]]
|
||||
[app.main.ui.ds.product.user-milestone :refer [user-milestone*]]
|
||||
[app.main.ui.ds.product.milestone :refer [milestone*]]
|
||||
[app.main.ui.ds.product.milestone-group :refer [milestone-group*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
|
|
@ -31,10 +31,10 @@
|
|||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def versions
|
||||
(def ^:private versions
|
||||
(l/derived :workspace-versions st/state))
|
||||
|
||||
(defn get-versions-stored-days
|
||||
(defn- get-versions-stored-days
|
||||
[team]
|
||||
(let [subscription-type (get-subscription-type (:subscription team))]
|
||||
(cond
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
(= subscription-type "enterprise") 90
|
||||
:else 7)))
|
||||
|
||||
(defn get-versions-warning-subtext
|
||||
(defn- get-versions-warning-subtext
|
||||
[team]
|
||||
(let [subscription-type (get-subscription-type (:subscription team))
|
||||
is-owner? (-> team :permissions :is-owner)
|
||||
|
|
@ -58,333 +58,330 @@
|
|||
(tr "subscription.workspace.versions.warning.subtext-member" email-owner email-owner))
|
||||
(tr "workspace.versions.warning.subtext" support-email))))
|
||||
|
||||
(defn group-snapshots
|
||||
(defn- group-snapshots
|
||||
[data]
|
||||
(->> (concat
|
||||
(->> data
|
||||
(filterv #(= "user" (:created-by %)))
|
||||
(filter #(= "user" (:created-by %)))
|
||||
(map #(assoc % :type :version)))
|
||||
(->> data
|
||||
(filterv #(= "system" (:created-by %)))
|
||||
(filter #(= "system" (:created-by %)))
|
||||
(group-by #(ct/format-inst (:created-at %) :iso-date))
|
||||
(map (fn [[day entries]]
|
||||
{:type :snapshot
|
||||
:created-at (ct/inst day)
|
||||
:snapshots entries}))))
|
||||
(sort-by :created-at)
|
||||
(map-indexed (fn [index item]
|
||||
(assoc item :index index)))
|
||||
(reverse)))
|
||||
|
||||
(mf/defc version-entry
|
||||
[{:keys [entry profile current-profile on-restore-version on-delete-version on-rename-version on-lock-version on-unlock-version editing?]}]
|
||||
(let [show-menu? (mf/use-state false)
|
||||
|
||||
handle-open-menu
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! show-menu? true)))
|
||||
|
||||
handle-close-menu
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! show-menu? false)))
|
||||
|
||||
handle-rename-version
|
||||
(mf/use-fn
|
||||
(mf/deps entry)
|
||||
(fn []
|
||||
(st/emit! (dwv/update-version-state {:editing (:id entry)}))))
|
||||
|
||||
handle-restore-version
|
||||
(mf/use-fn
|
||||
(mf/deps entry on-restore-version)
|
||||
(fn []
|
||||
(when on-restore-version
|
||||
(on-restore-version (:id entry)))))
|
||||
|
||||
handle-delete-version
|
||||
(mf/use-callback
|
||||
(mf/deps entry on-delete-version)
|
||||
(fn []
|
||||
(when on-delete-version
|
||||
(on-delete-version (:id entry)))))
|
||||
|
||||
handle-lock-version
|
||||
(mf/use-callback
|
||||
(mf/deps entry on-lock-version)
|
||||
(fn []
|
||||
(when on-lock-version
|
||||
(on-lock-version (:id entry)))))
|
||||
|
||||
handle-unlock-version
|
||||
(mf/use-callback
|
||||
(mf/deps entry on-unlock-version)
|
||||
(fn []
|
||||
(when on-unlock-version
|
||||
(on-unlock-version (:id entry)))))
|
||||
|
||||
handle-name-input-focus
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/select-text! (dom/get-target event))))
|
||||
|
||||
handle-name-input-blur
|
||||
(mf/use-fn
|
||||
(mf/deps entry on-rename-version)
|
||||
(fn [event]
|
||||
(let [label (str/trim (dom/get-target-val event))]
|
||||
(when (and (not (str/empty? label))
|
||||
(some? on-rename-version))
|
||||
(on-rename-version (:id entry) label))
|
||||
(st/emit! (dwv/update-version-state {:editing nil})))))
|
||||
|
||||
handle-name-input-key-down
|
||||
(mf/use-fn
|
||||
(mf/deps handle-name-input-blur)
|
||||
(fn [event]
|
||||
(cond
|
||||
(kbd/enter? event)
|
||||
(handle-name-input-blur event)
|
||||
|
||||
(kbd/esc? event)
|
||||
(st/emit! (dwv/update-version-state {:editing nil})))))]
|
||||
|
||||
[:li {:class (stl/css :version-entry-wrap)}
|
||||
[:> user-milestone* {:label (:label entry)
|
||||
:user #js {:name (:fullname profile)
|
||||
:avatar (cfg/resolve-profile-photo-url profile)
|
||||
:color (:color profile)}
|
||||
:editing editing?
|
||||
:date (:created-at entry)
|
||||
:locked (boolean (:locked-by entry))
|
||||
:onOpenMenu handle-open-menu
|
||||
:onFocusInput handle-name-input-focus
|
||||
:onBlurInput handle-name-input-blur
|
||||
:onKeyDownInput handle-name-input-key-down}]
|
||||
|
||||
[:& dropdown {:show @show-menu? :on-close handle-close-menu}
|
||||
(let [current-user-id (:id current-profile)
|
||||
version-creator-id (:profile-id entry)
|
||||
locked-by-id (:locked-by entry)
|
||||
is-version-creator? (= current-user-id version-creator-id)
|
||||
is-locked? (some? locked-by-id)
|
||||
is-locked-by-me? (= current-user-id locked-by-id)
|
||||
can-rename? is-version-creator?
|
||||
can-lock? (and is-version-creator? (not is-locked?))
|
||||
can-unlock? (and is-version-creator? is-locked-by-me?)
|
||||
can-delete? (or (not is-locked?) (and is-locked? is-locked-by-me?))]
|
||||
[:ul {:class (stl/css :version-options-dropdown)}
|
||||
(when can-rename?
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:on-click handle-rename-version} (tr "labels.rename")])
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:on-click handle-restore-version} (tr "labels.restore")]
|
||||
(cond
|
||||
can-unlock?
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:on-click handle-unlock-version} (tr "labels.unlock")]
|
||||
can-lock?
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:on-click handle-lock-version} (tr "labels.lock")])
|
||||
(when can-delete?
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:on-click handle-delete-version} (tr "labels.delete")])])]]))
|
||||
|
||||
(mf/defc snapshot-entry
|
||||
[{:keys [index is-expanded entry on-toggle-expand on-pin-snapshot on-restore-snapshot]}]
|
||||
|
||||
(let [open-menu (mf/use-state nil)
|
||||
entry-ref (mf/use-ref nil)
|
||||
|
||||
handle-toggle-expand
|
||||
(mf/use-fn
|
||||
(mf/deps index on-toggle-expand)
|
||||
(fn []
|
||||
(when on-toggle-expand
|
||||
(on-toggle-expand index))))
|
||||
|
||||
handle-pin-snapshot
|
||||
(mf/use-fn
|
||||
(mf/deps on-pin-snapshot)
|
||||
(fn [event]
|
||||
(let [node (dom/get-current-target event)
|
||||
id (-> (dom/get-data node "id") uuid/parse)]
|
||||
(when on-pin-snapshot (on-pin-snapshot id)))))
|
||||
|
||||
handle-restore-snapshot
|
||||
(mf/use-fn
|
||||
(mf/deps on-restore-snapshot)
|
||||
(fn [event]
|
||||
(let [node (dom/get-current-target event)
|
||||
id (-> (dom/get-data node "id") uuid/parse)]
|
||||
(when on-restore-snapshot (on-restore-snapshot id)))))
|
||||
|
||||
|
||||
handle-open-snapshot-menu
|
||||
(mf/use-fn
|
||||
(mf/deps entry)
|
||||
(fn [event index]
|
||||
(let [snapshot (nth (:snapshots entry) index)
|
||||
current-bb (-> entry-ref mf/ref-val dom/get-bounding-rect :top)
|
||||
target-bb (-> event dom/get-target dom/get-bounding-rect :top)
|
||||
offset (+ (- target-bb current-bb) 32)]
|
||||
(swap! open-menu assoc
|
||||
:snapshot (:id snapshot)
|
||||
:offset offset))))]
|
||||
|
||||
[:li {:ref entry-ref :class (stl/css :version-entry-wrap)}
|
||||
[:> autosaved-milestone*
|
||||
{:label (tr "workspace.versions.autosaved.version"
|
||||
(ct/format-inst (:created-at entry) :localized-date))
|
||||
:autosavedMessage (tr "workspace.versions.autosaved.entry" (count (:snapshots entry)))
|
||||
:snapshots (mapv :created-at (:snapshots entry))
|
||||
:versionToggled is-expanded
|
||||
:onClickSnapshotMenu handle-open-snapshot-menu
|
||||
:onToggleExpandSnapshots handle-toggle-expand}]
|
||||
|
||||
[:& dropdown {:show (some? @open-menu)
|
||||
:on-close #(reset! open-menu nil)}
|
||||
[:ul {:class (stl/css :version-options-dropdown)
|
||||
:style {"--offset" (dm/str (:offset @open-menu) "px")}}
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:data-id (dm/str (:snapshot @open-menu))
|
||||
:on-click handle-restore-snapshot}
|
||||
(tr "workspace.versions.button.restore")]
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:data-id (dm/str (:snapshot @open-menu))
|
||||
:on-click handle-pin-snapshot}
|
||||
(tr "workspace.versions.button.pin")]]]]))
|
||||
|
||||
(mf/defc versions-toolbox*
|
||||
[]
|
||||
(let [profiles (mf/deref refs/profiles)
|
||||
profile (mf/deref refs/profile)
|
||||
team (mf/deref refs/team)
|
||||
|
||||
expanded (mf/use-state #{})
|
||||
|
||||
{:keys [status data editing]}
|
||||
(mf/deref versions)
|
||||
|
||||
;; Store users that have a version
|
||||
data-users
|
||||
(mf/use-memo
|
||||
(mf/deps data)
|
||||
(fn []
|
||||
(into #{} (keep (fn [{:keys [created-by profile-id]}]
|
||||
(when (= "user" created-by) profile-id))) data)))
|
||||
data
|
||||
(mf/use-memo
|
||||
(mf/deps @versions)
|
||||
(fn []
|
||||
(->> data
|
||||
(filter #(or (not (:filter @versions))
|
||||
(and
|
||||
(= "user" (:created-by %))
|
||||
(= (:filter @versions) (:profile-id %)))))
|
||||
(group-snapshots))))
|
||||
|
||||
handle-create-version
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(st/emit! (dwv/create-version))))
|
||||
|
||||
handle-toggle-expand
|
||||
(mf/use-fn
|
||||
(fn [id]
|
||||
(swap! expanded
|
||||
(fn [expanded]
|
||||
(let [has-element? (contains? expanded id)]
|
||||
(cond-> expanded
|
||||
has-element? (disj id)
|
||||
(not has-element?) (conj id)))))))
|
||||
|
||||
handle-rename-version
|
||||
(mf/use-fn
|
||||
(fn [id label]
|
||||
(st/emit! (dwv/rename-version id label))))
|
||||
|
||||
|
||||
handle-restore-version
|
||||
(mf/use-fn
|
||||
(fn [origin id]
|
||||
(st/emit!
|
||||
(ntf/dialog
|
||||
(defn- open-restore-version-dialog
|
||||
[origin id]
|
||||
(st/emit! (ntf/dialog
|
||||
:content (tr "workspace.versions.restore-warning")
|
||||
:controls :inline-actions
|
||||
:cancel {:label (tr "workspace.updates.dismiss")
|
||||
:callback #(st/emit! (ntf/hide))}
|
||||
:accept {:label (tr "labels.restore")
|
||||
:callback #(st/emit! (dwv/restore-version id origin))}
|
||||
:tag :restore-dialog))))
|
||||
:tag :restore-dialog)))
|
||||
|
||||
handle-restore-version-pinned
|
||||
(mf/defc version-entry*
|
||||
{::mf/private true}
|
||||
[{:keys [entry current-profile on-restore on-delete on-rename on-lock on-unlock on-edit on-cancel-edit is-editing]}]
|
||||
(let [show-menu? (mf/use-state false)
|
||||
profiles (mf/deref refs/profiles)
|
||||
|
||||
created-by (get profiles (:profile-id entry))
|
||||
|
||||
on-open-menu
|
||||
(mf/use-fn #(reset! show-menu? true))
|
||||
|
||||
on-close-menu
|
||||
(mf/use-fn #(reset! show-menu? false))
|
||||
|
||||
on-edit
|
||||
(mf/use-fn
|
||||
(mf/deps handle-restore-version)
|
||||
(fn [id]
|
||||
(handle-restore-version :version id)))
|
||||
(mf/deps on-edit entry)
|
||||
(fn [event]
|
||||
(on-edit (:id entry) event)))
|
||||
|
||||
handle-restore-version-snapshot
|
||||
on-restore
|
||||
(mf/use-fn
|
||||
(mf/deps handle-restore-version)
|
||||
(fn [id]
|
||||
(handle-restore-version :snapshot id)))
|
||||
(mf/deps entry on-restore)
|
||||
(fn []
|
||||
(when (fn? on-restore)
|
||||
(on-restore (:id entry)))))
|
||||
|
||||
handle-delete-version
|
||||
on-delete
|
||||
(mf/use-callback
|
||||
(mf/deps entry on-delete)
|
||||
(fn [event]
|
||||
(when (fn? on-delete)
|
||||
(on-delete (:id entry) event))))
|
||||
|
||||
on-lock
|
||||
(mf/use-callback
|
||||
(mf/deps entry on-lock)
|
||||
(fn []
|
||||
(when on-lock
|
||||
(on-lock (:id entry)))))
|
||||
|
||||
on-unlock
|
||||
(mf/use-callback
|
||||
(mf/deps entry on-unlock)
|
||||
(fn []
|
||||
(when on-unlock
|
||||
(on-unlock (:id entry)))))
|
||||
|
||||
on-name-input-focus
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/select-text! (dom/get-target event))))
|
||||
|
||||
on-name-input-blur
|
||||
(mf/use-fn
|
||||
(mf/deps entry on-rename on-cancel-edit)
|
||||
(fn [event]
|
||||
(let [label (str/trim (dom/get-target-val event))]
|
||||
(if (and (not (str/empty? label))
|
||||
(fn? on-rename))
|
||||
(on-rename (:id entry) label event)
|
||||
(on-cancel-edit (:id entry) event)))))
|
||||
|
||||
on-name-input-key-down
|
||||
(mf/use-fn
|
||||
(mf/deps entry on-cancel-edit on-name-input-blur)
|
||||
(fn [event]
|
||||
(cond
|
||||
(kbd/enter? event)
|
||||
(on-name-input-blur event)
|
||||
|
||||
(kbd/esc? event)
|
||||
(when (fn? on-cancel-edit)
|
||||
(on-cancel-edit (:id entry) event)))))]
|
||||
|
||||
[:li {:class (stl/css :version-entry-wrap)}
|
||||
[:> milestone* {:label (:label entry)
|
||||
:profile created-by
|
||||
:editing is-editing
|
||||
:created-at (:created-at entry)
|
||||
:locked (some? (:locked-by entry))
|
||||
:on-open-menu on-open-menu
|
||||
:on-focus-input on-name-input-focus
|
||||
:on-blur-input on-name-input-blur
|
||||
:on-key-down-input on-name-input-key-down}]
|
||||
|
||||
[:& dropdown {:show @show-menu?
|
||||
:on-close on-close-menu}
|
||||
(let [current-user-id (:id current-profile)
|
||||
locked-by-id (:locked-by entry)
|
||||
im-the-owner? (= current-user-id (:id created-by))
|
||||
is-locked-by-me? (= current-user-id locked-by-id)
|
||||
is-locked? (some? locked-by-id)
|
||||
can-delete? (or (not is-locked?)
|
||||
(and is-locked?
|
||||
is-locked-by-me?))]
|
||||
[:ul {:class (stl/css :version-options-dropdown)}
|
||||
(when im-the-owner?
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:on-click on-edit}
|
||||
(tr "labels.rename")])
|
||||
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:on-click on-restore}
|
||||
(tr "labels.restore")]
|
||||
|
||||
(cond
|
||||
is-locked-by-me?
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:on-click on-unlock}
|
||||
(tr "labels.unlock")]
|
||||
|
||||
(and im-the-owner? (not is-locked?))
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:on-click on-lock}
|
||||
(tr "labels.lock")])
|
||||
|
||||
(when can-delete?
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:on-click on-delete}
|
||||
(tr "labels.delete")])])]]))
|
||||
|
||||
(mf/defc snapshot-entry*
|
||||
[{:keys [entry on-pin-snapshot on-restore-snapshot]}]
|
||||
|
||||
(let [open-menu* (mf/use-state nil)
|
||||
entry-ref (mf/use-ref nil)
|
||||
|
||||
on-pin-snapshot
|
||||
(mf/use-fn
|
||||
(mf/deps on-pin-snapshot)
|
||||
(fn [event]
|
||||
(let [node (dom/get-current-target event)
|
||||
id (-> node
|
||||
(dom/get-data "id")
|
||||
(uuid/parse))]
|
||||
(when (fn? on-pin-snapshot)
|
||||
(on-pin-snapshot id event)))))
|
||||
|
||||
on-restore-snapshot
|
||||
(mf/use-fn
|
||||
(mf/deps on-restore-snapshot)
|
||||
(fn [event]
|
||||
(let [node (dom/get-current-target event)
|
||||
id (-> node
|
||||
(dom/get-data "id")
|
||||
(uuid/parse))]
|
||||
(when (fn? on-restore-snapshot)
|
||||
(on-restore-snapshot id event)))))
|
||||
|
||||
on-open-snapshot-menu
|
||||
(mf/use-fn
|
||||
(mf/deps entry)
|
||||
(fn [index event]
|
||||
(let [snapshot (nth (:snapshots entry) index)
|
||||
current-bb (-> entry-ref mf/ref-val dom/get-bounding-rect :top)
|
||||
target-bb (-> event dom/get-target dom/get-bounding-rect :top)
|
||||
offset (+ (- target-bb current-bb) 32)]
|
||||
(swap! open-menu* assoc
|
||||
:snapshot (:id snapshot)
|
||||
:offset offset))))]
|
||||
|
||||
[:li {:ref entry-ref :class (stl/css :version-entry-wrap)}
|
||||
[:> milestone-group*
|
||||
{:label (tr "workspace.versions.autosaved.version"
|
||||
(ct/format-inst (:created-at entry) :localized-date))
|
||||
:snapshots (mapv :created-at (:snapshots entry))
|
||||
:on-menu-click on-open-snapshot-menu}]
|
||||
|
||||
[:& dropdown {:show (some? @open-menu*)
|
||||
:on-close #(reset! open-menu* nil)}
|
||||
[:ul {:class (stl/css :version-options-dropdown)
|
||||
:style {"--offset" (dm/str (:offset @open-menu*) "px")}}
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:data-id (dm/str (:snapshot @open-menu*))
|
||||
:on-click on-restore-snapshot}
|
||||
(tr "workspace.versions.button.restore")]
|
||||
[:li {:class (stl/css :menu-option)
|
||||
:role "button"
|
||||
:data-id (dm/str (:snapshot @open-menu*))
|
||||
:on-click on-pin-snapshot}
|
||||
(tr "workspace.versions.button.pin")]]]]))
|
||||
|
||||
(mf/defc versions-toolbox*
|
||||
[]
|
||||
(let [profiles (mf/deref refs/profiles)
|
||||
profile (mf/deref refs/profile)
|
||||
team (mf/deref refs/team)
|
||||
|
||||
{:keys [status data editing] :as state}
|
||||
(mf/deref versions)
|
||||
|
||||
users
|
||||
(mf/with-memo [data]
|
||||
(into #{}
|
||||
(keep (fn [{:keys [created-by profile-id]}]
|
||||
(when (= "user" created-by)
|
||||
profile-id)))
|
||||
data))
|
||||
|
||||
entries
|
||||
(mf/with-memo [state]
|
||||
(->> (:data state)
|
||||
(filter #(or (not (:filter state))
|
||||
(and (= "user" (:created-by %))
|
||||
(= (:filter state) (:profile-id %)))))
|
||||
(group-snapshots)))
|
||||
|
||||
on-create-version
|
||||
(mf/use-fn
|
||||
(fn [] (st/emit! (dwv/create-version))))
|
||||
|
||||
on-edit-version
|
||||
(mf/use-fn
|
||||
(fn [id _event]
|
||||
(st/emit! (dwv/update-versions-state {:editing id}))))
|
||||
|
||||
on-cancel-version-edition
|
||||
(mf/use-fn
|
||||
(fn [_id _event]
|
||||
(st/emit! (dwv/update-versions-state {:editing nil}))))
|
||||
|
||||
on-rename-version
|
||||
(mf/use-fn
|
||||
(fn [id label]
|
||||
(st/emit! (dwv/rename-version id label))))
|
||||
|
||||
on-restore-version
|
||||
(mf/use-fn
|
||||
(fn [id _event]
|
||||
(open-restore-version-dialog :version id)))
|
||||
|
||||
on-restore-snapshot
|
||||
(mf/use-fn
|
||||
(fn [id _event]
|
||||
(open-restore-version-dialog :snapshot id)))
|
||||
|
||||
on-delete-version
|
||||
(mf/use-fn
|
||||
(fn [id]
|
||||
(st/emit! (dwv/delete-version id))))
|
||||
|
||||
handle-pin-version
|
||||
on-pin-version
|
||||
(mf/use-fn
|
||||
(fn [id]
|
||||
(st/emit! (dwv/pin-version id))))
|
||||
(fn [id] (st/emit! (dwv/pin-version id))))
|
||||
|
||||
handle-lock-version
|
||||
on-lock-version
|
||||
(mf/use-fn
|
||||
(fn [id]
|
||||
(st/emit! (dwv/lock-version id))))
|
||||
|
||||
handle-unlock-version
|
||||
on-unlock-version
|
||||
(mf/use-fn
|
||||
(fn [id]
|
||||
(st/emit! (dwv/unlock-version id))))
|
||||
|
||||
handle-change-filter
|
||||
on-change-filter
|
||||
(mf/use-fn
|
||||
(fn [filter]
|
||||
(cond
|
||||
(= :all filter)
|
||||
(st/emit! (dwv/update-version-state {:filter nil}))
|
||||
(st/emit! (dwv/update-versions-state {:filter nil}))
|
||||
|
||||
(= :own filter)
|
||||
(st/emit! (dwv/update-version-state {:filter (:id profile)}))
|
||||
(st/emit! (dwv/update-versions-state {:filter (:id profile)}))
|
||||
|
||||
:else
|
||||
(st/emit! (dwv/update-version-state {:filter filter})))))]
|
||||
(st/emit! (dwv/update-versions-state {:filter filter})))))
|
||||
|
||||
options
|
||||
(mf/with-memo [users profile]
|
||||
(let [current-profile-id (get profile :id)]
|
||||
(into [{:value :all :label (tr "workspace.versions.filter.all")}
|
||||
{:value :own :label (tr "workspace.versions.filter.mine")}]
|
||||
(keep (fn [id]
|
||||
(when (not= id current-profile-id)
|
||||
(when-let [fullname (-> profiles (get id) (get :fullname))]
|
||||
{:value id :label (tr "workspace.versions.filter.user" fullname)}))))
|
||||
users)))]
|
||||
|
||||
(mf/with-effect []
|
||||
(st/emit! (dwv/init-version-state)))
|
||||
(st/emit! (dwv/init-versions-state)))
|
||||
|
||||
[:div {:class (stl/css :version-toolbox)}
|
||||
[:& select
|
||||
{:default-value :all
|
||||
:aria-label (tr "workspace.versions.filter.label")
|
||||
:options (into [{:value :all :label (tr "workspace.versions.filter.all")}
|
||||
{:value :own :label (tr "workspace.versions.filter.mine")}]
|
||||
(->> data-users
|
||||
(keep
|
||||
(fn [id]
|
||||
(let [{:keys [fullname]} (get profiles id)]
|
||||
(when (not= id (:id profile))
|
||||
{:value id :label (tr "workspace.versions.filter.user" fullname)}))))))
|
||||
:on-change handle-change-filter}]
|
||||
:options options
|
||||
:on-change on-change-filter}]
|
||||
|
||||
(cond
|
||||
(= status :loading)
|
||||
|
|
@ -397,7 +394,7 @@
|
|||
(tr "workspace.versions.button.save")
|
||||
[:> icon-button* {:variant "ghost"
|
||||
:aria-label (tr "workspace.versions.button.save")
|
||||
:on-click handle-create-version
|
||||
:on-click on-create-version
|
||||
:icon "pin"}]]
|
||||
|
||||
(if (empty? data)
|
||||
|
|
@ -406,28 +403,26 @@
|
|||
[:div {:class (stl/css :versions-entry-empty-msg)} (tr "workspace.versions.empty")]]
|
||||
|
||||
[:ul {:class (stl/css :versions-entries)}
|
||||
(for [[idx-entry entry] (->> data (map-indexed vector))]
|
||||
(for [entry entries]
|
||||
(case (:type entry)
|
||||
:version
|
||||
[:& version-entry {:key idx-entry
|
||||
:entry entry
|
||||
:editing? (= (:id entry) editing)
|
||||
:profile (get profiles (:profile-id entry))
|
||||
:current-profile profile
|
||||
:on-rename-version handle-rename-version
|
||||
:on-restore-version handle-restore-version-pinned
|
||||
:on-delete-version handle-delete-version
|
||||
:on-lock-version handle-lock-version
|
||||
:on-unlock-version handle-unlock-version}]
|
||||
[:> version-entry* {:key (:index entry)
|
||||
:entry entry
|
||||
:is-editing (= (:id entry) editing)
|
||||
:current-profile profile
|
||||
:on-edit on-edit-version
|
||||
:on-cancel-edit on-cancel-version-edition
|
||||
:on-rename on-rename-version
|
||||
:on-restore on-restore-version
|
||||
:on-delete on-delete-version
|
||||
:on-lock on-lock-version
|
||||
:on-unlock on-unlock-version}]
|
||||
|
||||
:snapshot
|
||||
[:& snapshot-entry {:key idx-entry
|
||||
:index idx-entry
|
||||
:entry entry
|
||||
:is-expanded (contains? @expanded idx-entry)
|
||||
:on-toggle-expand handle-toggle-expand
|
||||
:on-restore-snapshot handle-restore-version-snapshot
|
||||
:on-pin-snapshot handle-pin-version}]
|
||||
[:> snapshot-entry* {:key (:index entry)
|
||||
:entry entry
|
||||
:on-restore-snapshot on-restore-snapshot
|
||||
:on-pin-snapshot on-pin-version}]
|
||||
|
||||
nil))])
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue