0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-24 15:39:50 -05:00

💄 Update comment UI with new design

This commit is contained in:
Eva 2023-10-13 15:00:50 +02:00
parent 25c60f3e0f
commit f4323fd1ac
12 changed files with 999 additions and 255 deletions

View file

@ -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;

View file

@ -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);
}

View file

@ -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)}])]])))

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -45,7 +45,7 @@
}
&:disabled {
svg {
stroke: var(--button-foreground-color-disabled);
stroke: var(--button-background-color-disabled);
}
&::after {
background-image: none;

View file

@ -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")])])))

View file

@ -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;
}
}
}
}

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -43,7 +43,7 @@
}
&:disabled {
svg {
stroke: var(--button-foreground-color-disabled);
stroke: var(--button-background-color-disabled);
}
&::after {
background-image: none;