saby-customizer 0.0.0-pre.9 → 0.0.1

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