saby-customizer 0.0.0-pre.7 → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,52 +1,632 @@
1
1
  import { oom } from '@notml/core'
2
- import { toggleDialog } from '../lib/popup-dialog.js'
3
- import { keysMapping } from '../lib/hotkeys.js'
4
- import { getListOfOpenCards } from '../saby-lib/edo.js'
2
+ import { keysMapping, toolbar, PopupDialog, getListOfOpenCards, snackbar, context, db } from 'saby-customizer/lib.js'
3
+ import { octicons } from 'saby-customizer/octicons.js'
5
4
 
6
5
 
6
+ /** Класс для управления данными карточки задачи */
7
+ export class GitBranchCardData {
8
+
9
+ /**
10
+ * @typedef GitCardData
11
+ * @property {string} [branchSubName] Имя ветки указанное пользователем
12
+ */
13
+ /** @param {import('saby-customizer/lib.js').CardData & GitCardData} data Данные о задаче */
14
+ constructor(data) {
15
+ /** @type {import('saby-customizer/lib.js').CardData & GitCardData} Данные о задаче */
16
+ this.raw = {
17
+ id: data.id,
18
+ number: data.number,
19
+ branchSubName: data.branchSubName || data.number,
20
+ date: data.date,
21
+ name: data.name,
22
+ uuid: data.uuid,
23
+ url: data.url,
24
+ author: data.author,
25
+ milestones: data.milestones,
26
+ version: data.version,
27
+ description: data.description
28
+ }
29
+ }
30
+
31
+ /** @returns {string} Название регламента Задача/Ошибка и т.д. */
32
+ get name() {
33
+ return this.raw.name
34
+ }
35
+
36
+ /** @returns {string} Заголовок для описания задачи */
37
+ get branchTitle() {
38
+ const { name } = this.raw
39
+ const docNumber = ' № ' + this.raw.number
40
+ const version = ' веха ' + this.raw.version
41
+ const date = ' от ' + this.raw.date
42
+ const author = ' ' + this.raw.author
43
+
44
+ return name + docNumber + version + date + author
45
+ }
46
+
47
+ /** @returns {string} Версия задачи (веха), выбранная пользователем */
48
+ get version() {
49
+ return this.raw.version
50
+ }
51
+
52
+ /**
53
+ * Обновление выбранной версии и обновление карточки задачи
54
+ *
55
+ * @param {string} value Версия
56
+ */
57
+ set version(value) {
58
+ this.raw.version = value || this.raw.version
59
+ }
60
+
61
+ /** @returns {string} Часть имени ветки, зависящая от регламента задачи */
62
+ get branchType() {
63
+ return this.raw.name === 'Ошибка' ? 'bugfix' : 'feature'
64
+ }
65
+
66
+ /** @returns {string} Часть имени ветки, определяемая пользователем */
67
+ get branchSubName() {
68
+ return this.raw.branchSubName
69
+ }
70
+
71
+ /**
72
+ * Обновление доп. имени ветки и обновление карточки задачи
73
+ *
74
+ * @param {string} value Доп. имя ветки
75
+ */
76
+ set branchSubName(value) {
77
+ this.raw.branchSubName = value || this.raw.number
78
+ }
79
+
80
+ /** @returns {string} Краткое описание задачи */
81
+ get description() {
82
+ return this.raw.description
83
+ }
84
+
85
+ /**
86
+ * Обновление краткого описания и карточки задачи
87
+ *
88
+ * @param {string} value Доп. имя ветки
89
+ */
90
+ set description(value) {
91
+ this.raw.description = value || ''
92
+ }
93
+
94
+ /** @returns {string[]} Список доступных вех (версий) */
95
+ get milestones() {
96
+ return this.raw.milestones
97
+ }
98
+
99
+ /** @returns {string} Ссылка на документ в отдельной вкладке */
100
+ get url() {
101
+ return this.raw.url
102
+ }
103
+
104
+ /**
105
+ * Получение имени ветки с учетом выбора вехи и доп. имени ветки
106
+ *
107
+ * @returns {Promise<string>} Имя ветки
108
+ */
109
+ async getBranchName() {
110
+ const userLogin = await context.userLogin
111
+
112
+ return `${this.version}/${this.branchType}/${userLogin}/${this.branchSubName}`
113
+ }
114
+
115
+ /** Копирование ссылки на задачу */
116
+ copyLink() {
117
+ navigator.clipboard.writeText(this.raw.url)
118
+ snackbar.show(`Скопирована ссылка: ${this.raw.url}`)
119
+ }
120
+
121
+ /** Копирование имени ветки */
122
+ async copyBranchName() {
123
+ const branchName = await this.getBranchName()
124
+
125
+ navigator.clipboard.writeText(branchName)
126
+ snackbar.show(`Скопировано имя ветки: ${branchName}`)
127
+
128
+ await this.saveToHistory()
129
+ }
130
+
131
+ /** Копирование описания для git commit */
132
+ async copyDescription() {
133
+ const description = this.branchTitle + '\n' +
134
+ this.raw.url + '\n' +
135
+ this.raw.description
136
+
137
+ navigator.clipboard.writeText(description)
138
+ snackbar.show(`Скопировано описание для git commit:\n${description}`)
139
+
140
+ await this.saveToHistory()
141
+ }
142
+
143
+ /** Сохраняет данные в историю по карточкам для которых копировали имя ветки или описание */
144
+ async saveToHistory() {
145
+ const branchName = await this.getBranchName()
146
+ const gitBranchHistory = db.table('gitBranchHistory')
147
+ const historyOldData = await gitBranchHistory.get({
148
+ id: this.raw.id,
149
+ branch: branchName
150
+ })
151
+ const historyData = {
152
+ id: this.raw.id,
153
+ branch: branchName,
154
+ version: this.version,
155
+ useDate: new Date(),
156
+ data: this.raw
157
+ }
158
+
159
+ if (historyOldData) {
160
+ await gitBranchHistory.update(historyOldData.pk, historyData)
161
+ } else {
162
+ await gitBranchHistory.add(historyData)
163
+ }
164
+ }
165
+
166
+ /** Восстановление из истории последнего сохраненного состояния карточки */
167
+ async restoreFromHistory() {
168
+ const gitBranchHistory = db.table('gitBranchHistory')
169
+ const historyData = await gitBranchHistory
170
+ .where({ id: this.raw.id })
171
+ .reverse()
172
+ .sortBy('useDate')
173
+
174
+ if (historyData && historyData.length > 0) {
175
+ const [{ data: { version, branchSubName, description } }] = historyData
176
+
177
+ this.version = version
178
+ this.branchSubName = branchSubName
179
+ this.description = description
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Удаление карточки задачи из истории
185
+ *
186
+ * @param {number} primaryKey Ид записи в истории
187
+ */
188
+ async removeFromHistory(primaryKey) {
189
+ const gitBranchHistory = db.table('gitBranchHistory')
190
+
191
+ await gitBranchHistory.delete(primaryKey)
192
+ }
193
+
194
+ }
195
+
7
196
  /** Карточка со сведениями о задаче для копирования ветки */
8
197
  class GitBranchCard extends oom.extends(HTMLElement) {
9
198
 
10
199
  static tagName = 'sc-git-branch-сard'
11
200
  static style = oom.style({
12
- 'display': 'contents',
201
+ 'display': 'flex',
202
+ 'padding': '12px 12px 0',
203
+ 'width': '600px',
204
+ 'background': 'var(--mdc-theme-background)',
205
+ 'sc-git-branch-сard:last-child': {
206
+ paddingBottom: '12px'
207
+ },
13
208
  '.mdc-card': {
14
- margin: '16px',
15
- padding: '12px'
209
+ padding: '12px',
210
+ minWidth: '0',
211
+ width: '100%'
16
212
  },
17
213
  '.title': {
18
- fontWeight: 'bold'
214
+ fontWeight: 'bold',
215
+ fontSize: '18px',
216
+ overflow: 'hidden',
217
+ whiteSpace: 'nowrap',
218
+ textOverflow: 'ellipsis'
219
+ },
220
+ '.history-info': {
221
+ fontSize: '13px',
222
+ fontStyle: 'italic'
19
223
  },
20
224
  '.link': {
21
- fontSize: '14px'
225
+ fontSize: '14px',
226
+ color: 'var(--mdc-theme-primary, #6200ee)'
227
+ },
228
+ '.branch-line': {
229
+ display: 'flex',
230
+ alignItems: 'center',
231
+ justifyContent: 'space-between'
232
+ },
233
+ '.branch': {
234
+ display: 'flex',
235
+ alignItems: 'baseline',
236
+ maxWidth: '460px',
237
+ height: '48px'
238
+ },
239
+ '.version-button': {
240
+ borderBottom: '1px dashed',
241
+ cursor: 'pointer',
242
+ marginBottom: '-1px',
243
+ color: 'var(--mdc-theme-primary, #6200ee)'
244
+ },
245
+ '.sub-name-button': {
246
+ borderBottom: '1px dashed',
247
+ marginBottom: '-1px',
248
+ cursor: 'pointer',
249
+ color: 'var(--mdc-theme-primary, #6200ee)',
250
+ whiteSpace: 'nowrap',
251
+ overflow: 'hidden',
252
+ textOverflow: 'ellipsis'
253
+ },
254
+ '.branch-part': {
255
+ padding: '0 2px',
256
+ lineHeight: '48px'
257
+ },
258
+ '.branch-sub-name': {
259
+ height: '48px',
260
+ marginTop: '-1px'
261
+ },
262
+ '.branch-sub-name-version': {
263
+ width: '132px'
264
+ },
265
+ '.actions': {
266
+ display: 'flex',
267
+ marginLeft: '12px'
268
+ },
269
+ '.primary-action': {
270
+ color: 'var(--mdc-theme-primary, #6200ee)'
271
+ },
272
+ '.description-line': {
273
+ display: 'flex'
22
274
  },
23
275
  '.description': {
24
276
  fontSize: '14px',
25
277
  whiteSpace: 'nowrap',
26
278
  overflow: 'hidden',
27
- textOverflow: 'ellipsis'
279
+ textOverflow: 'ellipsis',
280
+ flexGrow: '1'
281
+ },
282
+ '.description-textarea': {
283
+ width: '100%'
284
+ },
285
+ '.icon_16': {
286
+ '--mdc-icon-size': '16px',
287
+ '--mdc-icon-button-size': '24px'
288
+ },
289
+ '.icon_18': {
290
+ '--mdc-icon-size': '18px',
291
+ '--mdc-icon-button-size': '24px'
292
+ },
293
+ '.hide': {
294
+ display: 'none'
28
295
  }
29
296
  })
30
297
 
31
- template = oom.div({ class: 'mdc-card' }, oom
32
- .div({ class: 'title' }, this.getTitle())
33
- .div({ class: 'link' }, oom.a(this.options.data.url, {
34
- target: '_blank',
35
- href: this.options.data.url
36
- }))
37
- .div({ class: 'description', title: this.options.data.description }, this.options.data.description))
298
+ /**
299
+ * @param {string} str Текст для подсчета отображаемых строк
300
+ * @returns {string} Количество строк для descriptionTextarea
301
+ */
302
+ static maxRows(str) {
303
+ const rows = str.split('\n').reduce((rows, subStr) => {
304
+ return rows + (subStr || ' ').match(/.{1,63}/g)?.length
305
+ }, 0)
306
+
307
+ return String(Math.min(Math.max(1, rows), 5))
308
+ }
309
+
310
+ data = new GitBranchCardData(this.options.data)
311
+ versionsList = this.data.milestones.concat(['Произвольная'])
312
+
313
+ elements = {
314
+ /** @type {HTMLSpanElement} */
315
+ cardTitle: oom.span(this.data.branchTitle, { title: this.data.branchTitle }).dom,
316
+ /** @type {import('@material/mwc-icon-button').IconButton} */// @ts-ignore
317
+ copyLinkButton: oom.mwcIconButton({
318
+ title: 'Скопировать ссылку',
319
+ class: 'icon_18',
320
+ innerHTML: octicons.link.toSVG({ width: 18 }),
321
+ onclick: () => this.copyLink()
322
+ }).dom,
323
+ /** @type {import('@material/mwc-icon-button').IconButton} */// @ts-ignore
324
+ descriptionEdit: oom.mwcIconButton({
325
+ title: 'Редактировать описание для git commit',
326
+ class: 'icon_18',
327
+ innerHTML: octicons.pencil.toSVG({ width: 18 }),
328
+ onclick: () => this.editDescription()
329
+ }).dom,
330
+ /** @type {HTMLSpanElement} */
331
+ description: oom.span({
332
+ class: 'description',
333
+ title: this.data.description,
334
+ onclick: () => this.editDescription()
335
+ }, this.data.description).dom,
336
+ /** @type {import('@material/mwc-textarea').TextArea} */// @ts-ignore
337
+ descriptionTextarea: oom.mwcTextarea({
338
+ class: 'description-textarea hide',
339
+ rows: GitBranchCard.maxRows(this.data.description),
340
+ label: 'Описание для git commit',
341
+ value: this.data.description,
342
+ oninput: () => this.oninputDescription()
343
+ }).dom,
344
+ /** @type {import('@material/mwc-menu').Menu} */// @ts-ignore
345
+ versions: this.versionsList.reduce((mwcMenu, item) => {
346
+ return mwcMenu(oom.mwcListItem(item))
347
+ }, oom.mwcMenu({ fixed: true, activatable: true })).dom,
348
+ /** @type {import('@material/mwc-textfield').TextField} */// @ts-ignore
349
+ branchVersionTextfield: oom.mwcTextfield({
350
+ class: 'branch-sub-name branch-sub-name-version hide',
351
+ value: this.data.version,
352
+ placeholder: 'YY.XXXX',
353
+ pattern: '^\\d\\d.\\d\\d\\d\\d$',
354
+ validationmessage: 'Пример: 99.9999',
355
+ oninput: () => this.oninputBranchVersion(),
356
+ onblur: () => this.onblurBranchVersion()
357
+ }).dom,
358
+ /** @type {HTMLDivElement} */// @ts-ignore
359
+ branchButton: oom.div({
360
+ class: 'version-button',
361
+ onclick: () => this.editBranchVersion()
362
+ }, this.data.version).dom,
363
+ /** @type {HTMLSpanElement} */
364
+ branchNamePart: oom.span({
365
+ class: 'branch-part'
366
+ }, `/${this.data.branchType}/***/`).dom,
367
+ /** @type {HTMLSpanElement} */
368
+ branchSubName: oom.span({
369
+ title: this.data.branchSubName,
370
+ class: 'sub-name-button',
371
+ onclick: () => this.editBranchSubName()
372
+ }, this.data.branchSubName).dom,
373
+ /** @type {import('@material/mwc-textfield').TextField} */// @ts-ignore
374
+ branchSubNameTextfield: oom.mwcTextfield({
375
+ class: 'branch-sub-name hide',
376
+ value: this.data.branchSubName,
377
+ placeholder: this.data.branchSubName,
378
+ pattern: '^\\w+[\\w()-]*$',
379
+ validationmessage: 'Латинские буквы, цифры и _()-',
380
+ oninput: () => this.oninputBranchSubName(),
381
+ onblur: () => this.onblurBranchSubName()
382
+ }).dom,
383
+ /** @type {import('@material/mwc-icon-button').IconButton} */// @ts-ignore
384
+ copyBranchButton: oom.mwcIconButton({
385
+ title: 'Скопировать имя ветки',
386
+ class: 'primary-action',
387
+ innerHTML: octicons['git-branch'].toSVG({ width: 24 }),
388
+ onclick: () => this.copyBranchName().catch(console.error)
389
+ }).dom,
390
+ /** @type {import('@material/mwc-icon-button').IconButton} */// @ts-ignore
391
+ copyDescriptionButton: oom.mwcIconButton({
392
+ title: 'Скопировать описание',
393
+ innerHTML: octicons.copy.toSVG({ width: 24 }),
394
+ onclick: () => this.copyDescription().catch(console.error)
395
+ }).dom
396
+ }
397
+
398
+ /** @returns {string} Версия задачи (веха), выбранная пользователем */
399
+ get version() {
400
+ return this.data.version
401
+ }
38
402
 
39
403
  /**
40
- * @returns {string} Заголовок
404
+ * Обновление выбранной версии и обновление карточки задачи
405
+ *
406
+ * @param {string} value Версия
41
407
  */
42
- getTitle() {
43
- const { name } = this.options.data
44
- const docNumber = ' № ' + this.options.data.number
45
- const version = ' веха ' + this.options.data.version
46
- const date = ' от ' + this.options.data.date
47
- const author = ' ' + this.options.data.author
408
+ set version(value) {
409
+ this.data.version = value
410
+ if (this.data.version === value) {
411
+ this.elements.branchButton.textContent = this.data.version
412
+ this.elements.branchVersionTextfield.value = this.data.version
413
+ this.elements.cardTitle.textContent = this.data.branchTitle
414
+ }
415
+ }
48
416
 
49
- return name + docNumber + version + date + author
417
+ /** @returns {string} Часть имени ветки, определяемая пользователем */
418
+ get branchSubName() {
419
+ return this.data.branchSubName
420
+ }
421
+
422
+ /**
423
+ * Обновление доп. имени ветки и обновление карточки задачи
424
+ *
425
+ * @param {string} value Доп. имя ветки
426
+ */
427
+ set branchSubName(value) {
428
+ this.data.branchSubName = value
429
+ oom(this.elements.branchSubName, { innerHTML: '', title: this.data.branchSubName }, this.data.branchSubName)
430
+ if (this.elements.branchSubNameTextfield.value !== this.data.branchSubName) {
431
+ this.elements.branchSubNameTextfield.value = this.data.branchSubName
432
+ }
433
+ }
434
+
435
+ /** @returns {string} Краткое описание задачи */
436
+ get description() {
437
+ return this.data.description
438
+ }
439
+
440
+ /**
441
+ * Обновление краткого описания и карточки задачи
442
+ *
443
+ * @param {string} value Доп. имя ветки
444
+ */
445
+ set description(value) {
446
+ this.data.description = value
447
+ this.elements.description.title = this.data.description
448
+ this.elements.description.textContent = this.data.description
449
+ if (this.elements.descriptionTextarea.value !== this.data.description) {
450
+ this.elements.descriptionTextarea.value = this.data.description
451
+ }
452
+ oom(this.elements.descriptionTextarea, { rows: GitBranchCard.maxRows(this.data.description) })
453
+ }
454
+
455
+ template = () => {
456
+ this.elements.versions.anchor = this.elements.branchButton
457
+ this.elements.versions.addEventListener('selected', (/** @type {CustomEvent<import('@material/mwc-list/mwc-list-foundation').ActionDetail>} */event) => {
458
+ this.selectedVersion(event.detail.index)
459
+ })
460
+
461
+ this.updateBranchNamePart().catch(console.error)
462
+
463
+ const cardElement = oom.div({ class: 'mdc-card' }, oom
464
+ .div({ class: 'title' }, this.elements.cardTitle))
465
+
466
+ if (this.options.pk) {
467
+ // Добавим элементы для карточки задачи из истории
468
+ const useDate = this.options.useDate.toLocaleString()
469
+
470
+ cardElement(oom.div(oom
471
+ .span({ class: 'history-info' }, `Скопировано: ${useDate}`)
472
+ .mwcIconButton({
473
+ title: 'Удалить из истории',
474
+ class: 'icon_16',
475
+ innerHTML: octicons.x.toSVG({ width: 16 }),
476
+ onclick: () => {
477
+ /** @type {GitBranchCardList} */// @ts-ignore
478
+ const cardList = this.parentElement.parentElement
479
+
480
+ this.remove()
481
+ this.data.removeFromHistory(this.options.pk)
482
+ .then(() => cardList.buildHistoryList())
483
+ .catch(console.error)
484
+ }
485
+ })))
486
+ }
487
+
488
+ return cardElement(oom
489
+ .div(oom
490
+ .a({ class: 'link' }, this.data.url, {
491
+ target: '_blank',
492
+ href: this.data.url
493
+ }), this.elements.copyLinkButton)
494
+ .div({
495
+ class: 'description-line'
496
+ }, this.elements.descriptionEdit, this.elements.description, this.elements.descriptionTextarea)
497
+ .div({ class: 'branch-line' }, oom
498
+ .div({ class: 'branch' },
499
+ this.elements.branchButton,
500
+ this.elements.versions,
501
+ this.elements.branchVersionTextfield,
502
+ this.elements.branchNamePart,
503
+ this.elements.branchSubName,
504
+ this.elements.branchSubNameTextfield)
505
+ .div({ class: 'actions' }, this.elements.copyBranchButton, this.elements.copyDescriptionButton)
506
+ )
507
+ )
508
+ }
509
+
510
+ /** Обновляет имя ветки после получения данных пользователя */
511
+ async updateBranchNamePart() {
512
+ const userLogin = await context.userLogin
513
+
514
+ this.elements.branchNamePart.textContent = `/${this.data.branchType}/${userLogin}/`
515
+ }
516
+
517
+ /** Открывает форму редактирования редактирование описания для коммита */
518
+ editDescription() {
519
+ this.elements.descriptionEdit.classList.toggle('hide')
520
+ this.elements.description.classList.toggle('hide')
521
+ this.elements.descriptionTextarea.classList.toggle('hide')
522
+ this.elements.descriptionTextarea.focus()
523
+ }
524
+
525
+ /** Обработка события при вводе текста в описание для расчета отображаемого кол-ва строк */
526
+ oninputDescription() {
527
+ this.description = this.elements.descriptionTextarea.value
528
+ }
529
+
530
+ /**
531
+ * Обработчик выбора версии из меню
532
+ *
533
+ * @param {number} index Выбранная в меню позиция
534
+ */
535
+ selectedVersion(index) {
536
+ const version = this.versionsList[index]
537
+
538
+ if (version === 'Произвольная') {
539
+ this.elements.branchVersionTextfield.classList.toggle('hide')
540
+ this.elements.branchButton.classList.toggle('hide')
541
+ this.elements.branchVersionTextfield.focus()
542
+ this.elements.branchVersionTextfield.select()
543
+ } else {
544
+ this.version = version
545
+ }
546
+ }
547
+
548
+ /** Открывает форму редактирования версии ветки */
549
+ editBranchVersion() {
550
+ if (this.data.milestones.length === 0) {
551
+ this.elements.branchVersionTextfield.classList.toggle('hide')
552
+ this.elements.branchButton.classList.toggle('hide')
553
+ this.elements.branchVersionTextfield.focus()
554
+ this.elements.branchVersionTextfield.select()
555
+ } else {
556
+ this.elements.versions.open = true
557
+ }
558
+ }
559
+
560
+ /** Сохранение данных о версии ветки при вводе */
561
+ oninputBranchVersion() {
562
+ if (this.elements.branchVersionTextfield.checkValidity()) {
563
+ this.version = this.elements.branchVersionTextfield.value
564
+ }
565
+ }
566
+
567
+ /** Скрывает поле ввода версии ветки при потере фокуса */
568
+ onblurBranchVersion() {
569
+ if (this.elements.branchVersionTextfield.checkValidity()) {
570
+ this.elements.branchVersionTextfield.classList.toggle('hide')
571
+ this.elements.branchButton.classList.toggle('hide')
572
+ this.version = this.elements.branchVersionTextfield.value
573
+ }
574
+ }
575
+
576
+ /** Открывает форму редактирования доп. имени ветки */
577
+ editBranchSubName() {
578
+ this.elements.branchSubName.classList.toggle('hide')
579
+ this.elements.branchSubNameTextfield.classList.toggle('hide')
580
+ this.elements.branchSubNameTextfield.focus()
581
+ this.elements.branchSubNameTextfield.select()
582
+ }
583
+
584
+ /** Сохранение данных о доп. имени ветки при вводе */
585
+ oninputBranchSubName() {
586
+ if (this.elements.branchSubNameTextfield.checkValidity()) {
587
+ this.branchSubName = this.elements.branchSubNameTextfield.value
588
+ }
589
+ }
590
+
591
+ /** Скрывает поле ввода доп. имени ветки при потере фокуса */
592
+ onblurBranchSubName() {
593
+ if (this.elements.branchSubNameTextfield.checkValidity()) {
594
+ this.elements.branchSubName.classList.toggle('hide')
595
+ this.elements.branchSubNameTextfield.classList.toggle('hide')
596
+ }
597
+ }
598
+
599
+ /** Копирование ссылки на задачу */
600
+ copyLink() {
601
+ this.data.copyLink()
602
+ }
603
+
604
+ /** Копирование имени ветки */
605
+ async copyBranchName() {
606
+ const valid = this.elements.branchSubNameTextfield.checkValidity() &&
607
+ this.elements.branchVersionTextfield.checkValidity()
608
+
609
+ if (valid) {
610
+ await this.data.copyBranchName()
611
+ }
612
+ }
613
+
614
+ /** Копирование описания для git commit */
615
+ async copyDescription() {
616
+ const valid = this.elements.branchSubNameTextfield.checkValidity() &&
617
+ this.elements.branchVersionTextfield.checkValidity()
618
+
619
+ if (valid) {
620
+ await this.data.copyDescription()
621
+ }
622
+ }
623
+
624
+ /** Восстановление из истории последнего сохраненного состояния карточки */
625
+ async restoreFromHistory() {
626
+ await this.data.restoreFromHistory()
627
+ this.version = this.data.version
628
+ this.branchSubName = this.data.branchSubName
629
+ this.description = this.data.description
50
630
  }
51
631
 
52
632
  }
@@ -59,35 +639,185 @@ class GitBranchCardList extends oom.extends(HTMLElement) {
59
639
  static style = oom.style({
60
640
  'display': 'contents',
61
641
  '.empty': {
62
- padding: '100px',
642
+ padding: '50px',
63
643
  textAlign: 'center'
644
+ },
645
+ '.content': {
646
+ flexGrow: '1',
647
+ overflowY: 'auto',
648
+ overflowX: 'hidden',
649
+ maxHeight: 'calc(var(--mdc-dialog-max-height) - 100px)',
650
+ background: 'var(--mdc-theme-background)'
651
+ },
652
+ '.hide': {
653
+ display: 'none'
654
+ },
655
+ '.nav-buttons': {
656
+ display: 'flex',
657
+ justifyContent: 'space-between',
658
+ alignItems: 'center',
659
+ padding: '4px 4px 0px',
660
+ height: '36px'
661
+ },
662
+ '.nav-buttons-space': {
663
+ width: '64px'
664
+ },
665
+ '.nav-buttons-page-label': {
666
+ userSelect: 'none'
64
667
  }
65
668
  })
66
669
 
67
- taskList = getListOfOpenCards({
68
- names: ['Ошибка', 'Задача'],
69
- hasVersion: true
70
- })
71
- .map(card => new GitBranchCard(card))
670
+ tabs = oom
671
+ .mwcTabBar(oom
672
+ .mwcTab({ label: 'Открытые задачи' })
673
+ // .mwcTab({ label: 'Список задач' })
674
+ .mwcTab({ label: 'История задач' }))
675
+
676
+ openTaskList = oom.div({ class: 'content' }, oom.div({ class: 'empty' }, 'Загрузка ...'))
677
+ historyTaskList = oom.div({ class: 'content hide' })
72
678
 
73
- taskListElement = (this.taskList.length > 0 && oom()(...this.taskList)) || oom
74
- .div({ class: 'empty' }, 'Открытых задач с вехой не найдено')
679
+ // Параметры навигации по истории
680
+ historyListPage = 0
681
+ historyListLimit = 3
75
682
 
76
- template = oom()(
77
- oom
78
- .mwcTabBar(oom
79
- .mwcTab({ label: 'Открытые задачи' })
80
- .mwcTab({ label: 'История задач' })),
81
- this.taskListElement
82
- )
683
+ template = async () => {
684
+ // if (this.openTaskList.length === 0) {
685
+ // this.tabs({ activeindex: '1' })
686
+ // }
687
+ oom(this, this.tabs, this.openTaskList, this.historyTaskList)
688
+
689
+ this.tabs.dom.addEventListener('MDCTabBar:activated', (/** @type {CustomEvent<import('@material/mwc-list/mwc-list-foundation').ActionDetail>} */event) => {
690
+ switch (event.detail.index) {
691
+ case 0:
692
+ this.historyTaskList({ innerHTML: '' })
693
+ this.historyTaskList.dom.classList.add('hide')
694
+ this.openTaskList.dom.classList.remove('hide')
695
+ break
696
+ case 1:
697
+ this.historyTaskList({ innerHTML: '' }, oom.div({ class: 'empty' }, 'Загрузка ...'))
698
+ this.openTaskList.dom.classList.add('hide')
699
+ this.historyTaskList.dom.classList.remove('hide')
700
+ this.historyListPage = 0
701
+ this.historyListLimit = 3
702
+ this.buildHistoryList().catch(console.error)
703
+ break
704
+ }
705
+ })
706
+
707
+ this.openTaskList({ innerHTML: '' }, await this.buildTaskList())
708
+ }
709
+
710
+ /** @returns {Promise<HTMLElement|DocumentFragment>} Вернет готовый список задач с учетом аинхронной загрузки данных */
711
+ async buildTaskList() {
712
+ let taskTist = null
713
+ const openTaskList = (await getListOfOpenCards({
714
+ names: ['Ошибка', 'Задача']
715
+ })).map(card => new GitBranchCard(card))
716
+
717
+ await Promise.all(openTaskList.map(card => card.restoreFromHistory()))
718
+
719
+ if (openTaskList.length > 0) {
720
+ taskTist = oom()(...openTaskList)
721
+ } else {
722
+ taskTist = oom.div({ class: 'empty' }, 'Открытых задач не найдено')
723
+ }
724
+
725
+ return taskTist.dom
726
+ }
727
+
728
+ /** Строит компонент списка истории задач */
729
+ async buildHistoryList() {
730
+ const offset = this.historyListPage * this.historyListLimit
731
+ const navButtons = oom.div({ class: 'nav-buttons' })
732
+ const gitBranchHistory = db.table('gitBranchHistory')
733
+ const historyData = await gitBranchHistory
734
+ .orderBy('useDate')
735
+ .reverse()
736
+ .offset(offset)
737
+ .limit(this.historyListLimit + 1)
738
+ .toArray()
739
+
740
+ if (historyData.length > 0) {
741
+ const totalCount = await gitBranchHistory.count()
742
+ const currentSize = offset + Math.min(this.historyListLimit, historyData.length)
743
+
744
+ if (this.historyListPage > 0) {
745
+ navButtons(oom.mwcButton({
746
+ innerHTML: octicons['arrow-left'].toSVG({ width: 24 }),
747
+ onclick: () => {
748
+ --this.historyListPage
749
+ this.buildHistoryList().catch(console.error)
750
+ }
751
+ }))
752
+ } else {
753
+ navButtons(oom.span({ class: 'nav-buttons-space' }))
754
+ }
755
+ navButtons(oom.span(`${offset + 1} - ${currentSize} / ${totalCount}`, {
756
+ class: 'nav-buttons-page-label'
757
+ }))
758
+ if (historyData.length > this.historyListLimit) {
759
+ historyData.pop()
760
+ navButtons(oom.mwcButton({
761
+ innerHTML: octicons['arrow-right'].toSVG({ width: 24 }),
762
+ onclick: () => {
763
+ ++this.historyListPage
764
+ this.buildHistoryList().catch(console.error)
765
+ }
766
+ }))
767
+ } else {
768
+ navButtons(oom.span({ class: 'nav-buttons-space' }))
769
+ }
770
+
771
+ this.historyTaskList({ innerHTML: '' }, navButtons, ...historyData.map(card => new GitBranchCard(card)))
772
+ } else if (this.historyListPage > 0) {
773
+ --this.historyListPage
774
+ this.buildHistoryList().catch(console.error)
775
+ } else {
776
+ this.historyTaskList({ innerHTML: '' }, oom.div({ class: 'empty' }, 'Задач в истории не найдено'))
777
+ }
778
+ }
83
779
 
84
780
  }
85
781
 
86
782
 
87
783
  oom.define(GitBranchCard, GitBranchCardList)
88
784
 
89
- keysMapping['alt-KeyB'] = () => toggleDialog({
785
+ keysMapping['alt-KeyB'] = () => PopupDialog.toggle({
90
786
  key: 'git-branch-dialog',
91
787
  title: 'Ветки для Git',
92
788
  Element: GitBranchCardList
93
789
  })
790
+
791
+ toolbar.addButton({
792
+ position: 'right',
793
+ title: 'Ветки для Git',
794
+ iconSVG: octicons['git-branch'].toSVG({ width: 24 }),
795
+ onclick: keysMapping['alt-KeyB']
796
+ })
797
+
798
+ toolbar.addButton({
799
+ position: 'top',
800
+ filter: { names: ['Ошибка', 'Задача'] },
801
+ title: 'Скопировать имя ветки для Git',
802
+ iconSVG: octicons['git-branch'].toSVG({ width: 18 }),
803
+ onclick: card => {
804
+ const gitCard = new GitBranchCardData(card.data)
805
+
806
+ gitCard.restoreFromHistory()
807
+ .then(() => gitCard.copyBranchName())
808
+ .catch(console.error)
809
+ }
810
+ })
811
+ toolbar.addButton({
812
+ position: 'top',
813
+ filter: { names: ['Ошибка', 'Задача'] },
814
+ title: 'Скопировать описание для Git коммита',
815
+ iconSVG: octicons['git-commit'].toSVG({ width: 18 }),
816
+ onclick: card => {
817
+ const gitCard = new GitBranchCardData(card.data)
818
+
819
+ gitCard.restoreFromHistory()
820
+ .then(() => gitCard.copyDescription())
821
+ .catch(console.error)
822
+ }
823
+ })