From f4323fd1ac82a7c6ba0a9f8e7e17351fdfc42f60 Mon Sep 17 00:00:00 2001 From: Eva Date: Fri, 13 Oct 2023 15:00:50 +0200 Subject: [PATCH] :lipstick: Update comment UI with new design --- .../styles/common/refactor/basic-rules.scss | 75 ++- .../styles/common/refactor/design-tokens.scss | 18 +- frontend/src/app/main/ui/comments.cljs | 571 ++++++++++++------ frontend/src/app/main/ui/comments.scss | 232 +++++++ .../app/main/ui/components/radio_buttons.scss | 8 +- .../app/main/ui/workspace/color_palette.scss | 2 +- .../src/app/main/ui/workspace/comments.cljs | 193 ++++-- .../src/app/main/ui/workspace/comments.scss | 141 +++++ .../main/ui/workspace/sidebar/history.scss | 4 +- .../sidebar/options/menus/align.scss | 4 +- .../workspace/sidebar/options/menus/bool.scss | 4 +- .../app/main/ui/workspace/text_palette.scss | 2 +- 12 files changed, 999 insertions(+), 255 deletions(-) create mode 100644 frontend/src/app/main/ui/comments.scss create mode 100644 frontend/src/app/main/ui/workspace/comments.scss diff --git a/frontend/resources/styles/common/refactor/basic-rules.scss b/frontend/resources/styles/common/refactor/basic-rules.scss index 8b912412c..45905cc3f 100644 --- a/frontend/resources/styles/common/refactor/basic-rules.scss +++ b/frontend/resources/styles/common/refactor/basic-rules.scss @@ -75,6 +75,12 @@ stroke: var(--button-primary-foreground-color-active); } } + &:global(.disabled) { + background-color: var(--button-background-color-disabled); + border: $s-1 solid var(--button-border-color-disabled); + color: var(--button-foreground-color-disabled); + cursor: unset; + } } .button-secondary { @@ -118,6 +124,12 @@ stroke: var(--button-secondary-foreground-color-active); } } + &:global(.disabled) { + background-color: var(--button-background-color-disabled); + border: $s-1 solid var(--button-border-color-disabled); + color: var(--button-foreground-color-disabled); + cursor: unset; + } } .button-tertiary { @@ -161,6 +173,12 @@ stroke: var(--button-tertiary-foreground-color-active); } } + &:global(.disabled) { + background-color: var(--button-background-color-disabled); + border: $s-1 solid var(--button-border-color-disabled); + color: var(--button-foreground-color-disabled); + cursor: unset; + } } .button-radio { @@ -375,6 +393,26 @@ } } +.checkbox-icon { + @include flexCenter; + width: $s-16; + height: $s-16; + min-width: $s-16; + min-height: $s-16; + border-radius: $br-6; + background-color: var(--input-background-color); + svg { + display: none; + } + &:global(.checked) { + background-color: var(--input-border-color-active); + svg { + @extend .button-icon-small; + stroke: var(--input-details-color); + } + } +} + .input-checkbox { display: flex; align-items: center; @@ -385,20 +423,7 @@ gap: $s-6; cursor: pointer; span { - @include flexCenter; - width: $s-16; - height: $s-16; - min-width: $s-16; - min-height: $s-16; - border-radius: $br-6; - background-color: var(--input-background-color); - &:global(.checked) { - background-color: var(--input-border-color-active); - svg { - @extend .button-icon-small; - stroke: var(--input-details-color); - } - } + @extend .checkbox-icon; } input { margin: 0; @@ -505,6 +530,28 @@ grid-template-columns: $s-92 1fr; } +.comment-bubbles { + @include titleTipography; + @include flexCenter; + height: $s-32; + width: $s-32; + border-radius: $br-circle; + background-color: var(--comment-bullet-background-color-rest); + border: $s-1 solid var(--comment-bullet-border-color-rest); + color: var(--comment-bullet-foreground-color-rest); +} + +.resolved-comment-bubble { + background-color: var(--comment-bullet-background-color-resolved); + border: $s-1 solid var(--comment-bullet-border-color-resolved); + color: var(--comment-bullet-foreground-color-resolved); +} +.unread-comment-bubble { + background-color: var(--comment-bullet-background-color-unread); + border: $s-1 solid var(--comment-bullet-border-color-unread); + color: var(--comment-bullet-foreground-color-unread); +} + // SELECTS AND DROPDOWNS .menu-dropdown { @include menuShadow; diff --git a/frontend/resources/styles/common/refactor/design-tokens.scss b/frontend/resources/styles/common/refactor/design-tokens.scss index 29d767015..4a811bfa5 100644 --- a/frontend/resources/styles/common/refactor/design-tokens.scss +++ b/frontend/resources/styles/common/refactor/design-tokens.scss @@ -20,7 +20,9 @@ --button-background-focus: var(--color-background-secondary); --button-foreground-focus: var(--color-foreground-primary); --button-border-focus: var(--color-accent-primary); - --button-foreground-color-disabled: var(--color-background-quaternary); + --button-foreground-color-disabled: var(--color-foreground-secondary); + --button-background-color-disabled: var(--color-background-primary); + --button-border-color-disabled: var(--color-background-quaternary); --button-primary-background-color-rest: var(--color-accent-primary); --button-primary-border-color-rest: var(--color-accent-primary); @@ -228,4 +230,18 @@ --colorpicker-details-color: var(--color-background-quaternary); --colorpicker-details-color-selected: var(--color-accent-primary); --colorpicker-handlers-color: var(--color-foreground-primary); + + // COMMENTS + --comment-title-color: var(--color-foreground-primary); + --comment-subtitle-color: var(--color-foreground-secondary); + --comment-bullet-background-color-rest: var(--color-background-quaternary); + --comment-bullet-foreground-color-rest: var(--color-foreground-primary); + --comment-bullet-border-color-rest: var(--color-background-secondary); + --comment-bullet-background-color-unread: var(--color-accent-primary); + --comment-bullet-foreground-color-unread: var(--color-background-primary); + --comment-bullet-border-color-unread: var(--color-background-secondary); + --comment-bullet-background-color-resolved: var(--color-background-primary); + --comment-bullet-foreground-color-resolved: var(--color-foreground-secondary); + --comment-bullet-border-color-resolved: var(--color-background-quaternary); + --comment-modal-background-color: var(--color-background-primary); } diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index db29a349f..36da25d9f 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -5,6 +5,7 @@ ;; Copyright (c) KALEIDOS INC (ns app.main.ui.comments + (:require-macros [app.main.style :as stl]) (:require [app.common.data :as d] [app.common.data.macros :as dm] @@ -17,6 +18,7 @@ [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.forms :as fm] + [app.main.ui.context :as ctx] [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] @@ -69,8 +71,6 @@ ;; In webkit browsers the mouseup event will be called after the on-focus causing and unselect (.addEventListener target "mouseup" dom/prevent-default #js {:once true})))))] - - (mf/use-layout-effect nil (fn [] @@ -90,9 +90,13 @@ (mf/defc reply-form [{:keys [thread] :as props}] - (let [show-buttons? (mf/use-state false) + (let [new-css-system (mf/use-ctx ctx/new-css-system) + show-buttons? (mf/use-state false) content (mf/use-state "") + disabled? (or (fm/all-spaces? @content) + (str/empty-or-nil? @content)) + on-focus (mf/use-fn #(reset! show-buttons? true)) @@ -116,29 +120,52 @@ (fn [] (st/emit! (dcm/add-comment thread @content)) (on-cancel)))] + (if new-css-system + [:div {:class (stl/css :reply-form)} + [:& resizing-textarea {:value @content + :placeholder "Reply" + :on-blur on-blur + :on-focus on-focus + :select-on-focus? false + :on-change on-change}] + (when (or @show-buttons? (seq @content)) + [:div {:class (stl/css :buttons-wrapper)} + [:input.btn-secondary + {:type "button" + :class (stl/css :cancel-btn) + :value "Cancel" + :on-click on-cancel}] + [:input + {:type "button" + :class (stl/css-case :post-btn true + :global/disabled disabled?) + :value "Post" + :on-click on-submit + :disabled disabled?}]])] - [:div.reply-form - [:& resizing-textarea {:value @content - :placeholder "Reply" - :on-blur on-blur - :on-focus on-focus - :on-change on-change}] - (when (or @show-buttons? (seq @content)) - [:div.buttons - [:input.btn-primary - {:type "button" - :value "Post" - :on-click on-submit - :disabled (or (fm/all-spaces? @content) - (str/empty-or-nil? @content))}] - [:input.btn-secondary - {:type "button" - :value "Cancel" - :on-click on-cancel}]])])) + + [:div.reply-form + [:& resizing-textarea {:value @content + :placeholder "Reply" + :on-blur on-blur + :on-focus on-focus + :on-change on-change}] + (when (or @show-buttons? (seq @content)) + [:div.buttons + [:input.btn-primary + {:type "button" + :value "Post" + :on-click on-submit + :disabled disabled?}] + [:input.btn-secondary + {:type "button" + :value "Cancel" + :on-click on-cancel}]])]))) (mf/defc draft-thread [{:keys [draft zoom on-cancel on-submit position-modifier]}] - (let [position (cond-> (:position draft) + (let [new-css-system (mf/use-ctx ctx/new-css-system) + position (cond-> (:position draft) (some? position-modifier) (gpt/transform position-modifier)) content (:content draft) @@ -146,6 +173,9 @@ pos-x (* (:x position) zoom) pos-y (* (:y position) zoom) + disabled? (or (fm/all-spaces? content) + (str/empty-or-nil? content)) + on-esc (mf/use-fn (mf/deps draft) @@ -165,38 +195,70 @@ (mf/use-fn (mf/deps draft) (partial on-submit draft))] + (if new-css-system + [:* + [:div + {:class (stl/css :floating-thread-bubble) + :style {:top (str pos-y "px") + :left (str pos-x "px")} + :on-click dom/stop-propagation} + "?"] + [:div {:class (stl/css :thread-content) + :style {:top (str (- pos-y 24) "px") + :left (str (+ pos-x 28) "px")} + :on-click dom/stop-propagation} + [:div {:class (stl/css :reply-form)} + [:& resizing-textarea {:placeholder (tr "labels.write-new-comment") + :value (or content "") + :autofocus true + :select-on-focus? false + :on-esc on-esc + :on-change on-change}] + [:div {:class (stl/css :buttons-wrapper)} - [:* - [:div.thread-bubble - {:style {:top (str pos-y "px") - :left (str pos-x "px")} - :on-click dom/stop-propagation} - [:span "?"]] - [:div.thread-content - {:style {:top (str (- pos-y 14) "px") - :left (str (+ pos-x 14) "px")} - :on-click dom/stop-propagation} - [:div.reply-form - [:& resizing-textarea {:placeholder (tr "labels.write-new-comment") - :value (or content "") - :autofocus true - :on-esc on-esc - :on-change on-change}] - [:div.buttons - [:input.btn-primary - {:on-click on-submit - :type "button" - :value "Post" - :disabled (or (fm/all-spaces? content) - (str/empty-or-nil? content))}] - [:input.btn-secondary - {:on-click on-esc - :type "button" - :value "Cancel"}]]]]])) + [:input {:on-click on-esc + :class (stl/css :cancel-btn) + :type "button" + :value "Cancel"}] + + [:input {:on-click on-submit + :type "button" + :value "Post" + :class (stl/css-case :post-btn true + :global/disabled disabled?) + :disabled disabled?}]]]]] + + [:* + [:div.thread-bubble + {:style {:top (str pos-y "px") + :left (str pos-x "px")} + :on-click dom/stop-propagation} + [:span "?"]] + [:div.thread-content + {:style {:top (str (- pos-y 14) "px") + :left (str (+ pos-x 14) "px")} + :on-click dom/stop-propagation} + [:div.reply-form + [:& resizing-textarea {:placeholder (tr "labels.write-new-comment") + :value (or content "") + :autofocus true + :on-esc on-esc + :on-change on-change}] + [:div.buttons + [:input.btn-primary + {:on-click on-submit + :type "button" + :value "Post" + :disabled disabled?}] + [:input.btn-secondary + {:on-click on-esc + :type "button" + :value "Cancel"}]]]]]))) (mf/defc edit-form [{:keys [content on-submit on-cancel] :as props}] - (let [content (mf/use-state content) + (let [new-css-system (mf/use-ctx ctx/new-css-system) + content (mf/use-state content) on-change (mf/use-fn @@ -205,34 +267,56 @@ on-submit* (mf/use-fn (mf/deps @content) - (fn [] (on-submit @content)))] + (fn [] (on-submit @content))) + + disabled? (or (fm/all-spaces? @content) + (str/empty-or-nil? @content))] + + (if new-css-system + [:div {:class (stl/css :edit-form)} + [:& resizing-textarea {:value @content + :autofocus true + :select-on-focus true + :select-on-focus? false + :on-change on-change}] + [:div {:class (stl/css :buttons-wrapper)} + [:input {:type "button" + :value "Cancel" + :class (stl/css :cancel-btn) + :on-click on-cancel}] + [:input {:type "button" + :class (stl/css-case :post-btn true + :global/disabled disabled?) + :value "Post" + :on-click on-submit* + :disabled disabled?}]]] - [:div.reply-form.edit-form - [:& resizing-textarea {:value @content - :autofocus true - :select-on-focus true - :on-change on-change}] - [:div.buttons - [:input.btn-primary {:type "button" - :value "Post" - :on-click on-submit* - :disabled (or (fm/all-spaces? @content) - (str/empty-or-nil? @content))}] - [:input.btn-secondary {:type "button" :value "Cancel" :on-click on-cancel}]]])) + [:div.reply-form.edit-form + [:& resizing-textarea {:value @content + :autofocus true + :select-on-focus true + :on-change on-change}] + [:div.buttons + [:input.btn-primary {:type "button" + :value "Post" + :on-click on-submit* + :disabled disabled?}] + [:input.btn-secondary {:type "button" :value "Cancel" :on-click on-cancel}]]]))) (mf/defc comment-item [{:keys [comment thread users origin] :as props}] - (let [owner (get users (:owner-id comment)) + (let [new-css-system (mf/use-ctx ctx/new-css-system) + owner (get users (:owner-id comment)) profile (mf/deref refs/profile) options (mf/use-state false) edition? (mf/use-state false) - on-show-options + on-toggle-options (mf/use-fn (fn [event] (dom/stop-propagation event) - (reset! options true))) + (swap! options not))) on-hide-options (mf/use-fn @@ -264,11 +348,11 @@ (mf/use-fn (mf/deps thread) #(st/emit! (modal/show - {:type :confirm - :title (tr "modals.delete-comment-thread.title") - :message (tr "modals.delete-comment-thread.message") - :accept-label (tr "modals.delete-comment-thread.accept") - :on-accept delete-thread}))) + {:type :confirm + :title (tr "modals.delete-comment-thread.title") + :message (tr "modals.delete-comment-thread.message") + :accept-label (tr "modals.delete-comment-thread.accept") + :on-accept delete-thread}))) on-submit (mf/use-fn @@ -287,39 +371,82 @@ (dom/stop-propagation event) (st/emit! (dcm/update-comment-thread (update thread :is-resolved not)))))] - [:div.comment-container - [:div.comment - [:div.author - [:div.avatar - [:img {:src (cfg/resolve-profile-photo-url owner)}]] - [:div.name - [:div.fullname (:fullname owner)] - [:div.timeago (dt/timeago (:modified-at comment))]] + (if new-css-system + [:div {:class (stl/css :comment-container)} + [:div {:class (stl/css :comment)} + [:div {:class (stl/css :author)} + [:div {:class (stl/css :avatar)} + [:img {:src (cfg/resolve-profile-photo-url owner)}]] + [:div {:class (stl/css :name)} + [:div {:class (stl/css :fullname)} (:fullname owner)] + [:div {:class (stl/css :timeago)} (dt/timeago (:modified-at comment))]] - (when (some? thread) - [:div.options-resolve {:on-click toggle-resolved} - (if (:is-resolved thread) - [:span i/checkbox-checked] - [:span i/checkbox-unchecked])]) + (when (some? thread) + [:div {:class (stl/css :options-resolve-wrapper) + :on-click toggle-resolved} + [:span {:class (stl/css-case :options-resolve true + :global/checked (:is-resolved thread))} i/tick-refactor]]) - (when (= (:id profile) (:id owner)) - [:div.options - [:div.options-icon {:on-click on-show-options} i/actions]])] + (when (= (:id profile) (:id owner)) + [:div {:class (stl/css :options) + :on-click on-toggle-options} + i/menu-refactor])] - [:div.content - (if @edition? - [:& edit-form {:content (:content comment) - :on-submit on-submit - :on-cancel on-cancel}] - [:span.text (:content comment)])]] + [:div {:class (stl/css :content)} + (if @edition? + [:& edit-form {:content (:content comment) + :on-submit on-submit + :on-cancel on-cancel}] + [:span {:class (stl/css :text)} (:content comment)])]] - [:& dropdown {:show @options - :on-close on-hide-options} - [:ul.dropdown.comment-options-dropdown - [:li {:on-click on-edit-clicked} (tr "labels.edit")] - (if thread - [:li {:on-click on-delete-thread} (tr "labels.delete-comment-thread")] - [:li {:on-click on-delete-comment} (tr "labels.delete-comment")])]]])) + [:& dropdown {:show @options + :on-close on-hide-options} + [:ul {:class (stl/css :comment-options-dropdown)} + [:li {:class (stl/css :context-menu-option) + :on-click on-edit-clicked} + (tr "labels.edit")] + (if thread + [:li {:class (stl/css :context-menu-option) + :on-click on-delete-thread} + (tr "labels.delete-comment-thread")] + [:li {:class (stl/css :context-menu-option) + :on-click on-delete-comment} + (tr "labels.delete-comment")])]]] + + + [:div.comment-container + [:div.comment + [:div.author + [:div.avatar + [:img {:src (cfg/resolve-profile-photo-url owner)}]] + [:div.name + [:div.fullname (:fullname owner)] + [:div.timeago (dt/timeago (:modified-at comment))]] + + (when (some? thread) + [:div.options-resolve {:on-click toggle-resolved} + (if (:is-resolved thread) + [:span i/checkbox-checked] + [:span i/checkbox-unchecked])]) + + (when (= (:id profile) (:id owner)) + [:div.options + [:div.options-icon {:on-click on-toggle-options} i/actions]])] + + [:div.content + (if @edition? + [:& edit-form {:content (:content comment) + :on-submit on-submit + :on-cancel on-cancel}] + [:span.text (:content comment)])]] + + [:& dropdown {:show @options + :on-close on-hide-options} + [:ul.dropdown.comment-options-dropdown + [:li {:on-click on-edit-clicked} (tr "labels.edit")] + (if thread + [:li {:on-click on-delete-thread} (tr "labels.delete-comment-thread")] + [:li {:on-click on-delete-comment} (tr "labels.delete-comment")])]]]))) (defn make-comments-ref [thread-id] @@ -328,7 +455,8 @@ (mf/defc thread-comments {::mf/wrap [mf/memo]} [{:keys [thread zoom users origin position-modifier]}] - (let [ref (mf/use-ref) + (let [new-css-system (mf/use-ctx ctx/new-css-system) + ref (mf/use-ref) thread-id (:id thread) @@ -338,8 +466,13 @@ (some? position-modifier) (gpt/transform position-modifier)) - pos-x (+ (* (:x pos) zoom) 14) - pos-y (- (* (:y pos) zoom) 14) + pos-x (if new-css-system + (+ (* (:x pos) zoom) 24) + (+ (* (:x pos) zoom) 14)) + pos-y (if new-css-system + (- (* (:y pos) zoom) 28) + (- (* (:y pos) zoom) 14)) + comments-ref (mf/with-memo [thread-id] (make-comments-ref thread-id)) @@ -360,26 +493,46 @@ (mf/with-layout-effect [thread-pos comments-map] (when-let [node (mf/ref-val ref)] (dom/scroll-into-view-if-needed! node))) + (if new-css-system + (when (some? comment) + [:div {:class (stl/css :thread-content) + :style {:top (str pos-y "px") + :left (str pos-x "px")} + :on-click dom/stop-propagation} - (when (some? comment) - [:div.thread-content - {:style {:top (str pos-y "px") - :left (str pos-x "px")} - :on-click dom/stop-propagation} + [:div {:class (stl/css :comments)} + [:& comment-item {:comment comment + :users users + :thread thread + :origin origin}] + (for [item (rest comments)] + [:* {:key (dm/str (:id item))} + [:& comment-item {:comment item + :users users + :origin origin}]]) + [:div {:ref ref}]] + [:& reply-form {:thread thread}]]) - [:div.comments - [:& comment-item {:comment comment - :users users - :thread thread - :origin origin}] - (for [item (rest comments)] - [:* {:key (dm/str (:id item))} - [:hr] - [:& comment-item {:comment item - :users users - :origin origin}]]) - [:div {:ref ref}]] - [:& reply-form {:thread thread}]]))) + + (when (some? comment) + [:div.thread-content + {:style {:top (str pos-y "px") + :left (str pos-x "px")} + :on-click dom/stop-propagation} + + [:div.comments + [:& comment-item {:comment comment + :users users + :thread thread + :origin origin}] + (for [item (rest comments)] + [:* {:key (dm/str (:id item))} + [:hr] + [:& comment-item {:comment item + :users users + :origin origin}]]) + [:div {:ref ref}]] + [:& reply-form {:thread thread}]])))) (defn use-buble [zoom {:keys [position frame-id]}] @@ -438,7 +591,8 @@ (mf/defc thread-bubble {::mf/wrap [mf/memo]} [{:keys [thread zoom open? on-click origin position-modifier]}] - (let [pos (cond-> (:position thread) + (let [new-css-system (mf/use-ctx ctx/new-css-system) + pos (cond-> (:position thread) (some? position-modifier) (gpt/transform position-modifier)) @@ -493,23 +647,37 @@ (dom/stop-propagation event) (when (= origin :viewer) (on-click thread))))] + (if new-css-system + [:div {:style {:top (str pos-y "px") + :left (str pos-x "px")} + :on-pointer-down on-pointer-down* + :on-pointer-up on-pointer-up* + :on-pointer-move on-pointer-move* + :on-click on-click* + :on-lost-pointer-capture on-lost-pointer-capture + :class (stl/css-case + :floating-thread-bubble true + :resolved (:is-resolved thread) + :unread (pos? (:count-unread-comments thread)))} + [:span (:seqn thread)]] - [:div.thread-bubble - {:style {:top (str pos-y "px") - :left (str pos-x "px")} - :on-pointer-down on-pointer-down* - :on-pointer-up on-pointer-up* - :on-pointer-move on-pointer-move* - :on-click on-click* - :on-lost-pointer-capture on-lost-pointer-capture - :class (dom/classnames - :resolved (:is-resolved thread) - :unread (pos? (:count-unread-comments thread)))} - [:span (:seqn thread)]])) + [:div.thread-bubble + {:style {:top (str pos-y "px") + :left (str pos-x "px")} + :on-pointer-down on-pointer-down* + :on-pointer-up on-pointer-up* + :on-pointer-move on-pointer-move* + :on-click on-click* + :on-lost-pointer-capture on-lost-pointer-capture + :class (dom/classnames + :resolved (:is-resolved thread) + :unread (pos? (:count-unread-comments thread)))} + [:span (:seqn thread)]]))) (mf/defc comment-thread [{:keys [item users on-click]}] - (let [owner (get users (:owner-id item)) + (let [new-css-system (mf/use-ctx ctx/new-css-system) + owner (get users (:owner-id item)) on-click* (mf/use-fn (mf/deps item) @@ -519,48 +687,99 @@ (when (fn? on-click) (on-click item))))] - [:div.comment {:on-click on-click*} - [:div.author - [:div.thread-bubble - {:class (dom/classnames - :resolved (:is-resolved item) - :unread (pos? (:count-unread-comments item)))} - (:seqn item)] - [:div.avatar - [:img {:src (cfg/resolve-profile-photo-url owner)}]] - [:div.name - [:div.fullname (:fullname owner) ", "] - [:div.timeago (dt/timeago (:modified-at item))]]] - [:div.content - [:span.text (:content item)]] - [:div.content.replies - (let [unread (:count-unread-comments item ::none) - total (:count-comments item 1)] - [:* - (when (> total 1) - (if (= total 2) - [:span.total-replies "1 reply"] - [:span.total-replies (str (dec total) " replies")])) + (if new-css-system + [:div {:class (stl/css :comment) + :on-click on-click*} + [:div {:class (stl/css :author)} + [:div {:class (stl/css-case :thread-bubble true + :resolved (:is-resolved item) + :unread (pos? (:count-unread-comments item)))} + (:seqn item)] + [:div {:class (stl/css :avatar)} + [:img {:src (cfg/resolve-profile-photo-url owner)}]] + [:div {:class (stl/css :name)} + [:div {:class (stl/css :fullname)} (:fullname owner)] + [:div {:class (stl/css :timeago)} (dt/timeago (:modified-at item))]]] + [:div {:class (stl/css :content)} + (:content item)] + [:div {:class (stl/css :replies)} + (let [unread (:count-unread-comments item ::none) + total (:count-comments item 1)] + [:* + (when (> total 1) + (if (= total 2) + [:span {:class (stl/css :total-replies)} "1 reply"] + [:span {:class (stl/css :total-replies)} (str (dec total) " replies")])) - (when (and (> total 1) (> unread 0)) - (if (= unread 1) - [:span.new-replies "1 new reply"] - [:span.new-replies (str unread " new replies")]))])]])) + (when (and (> total 1) (> unread 0)) + (if (= unread 1) + [:span {:class (stl/css :new-replies)} "1 new reply"] + [:span {:class (stl/css :new-replies)} (str unread " new replies")]))])]] + + + [:div.comment {:on-click on-click*} + [:div.author + [:div.thread-bubble + {:class (dom/classnames + :resolved (:is-resolved item) + :unread (pos? (:count-unread-comments item)))} + (:seqn item)] + [:div.avatar + [:img {:src (cfg/resolve-profile-photo-url owner)}]] + [:div.name + [:div.fullname (:fullname owner) ", "] + [:div.timeago (dt/timeago (:modified-at item))]]] + [:div.content + [:span.text (:content item)]] + [:div.content.replies + (let [unread (:count-unread-comments item ::none) + total (:count-comments item 1)] + [:* + (when (> total 1) + (if (= total 2) + [:span.total-replies "1 reply"] + [:span.total-replies (str (dec total) " replies")])) + + (when (and (> total 1) (> unread 0)) + (if (= unread 1) + [:span.new-replies "1 new reply"] + [:span.new-replies (str unread " new replies")]))])]]))) (mf/defc comment-thread-group [{:keys [group users on-thread-click]}] - [:div.thread-group - (if (:file-name group) - [:div.section-title - [:span.label.filename (:file-name group) ", "] - [:span.label (:page-name group)]] - [:div.section-title - [:span.icon i/file-html] - [:span.label (:page-name group)]]) - [:div.threads - (for [item (:items group)] - [:& comment-thread - {:item item - :on-click on-thread-click - :users users - :key (:id item)}])]]) + (let [new-css-system (mf/use-ctx ctx/new-css-system)] + (if new-css-system + [:div {:class (stl/css :thread-group)} + (if (:file-name group) + [:div {:class (stl/css :section-title)} + [:span {:class (stl/css :file-name)} (:file-name group) ", "] + [:span {:class (stl/css :page-name)} (:page-name group)]] + + [:div {:class (stl/css :section-title)} + [:span {:class (stl/css :icon)} i/document-refactor] + [:span {:class (stl/css :page-name)} (:page-name group)]]) + + [:div {:class (stl/css :threads)} + (for [item (:items group)] + [:& comment-thread + {:item item + :on-click on-thread-click + :users users + :key (:id item)}])]] + + + [:div.thread-group + (if (:file-name group) + [:div.section-title + [:span.label.filename (:file-name group) ", "] + [:span.label (:page-name group)]] + [:div.section-title + [:span.icon i/file-html] + [:span.label (:page-name group)]]) + [:div.threads + (for [item (:items group)] + [:& comment-thread + {:item item + :on-click on-thread-click + :users users + :key (:id item)}])]]))) diff --git a/frontend/src/app/main/ui/comments.scss b/frontend/src/app/main/ui/comments.scss new file mode 100644 index 000000000..d7a2e1de8 --- /dev/null +++ b/frontend/src/app/main/ui/comments.scss @@ -0,0 +1,232 @@ +// 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 + +@import "refactor/common-refactor.scss"; + +// Comment-thread-group +.thread-group { + padding: 0 $s-12; + .section-title { + @include titleTipography; + height: $s-32; + display: flex; + align-items: center; + margin-bottom: $s-8; + .file-name { + color: var(--comment-subtitle-color); + } + .page-name { + color: var(--comment-subtitle-color); + } + .icon { + display: flex; + align-items: center; + padding: 0 $s-6 0 $s-4; + width: $s-24; + height: $s-32; + margin-left: $s-6; + svg { + @extend .button-icon-small; + stroke: var(--icon-foreground); + } + } + } + .threads { + display: flex; + flex-direction: column; + gap: $s-24; + } +} + +// Comment-thread +.comment { + @include titleTipography; + display: flex; + flex-direction: column; + gap: $s-12; + .author { + display: flex; + gap: $s-8; + .thread-bubble { + @extend .comment-bubbles; + &.resolved { + @extend .resolved-comment-bubble; + } + &.unread { + @extend .unread-comment-bubble; + } + } + .avatar { + height: $s-32; + width: $s-32; + border-radius: $br-circle; + img { + border-radius: $br-circle; + } + } + .name { + flex-grow: 1; + .fullname { + @include textEllipsis; + color: var(--comment-title-color); + } + .timeago { + @include textEllipsis; + color: var(--comment-subtitle-color); + } + } + } + .content { + @include titleTipography; + color: var(--color-foreground-primary); + } + .replies { + display: flex; + gap: $s-8; + .total-replies { + color: var(--color-foreground-secondary); + } + .new-replies { + color: var(--color-accent-primary); + } + } +} + +// Thread-bubble + +.floating-thread-bubble { + @extend .comment-bubbles; + position: absolute; + cursor: pointer; + pointer-events: auto; + transform: translate(calc(-1 * $s-16), calc(-1 * $s-16)); + + &.resolved { + @extend .resolved-comment-bubble; + } + &.unread { + @extend .unread-comment-bubble; + } +} + +// thread-content +.thread-content { + position: absolute; + pointer-events: auto; + user-select: text; + width: $s-284; + padding: $s-12; + border-radius: $br-8; + background-color: var(--comment-modal-background-color); + .comments { + display: flex; + flex-direction: column; + gap: $s-24; + } +} + +// comment-item + +.comment-container { + position: relative; + .comment { + @include titleTipography; + .author { + display: flex; + gap: $s-8; + .avatar { + height: $s-32; + width: $s-32; + border-radius: $br-circle; + img { + border-radius: $br-circle; + } + } + .name { + flex-grow: 1; + .fullname { + @include textEllipsis; + color: var(--comment-title-color); + } + .timeago { + @include textEllipsis; + color: var(--comment-subtitle-color); + } + } + .options-resolve-wrapper { + @include flexCenter; + width: $s-16; + height: $s-32; + .options-resolve { + @extend .checkbox-icon; + cursor: pointer; + } + } + .options { + @extend .button-tertiary; + height: $s-32; + width: $s-28; + svg { + @extend .button-icon; + } + } + } + .content { + position: relative; + .text { + @include titleTipography; + } + } + } + .comment-options-dropdown { + @extend .dropdown-wrapper; + position: absolute; + width: $s-120; + right: 0; + left: unset; + .context-menu-option { + @extend .dropdown-element-base; + } + } +} + +// edit-form & reply-form + +.edit-form, +.reply-form { + textarea { + @extend .input-element; + line-height: 1.45; + height: 100%; + width: 100%; + max-width: $s-260; + min-width: $s-260; + margin-bottom: $s-8; + padding: $s-8; + color: var(--input-foreground-color-active); + &:focus { + border: $s-1 solid var(--input-border-color-active); + outline: none; + } + } + .buttons-wrapper { + display: flex; + justify-content: flex-end; + gap: $s-4; + .post-btn { + @extend .button-primary; + height: $s-32; + width: $s-92; + margin-bottom: 0; + } + .cancel-btn { + @extend .button-secondary; + height: $s-32; + width: $s-92; + margin-bottom: 0; + } + } +} diff --git a/frontend/src/app/main/ui/components/radio_buttons.scss b/frontend/src/app/main/ui/components/radio_buttons.scss index 9f575372d..79c8f3362 100644 --- a/frontend/src/app/main/ui/components/radio_buttons.scss +++ b/frontend/src/app/main/ui/components/radio_buttons.scss @@ -44,19 +44,19 @@ &.disabled { cursor: default; svg { - stroke: var(--button-foreground-color-disabled); + stroke: var(--button-background-color-disabled); } .title-name { - color: var(--button-foreground-color-disabled); + color: var(--button-background-color-disabled); } &:hover { border: none; background-color: transparent; svg { - stroke: var(--button-foreground-color-disabled); + stroke: var(--button-background-color-disabled); } .title-name { - color: var(--button-foreground-color-disabled); + color: var(--button-background-color-disabled); } } } diff --git a/frontend/src/app/main/ui/workspace/color_palette.scss b/frontend/src/app/main/ui/workspace/color_palette.scss index 3a273d6e6..8c65031f1 100644 --- a/frontend/src/app/main/ui/workspace/color_palette.scss +++ b/frontend/src/app/main/ui/workspace/color_palette.scss @@ -45,7 +45,7 @@ } &:disabled { svg { - stroke: var(--button-foreground-color-disabled); + stroke: var(--button-background-color-disabled); } &::after { background-image: none; diff --git a/frontend/src/app/main/ui/workspace/comments.cljs b/frontend/src/app/main/ui/workspace/comments.cljs index 45d5633da..ca7a4d4e8 100644 --- a/frontend/src/app/main/ui/workspace/comments.cljs +++ b/frontend/src/app/main/ui/workspace/comments.cljs @@ -5,6 +5,7 @@ ;; Copyright (c) KALEIDOS INC (ns app.main.ui.workspace.comments + (:require-macros [app.main.style :as stl]) (:require [app.main.data.comments :as dcm] [app.main.data.events :as ev] @@ -27,54 +28,104 @@ (mf/defc sidebar-options [] - (let [{cmode :mode cshow :show} (mf/deref refs/comments-local) + (let [new-css-system (mf/use-ctx ctx/new-css-system) + {cmode :mode cshow :show} (mf/deref refs/comments-local) update-mode (mf/use-fn - (fn [mode] - (st/emit! (dcm/update-filters {:mode mode})))) + (fn [event] + (let [mode (-> (dom/get-current-target event) + (dom/get-data "value") + (keyword))] + (st/emit! (dcm/update-filters {:mode mode}))))) update-show (mf/use-fn - (fn [mode] - (st/emit! (dcm/update-filters {:show mode}))))] + (mf/deps cshow) + (fn [] + (let [mode (if (= :pending cshow) :all :pending)] + (st/emit! (dcm/update-filters {:show mode})))))] - [:ul.dropdown.with-check - [:li {:class (dom/classnames :selected (or (= :all cmode) (nil? cmode))) - :on-click #(update-mode :all)} - [:span.icon i/tick] - [:span.label (tr "labels.show-all-comments")]] + (if new-css-system + [:ul {:class (stl/css :comment-mode-dropdown)} + [:li {:class (stl/css-case :dropdown-item true + :selected (or (= :all cmode) (nil? cmode))) + :data-value "all" + :on-click update-mode} - [:li {:class (dom/classnames :selected (= :yours cmode)) - :on-click #(update-mode :yours)} - [:span.icon i/tick] - [:span.label (tr "labels.show-your-comments")]] + [:span {:class (stl/css :label)} (tr "labels.show-all-comments")] + [:span {:class (stl/css :icon)} i/tick-refactor]] + [:li {:class (stl/css-case :dropdown-item true + :selected (= :yours cmode)) + :data-value "yours" + :on-click update-mode} + [:span {:class (stl/css :label)} (tr "labels.show-your-comments")] + [:span {:class (stl/css :icon)} i/tick-refactor]] + [:li {:class (stl/css :separator)}] + [:li {:class (stl/css-case :dropdown-item true + :selected (= :pending cshow)) + :on-click update-show} + [:span {:class (stl/css :label)} (tr "labels.hide-resolved-comments")] + [:span {:class (stl/css :icon)} i/tick-refactor]]] - [:hr] - [:li {:class (dom/classnames :selected (= :pending cshow)) - :on-click #(update-show (if (= :pending cshow) :all :pending))} - [:span.icon i/tick] - [:span.label (tr "labels.hide-resolved-comments")]]])) + [:ul.dropdown.with-check + [:li {:class (dom/classnames :selected (or (= :all cmode) (nil? cmode))) + :data-value "all" + :on-click update-mode} + [:span.icon i/tick] + [:span.label (tr "labels.show-all-comments")]] + + [:li {:class (dom/classnames :selected (= :yours cmode)) + :data-value "yours" + :on-click update-mode} + [:span.icon i/tick] + [:span.label (tr "labels.show-your-comments")]] + + [:hr] + + [:li {:class (dom/classnames :selected (= :pending cshow)) + :on-click update-show} + [:span.icon i/tick] + [:span.label (tr "labels.hide-resolved-comments")]]]) + + )) (mf/defc comments-sidebar [{:keys [users threads page-id]}] - (let [threads-map (mf/deref refs/threads-ref) + (let [new-css-system (mf/use-ctx ctx/new-css-system) + threads-map (mf/deref refs/threads-ref) profile (mf/deref refs/profile) users-refs (mf/deref refs/current-file-comments-users) users (or users users-refs) local (mf/deref refs/comments-local) - options? (mf/use-state false) + + state* (mf/use-state false) + options? (deref state*) + threads (if (nil? threads) (->> (vals threads-map) (sort-by :modified-at) (reverse) (dcm/apply-filters local profile)) threads) - tgroups (->> threads - (dcm/group-threads-by-page)) + + close-section + (mf/use-fn + (mf/deps) + #(st/emit! :interrupt (dw/deselect-all true))) + + tgroups (->> threads + (dcm/group-threads-by-page)) page-id (or page-id (mf/use-ctx ctx/current-page-id)) + toggle-mode-selector + (mf/use-fn + (mf/deps) + (fn [event] + (dom/stop-propagation event) + (swap! state* not))) + on-thread-click (mf/use-fn (mf/deps page-id) @@ -88,38 +139,76 @@ (dwcm/center-to-comment-thread thread) (-> (dcm/open-thread thread) (with-meta {::ev/origin "workspace"})))))))] + (if new-css-system + [:div {:class (stl/css :comments-section)} + [:div {:class (stl/css :comments-section-title)} + [:span (tr "labels.comments")] + [:button {:class (stl/css :close-button) + :on-click close-section} + i/close-refactor]] - [:div.comments-section.comment-threads-section - [:div.workspace-comment-threads-sidebar-header - [:div.label (tr "labels.comments")] - [:div.options {:on-click - (fn [event] - (dom/stop-propagation event) - (reset! options? true))} - [:div.label (case (:mode local) - (nil :all) (tr "labels.all") - :yours (tr "labels.only-yours"))] - [:div.icon i/arrow-down]] + (when (seq tgroups) + [:button {:class (stl/css :mode-dropdown-wrapper) + :on-click toggle-mode-selector} - [:& dropdown {:show @options? - :on-close #(reset! options? false)} - [:& sidebar-options {:local local}]]] + [:span {:class (stl/css :mode-label)} (case (:mode local) + (nil :all) (tr "labels.show-all-comments") + :yours (tr "labels.show-your-comments"))] + [:div {:class (stl/css :icon)} i/arrow-refactor]] - (if (seq tgroups) - [:div.thread-groups - [:& cmt/comment-thread-group - {:group (first tgroups) - :on-thread-click on-thread-click - :users users}] - (for [tgroup (rest tgroups)] - [:* - [:hr] + [:& dropdown {:show options? + :on-close #(reset! state* false)} + [:& sidebar-options {:local local}]]) + + [:div {:class (stl/css :comments-section-content)} + + (if (seq tgroups) + [:div {:class (stl/css :thread-groups)} [:& cmt/comment-thread-group - {:group tgroup + {:group (first tgroups) :on-thread-click on-thread-click - :users users - :key (:page-id tgroup)}]])] + :users users}] + (for [tgroup (rest tgroups)] + [:& cmt/comment-thread-group + {:group tgroup + :on-thread-click on-thread-click + :users users + :key (:page-id tgroup)}])] - [:div.thread-groups-placeholder - i/chat - (tr "labels.no-comments-available")])])) + [:div {:class (stl/css :thread-group-placeholder)} + [:span {:class (stl/css :placeholder-icon)} i/comments-refactor] + [:span {:class (stl/css :placeholder-label)} + (tr "labels.no-comments-available")]])]] + + + [:div.comments-section.comment-threads-section + [:div.workspace-comment-threads-sidebar-header + [:div.label (tr "labels.comments")] + [:div.options {:on-click toggle-mode-selector} + [:div.label (case (:mode local) + (nil :all) (tr "labels.all") + :yours (tr "labels.only-yours"))] + [:div.icon i/arrow-down]] + + [:& dropdown {:show options? + :on-close #(reset! state* false)} + [:& sidebar-options {:local local}]]] + + (if (seq tgroups) + [:div.thread-groups + [:& cmt/comment-thread-group + {:group (first tgroups) + :on-thread-click on-thread-click + :users users}] + (for [tgroup (rest tgroups)] + [:* + [:hr] + [:& cmt/comment-thread-group + {:group tgroup + :on-thread-click on-thread-click + :users users + :key (:page-id tgroup)}]])] + + [:div.thread-groups-placeholder + i/chat + (tr "labels.no-comments-available")])]))) diff --git a/frontend/src/app/main/ui/workspace/comments.scss b/frontend/src/app/main/ui/workspace/comments.scss new file mode 100644 index 000000000..7f5e1c70c --- /dev/null +++ b/frontend/src/app/main/ui/workspace/comments.scss @@ -0,0 +1,141 @@ +// 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 + +@import "refactor/common-refactor.scss"; + +.comments-section { + position: relative; + background-color: var(--panel-background-color); + .comments-section-title { + @include flexCenter; + @include tabTitleTipography; + display: flex; + justify-content: space-between; + align-items: center; + height: $s-32; + min-height: $s-32; + margin: $s-8 $s-8 0 $s-8; + border-radius: $br-8; + background-color: var(--panel-title-background-color); + span { + @include flexCenter; + flex-grow: 1; + color: var(--title-foreground-color-hover); + } + + .close-button { + @extend .button-tertiary; + height: $s-28; + width: $s-28; + border-radius: $br-6; + svg { + @extend .button-icon; + stroke: var(--icon-foreground); + } + } + } + .mode-dropdown-wrapper { + @include buttonStyle; + @extend .asset-element; + background-color: var(--color-background-tertiary); + display: flex; + width: $s-256; + height: $s-32; + padding: $s-8; + border-radius: $br-8; + margin: $s-16 auto 0 auto; + cursor: pointer; + position: relative; + .mode-label { + padding-right: 8px; + flex-grow: 1; + display: flex; + justify-content: flex-start; + } + .icon { + @include flexCenter; + padding-right: 8px; + height: $s-24; + width: $s-24; + svg { + @extend .button-icon-small; + transform: rotate(90deg); + stroke: var(--icon-foreground); + } + } + } + .comment-mode-dropdown { + @extend .dropdown-wrapper; + top: $s-80; + left: $s-12; + width: $s-256; + .dropdown-item { + @extend .dropdown-element-base; + justify-content: space-between; + .icon { + @include flexCenter; + height: $s-24; + width: $s-24; + svg { + @extend .button-icon-small; + stroke: transparent; + } + } + .label { + @include titleTipography; + } + &:hover { + .icon svg { + stroke: transparent; + } + } + &.selected { + .label { + color: var(--menu-foreground-color); + } + .icon svg { + stroke: var(--icon-foreground-hover); + } + } + } + .separator { + height: $s-12; + } + } + .comments-section-content { + .thread-groups { + display: flex; + flex-direction: column; + gap: $s-24; + } + .thread-group-placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + gap: $s-4; + margin-top: $s-36; + .placeholder-icon { + @include flexCenter; + height: $s-48; + width: $s-48; + border-radius: $br-circle; + background-color: var(--empty-message-background-color); + svg { + @extend .button-icon; + height: $s-28; + width: $s-28; + stroke: var(--empty-message-foreground-color); + } + } + .placeholder-label { + @include titleTipography; + text-align: center; + width: $s-184; + } + } + } +} diff --git a/frontend/src/app/main/ui/workspace/sidebar/history.scss b/frontend/src/app/main/ui/workspace/sidebar/history.scss index 9dcb922b2..175742a2d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/history.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/history.scss @@ -17,7 +17,7 @@ position: relative; height: $s-32; min-height: $s-32; - margin: $s-4 $s-4 0 $s-4; + margin: $s-8 $s-8 0 $s-8; border-radius: $br-8; background-color: var(--panel-title-background-color); @@ -53,7 +53,7 @@ @include flexCenter; height: $s-48; width: $s-48; - border-radius: 50%; + border-radius: $br-circle; background-color: var(--empty-message-background-color); svg { @extend .button-icon; diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.scss index 07c0002f5..bee5c3079 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.scss @@ -28,12 +28,12 @@ &.disabled { cursor: default; svg { - stroke: var(--button-foreground-color-disabled); + stroke: var(--button-background-color-disabled); } &:hover { background-color: var(--panel-background-color); svg { - stroke: var(--button-foreground-color-disabled); + stroke: var(--button-background-color-disabled); } } } diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.scss index 421331ea5..4b09dc888 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.scss @@ -26,12 +26,12 @@ &.disabled { cursor: default; svg { - stroke: var(--button-foreground-color-disabled); + stroke: var(--button-background-color-disabled); } &:hover { background-color: var(--panel-background-color); svg { - stroke: var(--button-foreground-color-disabled); + stroke: var(--button-background-color-disabled); } } } diff --git a/frontend/src/app/main/ui/workspace/text_palette.scss b/frontend/src/app/main/ui/workspace/text_palette.scss index 6e82f1050..421511c4d 100644 --- a/frontend/src/app/main/ui/workspace/text_palette.scss +++ b/frontend/src/app/main/ui/workspace/text_palette.scss @@ -43,7 +43,7 @@ } &:disabled { svg { - stroke: var(--button-foreground-color-disabled); + stroke: var(--button-background-color-disabled); } &::after { background-image: none;