@@ -8,32 +8,30 @@ import { component_size, size } from "../../../common/gui/size.js"
88import { AllIcons , Icon , IconSize } from "../../../common/gui/base/Icon.js"
99import { Icons } from "../../../common/gui/base/icons/Icons.js"
1010import { theme } from "../../../common/gui/theme.js"
11- import { Keys , MAX_LABELS_PER_MAIL , TabIndex } from "../../../common/api/common/TutanotaConstants.js"
11+ import { Keys , TabIndex } from "../../../common/api/common/TutanotaConstants.js"
1212import { getElementId } from "../../../common/api/common/utils/EntityUtils.js"
1313import { getLabelColor } from "../../../common/gui/base/Label.js"
1414import { LabelState } from "../model/MailModel.js"
1515import { AriaRole } from "../../../common/gui/AriaUtils.js"
1616import { lang } from "../../../common/misc/LanguageViewModel.js"
1717import { noOp } from "@tutao/tutanota-utils"
18+ import { LabelsPopupViewModel } from "./LabelsPopupViewModel"
1819
1920/**
2021 * Popup that displays assigned labels and allows changing them
2122 */
2223export class LabelsPopup implements ModalComponent {
2324 private dom : HTMLElement | null = null
24- private isMaxLabelsReached : boolean
2525
2626 constructor (
2727 private readonly sourceElement : HTMLElement ,
2828 private readonly origin : PosRect ,
2929 private readonly width : number ,
30- private readonly labelsForMails : ReadonlyMap < Id , ReadonlyArray < MailSet > > ,
31- private readonly labels : { label : MailSet ; state : LabelState } [ ] ,
30+ private readonly viewModel : LabelsPopupViewModel ,
3231 private readonly onLabelsApplied : ( addedLabels : MailSet [ ] , removedLabels : MailSet [ ] ) => unknown ,
3332 ) {
3433 this . view = this . view . bind ( this )
3534 this . oncreate = this . oncreate . bind ( this )
36- this . isMaxLabelsReached = this . checkIsMaxLabelsReached ( )
3735 }
3836
3937 async hideAnimation ( ) : Promise < void > { }
@@ -69,49 +67,47 @@ export class LabelsPopup implements ModalComponent {
6967 [
7068 m (
7169 ".pb-8.scroll" ,
72- this . labels
73- . sort ( ( labelA , labelB ) => labelA . label . name . localeCompare ( labelB . label . name ) )
74- . map ( ( labelState ) => {
75- const { label, state } = labelState
76- const color = theme . on_surface
77- const canToggleLabel = state === LabelState . Applied || state === LabelState . AppliedToSome || ! this . isMaxLabelsReached
78- const opacity = ! canToggleLabel ? 0.5 : undefined
79-
80- return m (
81- "label-item.flex.items-center.plr-12.state-bg.cursor-pointer" ,
82-
83- {
84- "data-labelid" : getElementId ( label ) ,
85- role : AriaRole . MenuItemCheckbox ,
86- tabindex : TabIndex . Default ,
87- "aria-checked" : ariaCheckedForState ( state ) ,
88- "aria-disabled" : ! canToggleLabel ,
89- onclick : canToggleLabel ? ( ) => this . toggleLabel ( labelState ) : noOp ,
90- } ,
91- [
92- m ( Icon , {
93- icon : this . iconForState ( state ) ,
94- size : IconSize . PX24 ,
70+ this . viewModel . getLabelState ( ) . map ( ( labelState ) => {
71+ const { label, state } = labelState
72+ const color = theme . on_surface
73+ const canToggleLabel = state === LabelState . Applied || state === LabelState . AppliedToSome || ! this . viewModel . isLabelLimitReached ( )
74+ const opacity = ! canToggleLabel ? 0.5 : undefined
75+
76+ return m (
77+ "label-item.flex.items-center.plr-12.state-bg.cursor-pointer" ,
78+
79+ {
80+ "data-labelid" : getElementId ( label ) ,
81+ role : AriaRole . MenuItemCheckbox ,
82+ tabindex : TabIndex . Default ,
83+ "aria-checked" : ariaCheckedForState ( state ) ,
84+ "aria-disabled" : ! canToggleLabel ,
85+ onclick : canToggleLabel ? ( ) => this . viewModel . toggleLabel ( label ) : noOp ,
86+ } ,
87+ [
88+ m ( Icon , {
89+ icon : this . iconForState ( state ) ,
90+ size : IconSize . PX24 ,
91+ style : {
92+ fill : getLabelColor ( label . color ) ,
93+ opacity,
94+ } ,
95+ } ) ,
96+ m (
97+ ".button-height.flex.items-center.ml-12.overflow-hidden" ,
98+ {
9599 style : {
96- fill : getLabelColor ( label . color ) ,
100+ color,
97101 opacity,
98102 } ,
99- } ) ,
100- m (
101- ".button-height.flex.items-center.ml-12.overflow-hidden" ,
102- {
103- style : {
104- color,
105- opacity,
106- } ,
107- } ,
108- m ( ".text-ellipsis" , label . name ) ,
109- ) ,
110- ] ,
111- )
112- } ) ,
103+ } ,
104+ m ( ".text-ellipsis" , label . name ) ,
105+ ) ,
106+ ] ,
107+ )
108+ } ) ,
113109 ) ,
114- this . isMaxLabelsReached && m ( ".small.center.pb-8" , lang . get ( "maximumLabelsPerMailReached_msg" ) ) ,
110+ this . viewModel . isLabelLimitReached ( ) ? m ( ".small.center.pb-8" , lang . get ( "maximumLabelsPerMailReached_msg" ) ) : null ,
115111 m ( BaseButton , {
116112 label : "apply_action" ,
117113 text : lang . get ( "apply_action" ) ,
@@ -143,48 +139,8 @@ export class LabelsPopup implements ModalComponent {
143139 }
144140 }
145141
146- private checkIsMaxLabelsReached ( ) : boolean {
147- const { addedLabels, removedLabels } = this . getSortedLabels ( )
148- if ( addedLabels . length >= MAX_LABELS_PER_MAIL ) {
149- return true
150- }
151-
152- for ( const [ , labels ] of this . labelsForMails ) {
153- const labelsOnMail = new Set < Id > ( labels . map ( ( label ) => getElementId ( label ) ) )
154-
155- for ( const label of removedLabels ) {
156- labelsOnMail . delete ( getElementId ( label ) )
157- }
158- if ( labelsOnMail . size >= MAX_LABELS_PER_MAIL ) {
159- return true
160- }
161-
162- for ( const label of addedLabels ) {
163- labelsOnMail . add ( getElementId ( label ) )
164- if ( labelsOnMail . size >= MAX_LABELS_PER_MAIL ) {
165- return true
166- }
167- }
168- }
169-
170- return false
171- }
172-
173- private getSortedLabels ( ) : Record < "addedLabels" | "removedLabels" , MailSet [ ] > {
174- const removedLabels : MailSet [ ] = [ ]
175- const addedLabels : MailSet [ ] = [ ]
176- for ( const { label, state } of this . labels ) {
177- if ( state === LabelState . Applied ) {
178- addedLabels . push ( label )
179- } else if ( state === LabelState . NotApplied ) {
180- removedLabels . push ( label )
181- }
182- }
183- return { addedLabels, removedLabels }
184- }
185-
186142 private applyLabels ( ) {
187- const { addedLabels, removedLabels } = this . getSortedLabels ( )
143+ const { addedLabels, removedLabels } = this . viewModel . getLabelStateChange ( )
188144 this . onLabelsApplied ( addedLabels , removedLabels )
189145 modal . remove ( this )
190146 }
@@ -193,7 +149,7 @@ export class LabelsPopup implements ModalComponent {
193149 this . dom = vnode . dom as HTMLElement
194150
195151 // restrict label height to showing maximum 6 labels to avoid overflow
196- const displayedLabels = Math . min ( this . labels . length , 6 )
152+ const displayedLabels = Math . min ( this . viewModel . getLabelState ( ) . length , 6 )
197153 const height = ( displayedLabels + 1 ) * component_size . button_height + size . spacing_8 * 2
198154 showDropdown ( this . origin , this . dom , height , this . width ) . then ( ( ) => {
199155 const firstLabel = vnode . dom . getElementsByTagName ( "label-item" ) . item ( 0 )
@@ -243,10 +199,7 @@ export class LabelsPopup implements ModalComponent {
243199 exec : ( ) => {
244200 const labelId = document . activeElement ?. getAttribute ( "data-labelid" )
245201 if ( labelId ) {
246- const labelItem = this . labels . find ( ( item ) => getElementId ( item . label ) === labelId )
247- if ( labelItem ) {
248- this . toggleLabel ( labelItem )
249- }
202+ this . viewModel . toggleLabelById ( labelId )
250203 } else {
251204 return true
252205 }
@@ -258,22 +211,6 @@ export class LabelsPopup implements ModalComponent {
258211 show ( ) {
259212 modal . displayUnique ( this , false )
260213 }
261-
262- private toggleLabel ( labelState : { label : MailSet ; state : LabelState } ) {
263- switch ( labelState . state ) {
264- case LabelState . AppliedToSome :
265- labelState . state = this . isMaxLabelsReached ? LabelState . NotApplied : LabelState . Applied
266- break
267- case LabelState . NotApplied :
268- labelState . state = LabelState . Applied
269- break
270- case LabelState . Applied :
271- labelState . state = LabelState . NotApplied
272- break
273- }
274-
275- this . isMaxLabelsReached = this . checkIsMaxLabelsReached ( )
276- }
277214}
278215
279216function ariaCheckedForState ( state : LabelState ) : string {
0 commit comments