@masterteam/workspace-builder 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.
@@ -0,0 +1,1310 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, Injectable, computed, input, signal, Component, effect, ChangeDetectionStrategy } from '@angular/core';
4
+ import { toSignal } from '@angular/core/rxjs-interop';
5
+ import * as i3 from '@jsverse/transloco';
6
+ import { TranslocoModule, TranslocoService, TranslocoDirective } from '@jsverse/transloco';
7
+ import * as i1 from '@angular/forms';
8
+ import { FormsModule } from '@angular/forms';
9
+ import { ConfirmationService } from '@masterteam/components/confirmation';
10
+ import { DynamicDialogConfig, ModalRef, DialogService } from '@masterteam/components/dialog';
11
+ import { ModalService } from '@masterteam/components/modal';
12
+ import { Page } from '@masterteam/components/page';
13
+ import { Card } from '@masterteam/components/card';
14
+ import { TextField } from '@masterteam/components/text-field';
15
+ import { ToggleField } from '@masterteam/components/toggle-field';
16
+ import { ToastService } from '@masterteam/components/toast';
17
+ import * as i3$1 from 'primeng/contextmenu';
18
+ import { ContextMenuModule } from 'primeng/contextmenu';
19
+ import * as i2$1 from 'primeng/dragdrop';
20
+ import { DragDropModule } from 'primeng/dragdrop';
21
+ import { finalize } from 'rxjs';
22
+ import { Action, Selector, State, Store, select } from '@ngxs/store';
23
+ import { HttpClient } from '@angular/common/http';
24
+ import { CrudStateBase, handleApiRequest } from '@masterteam/components';
25
+ import { Button } from '@masterteam/components/button';
26
+ import * as i2 from 'primeng/inputtext';
27
+ import { InputTextModule } from 'primeng/inputtext';
28
+
29
+ class SetWorkspaceBuilderLevelId {
30
+ levelId;
31
+ static type = '[WorkspaceBuilder] Set Level ID';
32
+ constructor(levelId) {
33
+ this.levelId = levelId;
34
+ }
35
+ }
36
+ class GetWorkspaceBuilderLayout {
37
+ static type = '[WorkspaceBuilder] Get Layout';
38
+ }
39
+ class PatchWorkspaceBuilderLayoutLocally {
40
+ layout;
41
+ static type = '[WorkspaceBuilder] Patch Layout Locally';
42
+ constructor(layout) {
43
+ this.layout = layout;
44
+ }
45
+ }
46
+ class SaveWorkspaceBuilderLayout {
47
+ static type = '[WorkspaceBuilder] Save Layout';
48
+ }
49
+ class CreateWorkspaceBuilderGroup {
50
+ title;
51
+ area;
52
+ order;
53
+ static type = '[WorkspaceBuilder] Create Group';
54
+ constructor(title, area = 'main', order) {
55
+ this.title = title;
56
+ this.area = area;
57
+ this.order = order;
58
+ }
59
+ }
60
+ class RenameWorkspaceBuilderGroup {
61
+ groupId;
62
+ title;
63
+ static type = '[WorkspaceBuilder] Rename Group';
64
+ constructor(groupId, title) {
65
+ this.groupId = groupId;
66
+ this.title = title;
67
+ }
68
+ }
69
+ class DeleteWorkspaceBuilderGroup {
70
+ groupId;
71
+ static type = '[WorkspaceBuilder] Delete Group';
72
+ constructor(groupId) {
73
+ this.groupId = groupId;
74
+ }
75
+ }
76
+ class ResetWorkspaceBuilderState {
77
+ static type = '[WorkspaceBuilder] Reset State';
78
+ }
79
+
80
+ var WorkspaceBuilderActionKey;
81
+ (function (WorkspaceBuilderActionKey) {
82
+ WorkspaceBuilderActionKey["GetLayout"] = "getLayout";
83
+ WorkspaceBuilderActionKey["SaveLayout"] = "saveLayout";
84
+ WorkspaceBuilderActionKey["CreateGroup"] = "createGroup";
85
+ WorkspaceBuilderActionKey["UpdateGroup"] = "updateGroup";
86
+ WorkspaceBuilderActionKey["DeleteGroup"] = "deleteGroup";
87
+ })(WorkspaceBuilderActionKey || (WorkspaceBuilderActionKey = {}));
88
+
89
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
90
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
91
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
92
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
93
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
94
+ };
95
+ const AREAS = ['main', 'more'];
96
+ function normalizeName(value) {
97
+ if (typeof value === 'string') {
98
+ return value;
99
+ }
100
+ if (value && typeof value === 'object') {
101
+ const localized = value;
102
+ const display = localized['display'];
103
+ if (typeof display === 'string' && display.trim().length > 0) {
104
+ return display;
105
+ }
106
+ const en = localized['en'];
107
+ if (typeof en === 'string' && en.trim().length > 0) {
108
+ return en;
109
+ }
110
+ const ar = localized['ar'];
111
+ if (typeof ar === 'string' && ar.trim().length > 0) {
112
+ return ar;
113
+ }
114
+ }
115
+ return '';
116
+ }
117
+ function createEmptyLayout(levelId = 0) {
118
+ return {
119
+ levelId,
120
+ main: [],
121
+ more: [],
122
+ };
123
+ }
124
+ function cloneLayout(layout) {
125
+ return {
126
+ levelId: layout.levelId,
127
+ main: layout.main.map(cloneItem),
128
+ more: layout.more.map(cloneItem),
129
+ };
130
+ }
131
+ function cloneItem(item) {
132
+ if (item.type === 'group') {
133
+ return {
134
+ type: 'group',
135
+ id: item.id,
136
+ name: item.name,
137
+ order: item.order,
138
+ modules: item.modules.map((module) => ({
139
+ id: module.id,
140
+ name: module.name,
141
+ order: module.order,
142
+ })),
143
+ };
144
+ }
145
+ return {
146
+ type: 'module',
147
+ id: item.id,
148
+ name: item.name,
149
+ order: item.order,
150
+ };
151
+ }
152
+ function normalizeGroupModules(modules) {
153
+ if (!Array.isArray(modules)) {
154
+ return [];
155
+ }
156
+ return modules.map((module, index) => ({
157
+ id: String(module?.id ?? ''),
158
+ name: normalizeName(module?.name),
159
+ order: index + 1,
160
+ }));
161
+ }
162
+ function normalizeAreaItems(items) {
163
+ if (!Array.isArray(items)) {
164
+ return [];
165
+ }
166
+ return items.map((item, index) => {
167
+ if (item?.type === 'group') {
168
+ const group = {
169
+ type: 'group',
170
+ id: String(item?.id ?? ''),
171
+ name: normalizeName(item?.name),
172
+ order: index + 1,
173
+ modules: normalizeGroupModules(item?.modules ?? []),
174
+ };
175
+ return group;
176
+ }
177
+ const moduleItem = {
178
+ type: 'module',
179
+ id: String(item?.id ?? ''),
180
+ name: normalizeName(item?.name),
181
+ order: index + 1,
182
+ };
183
+ return moduleItem;
184
+ });
185
+ }
186
+ function normalizeLayout(layout) {
187
+ const normalized = {
188
+ levelId: layout?.levelId ?? 0,
189
+ main: normalizeAreaItems(layout?.main ?? []),
190
+ more: normalizeAreaItems(layout?.more ?? []),
191
+ };
192
+ return normalized;
193
+ }
194
+ function toSaveLayoutDto(layout) {
195
+ return {
196
+ main: layout.main.map((item, index) => {
197
+ if (item.type === 'group') {
198
+ return {
199
+ type: 'group',
200
+ id: item.id,
201
+ order: index + 1,
202
+ modules: item.modules.map((module, moduleIndex) => ({
203
+ id: module.id,
204
+ order: moduleIndex + 1,
205
+ })),
206
+ };
207
+ }
208
+ return {
209
+ type: 'module',
210
+ id: item.id,
211
+ order: index + 1,
212
+ };
213
+ }),
214
+ more: layout.more.map((item, index) => {
215
+ if (item.type === 'group') {
216
+ return {
217
+ type: 'group',
218
+ id: item.id,
219
+ order: index + 1,
220
+ modules: item.modules.map((module, moduleIndex) => ({
221
+ id: module.id,
222
+ order: moduleIndex + 1,
223
+ })),
224
+ };
225
+ }
226
+ return {
227
+ type: 'module',
228
+ id: item.id,
229
+ order: index + 1,
230
+ };
231
+ }),
232
+ };
233
+ }
234
+ function hasDuplicate(values) {
235
+ return new Set(values).size !== values.length;
236
+ }
237
+ function validateLayout(layout) {
238
+ const errors = [];
239
+ const moduleIds = [];
240
+ const groupIds = [];
241
+ for (const area of AREAS) {
242
+ const items = layout[area];
243
+ const orders = items.map((item) => item.order);
244
+ if (hasDuplicate(orders.map(String))) {
245
+ errors.push(`Duplicate order detected in ${area} list.`);
246
+ }
247
+ items.forEach((item) => {
248
+ if (item.type === 'group') {
249
+ groupIds.push(item.id);
250
+ const groupOrders = item.modules.map((module) => module.order);
251
+ if (hasDuplicate(groupOrders.map(String))) {
252
+ errors.push(`Duplicate order detected in group ${item.id}.`);
253
+ }
254
+ item.modules.forEach((module) => {
255
+ const typedModule = module;
256
+ if (typedModule?.type === 'group' ||
257
+ Array.isArray(typedModule?.modules)) {
258
+ errors.push(`Nested groups are not supported (group ${item.id}).`);
259
+ }
260
+ moduleIds.push(module.id);
261
+ });
262
+ }
263
+ else {
264
+ moduleIds.push(item.id);
265
+ }
266
+ });
267
+ }
268
+ if (hasDuplicate(moduleIds)) {
269
+ errors.push('Duplicate module IDs detected across main, more, and groups.');
270
+ }
271
+ if (hasDuplicate(groupIds)) {
272
+ errors.push('Duplicate group IDs detected in layout.');
273
+ }
274
+ return Array.from(new Set(errors));
275
+ }
276
+ function computeIsDirty(layout, originalLayout) {
277
+ if (!originalLayout) {
278
+ return true;
279
+ }
280
+ return (JSON.stringify(toSaveLayoutDto(layout)) !== JSON.stringify(originalLayout));
281
+ }
282
+ function insertGroupIntoLayout(layout, groupId, title, area, order) {
283
+ const next = cloneLayout(layout);
284
+ const list = next[area];
285
+ const insertIndex = Math.max(0, Math.min((order ?? list.length + 1) - 1, list.length));
286
+ const group = {
287
+ type: 'group',
288
+ id: groupId,
289
+ name: title,
290
+ order: 0,
291
+ modules: [],
292
+ };
293
+ list.splice(insertIndex, 0, group);
294
+ return normalizeLayout(next);
295
+ }
296
+ function renameGroupInLayout(layout, groupId, title) {
297
+ const next = cloneLayout(layout);
298
+ for (const area of AREAS) {
299
+ const group = next[area].find((item) => item.type === 'group' && item.id === groupId);
300
+ if (group) {
301
+ group.name = title;
302
+ break;
303
+ }
304
+ }
305
+ return normalizeLayout(next);
306
+ }
307
+ function removeGroupAndPromoteModules(layout, groupId) {
308
+ const next = cloneLayout(layout);
309
+ for (const area of AREAS) {
310
+ const index = next[area].findIndex((item) => item.type === 'group' && item.id === groupId);
311
+ if (index === -1) {
312
+ continue;
313
+ }
314
+ const group = next[area][index];
315
+ const promotedModules = group.modules.map((module) => ({
316
+ type: 'module',
317
+ id: module.id,
318
+ name: module.name,
319
+ order: 0,
320
+ }));
321
+ next[area].splice(index, 1, ...promotedModules);
322
+ break;
323
+ }
324
+ return normalizeLayout(next);
325
+ }
326
+ const DEFAULTS = {
327
+ levelId: null,
328
+ layout: createEmptyLayout(),
329
+ originalLayout: null,
330
+ validationErrors: [],
331
+ isDirty: false,
332
+ successMessages: {},
333
+ loadingActive: [],
334
+ errors: {},
335
+ };
336
+ let WorkspaceBuilderState = class WorkspaceBuilderState extends CrudStateBase {
337
+ http = inject(HttpClient);
338
+ static getLevelId(state) {
339
+ return state.levelId;
340
+ }
341
+ static getLayout(state) {
342
+ return state.layout;
343
+ }
344
+ static getMainItems(state) {
345
+ return state.layout.main;
346
+ }
347
+ static getMoreItems(state) {
348
+ return state.layout.more;
349
+ }
350
+ static getValidationErrors(state) {
351
+ return state.validationErrors;
352
+ }
353
+ static getIsDirty(state) {
354
+ return state.isDirty;
355
+ }
356
+ static getLoadingActive(state) {
357
+ return state.loadingActive;
358
+ }
359
+ static getErrors(state) {
360
+ return state.errors;
361
+ }
362
+ static getSuccessMessages(state) {
363
+ return state.successMessages;
364
+ }
365
+ setLevelId(ctx, { levelId }) {
366
+ const state = ctx.getState();
367
+ if (state.levelId === levelId) {
368
+ return;
369
+ }
370
+ ctx.patchState({
371
+ levelId,
372
+ layout: createEmptyLayout(levelId),
373
+ originalLayout: null,
374
+ validationErrors: [],
375
+ isDirty: false,
376
+ successMessages: {},
377
+ });
378
+ }
379
+ getLayout(ctx) {
380
+ const { levelId } = ctx.getState();
381
+ if (!levelId) {
382
+ return;
383
+ }
384
+ const req$ = this.http.get(`levels/${levelId}/workspaceBuilder`);
385
+ return handleApiRequest({
386
+ ctx,
387
+ key: WorkspaceBuilderActionKey.GetLayout,
388
+ request$: req$,
389
+ onSuccess: (response) => {
390
+ const incoming = response.data
391
+ ? { ...response.data, levelId }
392
+ : createEmptyLayout(levelId);
393
+ const normalized = normalizeLayout(incoming);
394
+ const validationErrors = validateLayout(normalized);
395
+ return {
396
+ layout: normalized,
397
+ originalLayout: toSaveLayoutDto(normalized),
398
+ validationErrors,
399
+ isDirty: false,
400
+ };
401
+ },
402
+ });
403
+ }
404
+ patchLayoutLocally(ctx, { layout }) {
405
+ const state = ctx.getState();
406
+ const normalized = normalizeLayout({
407
+ ...layout,
408
+ levelId: state.levelId ?? layout.levelId,
409
+ });
410
+ const validationErrors = validateLayout(normalized);
411
+ ctx.patchState({
412
+ layout: normalized,
413
+ validationErrors,
414
+ isDirty: computeIsDirty(normalized, state.originalLayout),
415
+ });
416
+ }
417
+ saveLayout(ctx) {
418
+ const state = ctx.getState();
419
+ const { levelId } = state;
420
+ if (!levelId) {
421
+ return;
422
+ }
423
+ const normalized = normalizeLayout({
424
+ ...state.layout,
425
+ levelId,
426
+ });
427
+ const validationErrors = validateLayout(normalized);
428
+ if (validationErrors.length > 0) {
429
+ ctx.patchState({
430
+ validationErrors,
431
+ });
432
+ return;
433
+ }
434
+ const payload = toSaveLayoutDto(normalized);
435
+ const req$ = this.http.put(`levels/${levelId}/workspaceBuilder`, payload);
436
+ return handleApiRequest({
437
+ ctx,
438
+ key: WorkspaceBuilderActionKey.SaveLayout,
439
+ request$: req$,
440
+ onSuccess: (response, state) => ({
441
+ layout: normalized,
442
+ originalLayout: payload,
443
+ validationErrors: [],
444
+ isDirty: false,
445
+ successMessages: {
446
+ ...state.successMessages,
447
+ [WorkspaceBuilderActionKey.SaveLayout]: response.message ?? '',
448
+ },
449
+ }),
450
+ });
451
+ }
452
+ createGroup(ctx, { title, area, order }) {
453
+ const { levelId } = ctx.getState();
454
+ if (!levelId) {
455
+ return;
456
+ }
457
+ const req$ = this.http.post(`levels/${levelId}/workspaceBuilder/groups`, {
458
+ title,
459
+ area,
460
+ order: order ?? 1,
461
+ });
462
+ return handleApiRequest({
463
+ ctx,
464
+ key: WorkspaceBuilderActionKey.CreateGroup,
465
+ request$: req$,
466
+ onSuccess: (response, state) => {
467
+ const groupId = response.data?.groupId;
468
+ if (!groupId) {
469
+ return {};
470
+ }
471
+ const nextLayout = insertGroupIntoLayout(state.layout, groupId, title, area, order);
472
+ const validationErrors = validateLayout(nextLayout);
473
+ return {
474
+ layout: nextLayout,
475
+ validationErrors,
476
+ isDirty: computeIsDirty(nextLayout, state.originalLayout),
477
+ successMessages: {
478
+ ...state.successMessages,
479
+ [WorkspaceBuilderActionKey.CreateGroup]: response.message ?? '',
480
+ },
481
+ };
482
+ },
483
+ });
484
+ }
485
+ renameGroup(ctx, { groupId, title }) {
486
+ const { levelId } = ctx.getState();
487
+ if (!levelId) {
488
+ return;
489
+ }
490
+ const req$ = this.http.patch(`levels/${levelId}/workspaceBuilder/groups/${groupId}`, {
491
+ title,
492
+ });
493
+ return handleApiRequest({
494
+ ctx,
495
+ key: WorkspaceBuilderActionKey.UpdateGroup,
496
+ request$: req$,
497
+ onSuccess: (response, state) => {
498
+ const nextLayout = renameGroupInLayout(state.layout, groupId, title);
499
+ const validationErrors = validateLayout(nextLayout);
500
+ return {
501
+ layout: nextLayout,
502
+ validationErrors,
503
+ isDirty: computeIsDirty(nextLayout, state.originalLayout),
504
+ successMessages: {
505
+ ...state.successMessages,
506
+ [WorkspaceBuilderActionKey.UpdateGroup]: response.message ?? '',
507
+ },
508
+ };
509
+ },
510
+ });
511
+ }
512
+ deleteGroup(ctx, { groupId }) {
513
+ const { levelId } = ctx.getState();
514
+ if (!levelId) {
515
+ return;
516
+ }
517
+ const req$ = this.http.delete(`levels/${levelId}/workspaceBuilder/groups/${groupId}`);
518
+ return handleApiRequest({
519
+ ctx,
520
+ key: WorkspaceBuilderActionKey.DeleteGroup,
521
+ request$: req$,
522
+ onSuccess: (response, state) => {
523
+ const nextLayout = removeGroupAndPromoteModules(state.layout, groupId);
524
+ const validationErrors = validateLayout(nextLayout);
525
+ return {
526
+ layout: nextLayout,
527
+ validationErrors,
528
+ isDirty: computeIsDirty(nextLayout, state.originalLayout),
529
+ successMessages: {
530
+ ...state.successMessages,
531
+ [WorkspaceBuilderActionKey.DeleteGroup]: response.message ?? '',
532
+ },
533
+ };
534
+ },
535
+ });
536
+ }
537
+ resetState(ctx) {
538
+ ctx.setState(DEFAULTS);
539
+ }
540
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: WorkspaceBuilderState, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
541
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: WorkspaceBuilderState });
542
+ };
543
+ __decorate([
544
+ Action(SetWorkspaceBuilderLevelId)
545
+ ], WorkspaceBuilderState.prototype, "setLevelId", null);
546
+ __decorate([
547
+ Action(GetWorkspaceBuilderLayout)
548
+ ], WorkspaceBuilderState.prototype, "getLayout", null);
549
+ __decorate([
550
+ Action(PatchWorkspaceBuilderLayoutLocally)
551
+ ], WorkspaceBuilderState.prototype, "patchLayoutLocally", null);
552
+ __decorate([
553
+ Action(SaveWorkspaceBuilderLayout)
554
+ ], WorkspaceBuilderState.prototype, "saveLayout", null);
555
+ __decorate([
556
+ Action(CreateWorkspaceBuilderGroup)
557
+ ], WorkspaceBuilderState.prototype, "createGroup", null);
558
+ __decorate([
559
+ Action(RenameWorkspaceBuilderGroup)
560
+ ], WorkspaceBuilderState.prototype, "renameGroup", null);
561
+ __decorate([
562
+ Action(DeleteWorkspaceBuilderGroup)
563
+ ], WorkspaceBuilderState.prototype, "deleteGroup", null);
564
+ __decorate([
565
+ Action(ResetWorkspaceBuilderState)
566
+ ], WorkspaceBuilderState.prototype, "resetState", null);
567
+ __decorate([
568
+ Selector()
569
+ ], WorkspaceBuilderState, "getLevelId", null);
570
+ __decorate([
571
+ Selector()
572
+ ], WorkspaceBuilderState, "getLayout", null);
573
+ __decorate([
574
+ Selector()
575
+ ], WorkspaceBuilderState, "getMainItems", null);
576
+ __decorate([
577
+ Selector()
578
+ ], WorkspaceBuilderState, "getMoreItems", null);
579
+ __decorate([
580
+ Selector()
581
+ ], WorkspaceBuilderState, "getValidationErrors", null);
582
+ __decorate([
583
+ Selector()
584
+ ], WorkspaceBuilderState, "getIsDirty", null);
585
+ __decorate([
586
+ Selector()
587
+ ], WorkspaceBuilderState, "getLoadingActive", null);
588
+ __decorate([
589
+ Selector()
590
+ ], WorkspaceBuilderState, "getErrors", null);
591
+ __decorate([
592
+ Selector()
593
+ ], WorkspaceBuilderState, "getSuccessMessages", null);
594
+ WorkspaceBuilderState = __decorate([
595
+ State({
596
+ name: 'workspaceBuilder',
597
+ defaults: DEFAULTS,
598
+ })
599
+ ], WorkspaceBuilderState);
600
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: WorkspaceBuilderState, decorators: [{
601
+ type: Injectable
602
+ }], propDecorators: { setLevelId: [], getLayout: [], patchLayoutLocally: [], saveLayout: [], createGroup: [], renameGroup: [], deleteGroup: [], resetState: [] } });
603
+
604
+ class WorkspaceBuilderFacade {
605
+ store = inject(Store);
606
+ levelId = select(WorkspaceBuilderState.getLevelId);
607
+ layout = select(WorkspaceBuilderState.getLayout);
608
+ mainItems = select(WorkspaceBuilderState.getMainItems);
609
+ moreItems = select(WorkspaceBuilderState.getMoreItems);
610
+ validationErrors = select(WorkspaceBuilderState.getValidationErrors);
611
+ isDirty = select(WorkspaceBuilderState.getIsDirty);
612
+ successMessages = select(WorkspaceBuilderState.getSuccessMessages);
613
+ loadingActive = select(WorkspaceBuilderState.getLoadingActive);
614
+ errors = select(WorkspaceBuilderState.getErrors);
615
+ isLoadingLayout = computed(() => this.loadingActive().includes(WorkspaceBuilderActionKey.GetLayout), ...(ngDevMode ? [{ debugName: "isLoadingLayout" }] : []));
616
+ isSavingLayout = computed(() => this.loadingActive().includes(WorkspaceBuilderActionKey.SaveLayout), ...(ngDevMode ? [{ debugName: "isSavingLayout" }] : []));
617
+ isCreatingGroup = computed(() => this.loadingActive().includes(WorkspaceBuilderActionKey.CreateGroup), ...(ngDevMode ? [{ debugName: "isCreatingGroup" }] : []));
618
+ isUpdatingGroup = computed(() => this.loadingActive().includes(WorkspaceBuilderActionKey.UpdateGroup), ...(ngDevMode ? [{ debugName: "isUpdatingGroup" }] : []));
619
+ isDeletingGroup = computed(() => this.loadingActive().includes(WorkspaceBuilderActionKey.DeleteGroup), ...(ngDevMode ? [{ debugName: "isDeletingGroup" }] : []));
620
+ layoutError = computed(() => this.errors()[WorkspaceBuilderActionKey.GetLayout] ?? null, ...(ngDevMode ? [{ debugName: "layoutError" }] : []));
621
+ saveError = computed(() => this.errors()[WorkspaceBuilderActionKey.SaveLayout] ?? null, ...(ngDevMode ? [{ debugName: "saveError" }] : []));
622
+ getSuccessMessage(key) {
623
+ return this.successMessages()[key] ?? null;
624
+ }
625
+ getErrorMessage(key) {
626
+ return this.errors()[key] ?? null;
627
+ }
628
+ setLevelId(levelId) {
629
+ return this.store.dispatch(new SetWorkspaceBuilderLevelId(levelId));
630
+ }
631
+ getLayout() {
632
+ return this.store.dispatch(new GetWorkspaceBuilderLayout());
633
+ }
634
+ patchLayout(layout) {
635
+ return this.store.dispatch(new PatchWorkspaceBuilderLayoutLocally(layout));
636
+ }
637
+ saveLayout() {
638
+ return this.store.dispatch(new SaveWorkspaceBuilderLayout());
639
+ }
640
+ createGroup(title, area = 'main', order) {
641
+ return this.store.dispatch(new CreateWorkspaceBuilderGroup(title, area, order));
642
+ }
643
+ renameGroup(groupId, title) {
644
+ return this.store.dispatch(new RenameWorkspaceBuilderGroup(groupId, title));
645
+ }
646
+ deleteGroup(groupId) {
647
+ return this.store.dispatch(new DeleteWorkspaceBuilderGroup(groupId));
648
+ }
649
+ resetState() {
650
+ return this.store.dispatch(new ResetWorkspaceBuilderState());
651
+ }
652
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: WorkspaceBuilderFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
653
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: WorkspaceBuilderFacade, providedIn: 'root' });
654
+ }
655
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: WorkspaceBuilderFacade, decorators: [{
656
+ type: Injectable,
657
+ args: [{ providedIn: 'root' }]
658
+ }] });
659
+
660
+ class WorkspaceBuilderCreateGroupDialog {
661
+ area = input(null, ...(ngDevMode ? [{ debugName: "area" }] : []));
662
+ title = signal('', ...(ngDevMode ? [{ debugName: "title" }] : []));
663
+ modal = inject(ModalService);
664
+ config = inject(DynamicDialogConfig, { optional: true });
665
+ ref = inject(ModalRef);
666
+ resolvedArea() {
667
+ return (this.area() ??
668
+ this.config?.data?.area ??
669
+ 'main');
670
+ }
671
+ submit() {
672
+ const trimmed = this.title().trim();
673
+ if (!trimmed) {
674
+ return;
675
+ }
676
+ this.ref.close({
677
+ area: this.resolvedArea(),
678
+ title: trimmed,
679
+ });
680
+ }
681
+ cancel() {
682
+ this.ref.close(null);
683
+ }
684
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: WorkspaceBuilderCreateGroupDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
685
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.3", type: WorkspaceBuilderCreateGroupDialog, isStandalone: true, selector: "mt-workspace-builder-create-group-dialog", inputs: { area: { classPropertyName: "area", publicName: "area", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'workspace-builder'\">\r\n <div [class]=\"'flex flex-col gap-3 p-4 ' + modal.contentClass\">\r\n <input\r\n pInputText\r\n [(ngModel)]=\"title\"\r\n [placeholder]=\"t('new-group-placeholder')\"\r\n class=\"group-title-input\"\r\n (keydown.enter)=\"submit()\"\r\n />\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n [label]=\"t('cancel')\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n\r\n <mt-button\r\n [label]=\"t('add-group')\"\r\n [disabled]=\"!title().trim()\"\r\n (click)=\"submit()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n", styles: [".group-title-input{width:100%}.group-dialog-label{color:var(--p-surface-600, #4b5563);font-size:.875rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "directive", type: i3.TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
686
+ }
687
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: WorkspaceBuilderCreateGroupDialog, decorators: [{
688
+ type: Component,
689
+ args: [{ selector: 'mt-workspace-builder-create-group-dialog', standalone: true, imports: [
690
+ CommonModule,
691
+ FormsModule,
692
+ InputTextModule,
693
+ Button,
694
+ TranslocoModule,
695
+ ], template: "<ng-container *transloco=\"let t; prefix: 'workspace-builder'\">\r\n <div [class]=\"'flex flex-col gap-3 p-4 ' + modal.contentClass\">\r\n <input\r\n pInputText\r\n [(ngModel)]=\"title\"\r\n [placeholder]=\"t('new-group-placeholder')\"\r\n class=\"group-title-input\"\r\n (keydown.enter)=\"submit()\"\r\n />\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n [label]=\"t('cancel')\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n\r\n <mt-button\r\n [label]=\"t('add-group')\"\r\n [disabled]=\"!title().trim()\"\r\n (click)=\"submit()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n", styles: [".group-title-input{width:100%}.group-dialog-label{color:var(--p-surface-600, #4b5563);font-size:.875rem}\n"] }]
696
+ }], propDecorators: { area: [{ type: i0.Input, args: [{ isSignal: true, alias: "area", required: false }] }] } });
697
+
698
+ const DRAG_PAYLOAD_MIME = 'application/x-workspace-builder';
699
+ class WorkspaceBuilder {
700
+ levelId = input(null, ...(ngDevMode ? [{ debugName: "levelId" }] : []));
701
+ areas = ['main', 'more'];
702
+ dropScope = 'workspace-builder-item';
703
+ createGroupArea = signal('main', ...(ngDevMode ? [{ debugName: "createGroupArea" }] : []));
704
+ editingGroupId = signal(null, ...(ngDevMode ? [{ debugName: "editingGroupId" }] : []));
705
+ editingGroupTitle = signal('', ...(ngDevMode ? [{ debugName: "editingGroupTitle" }] : []));
706
+ autoSaveEnabled = signal(true, ...(ngDevMode ? [{ debugName: "autoSaveEnabled" }] : []));
707
+ expandedGroups = signal({}, ...(ngDevMode ? [{ debugName: "expandedGroups" }] : []));
708
+ dragPayload = signal(null, ...(ngDevMode ? [{ debugName: "dragPayload" }] : []));
709
+ autoSaveInFlight = false;
710
+ autoSaveQueued = false;
711
+ autoSaveQueuedNotifySuccess = false;
712
+ facade = inject(WorkspaceBuilderFacade);
713
+ confirmationService = inject(ConfirmationService);
714
+ modal = inject(ModalService);
715
+ transloco = inject(TranslocoService);
716
+ ts = inject(ToastService);
717
+ addGroupLabel = toSignal(this.transloco.selectTranslate('workspace-builder.add-group'), {
718
+ initialValue: this.transloco.translate('workspace-builder.add-group'),
719
+ });
720
+ layout = this.facade.layout;
721
+ validationErrors = this.facade.validationErrors;
722
+ isSavingLayout = this.facade.isSavingLayout;
723
+ areaContextItems = computed(() => [
724
+ {
725
+ label: this.addGroupLabel(),
726
+ icon: 'pi pi-plus',
727
+ command: () => this.openCreateGroupDialog(this.createGroupArea()),
728
+ },
729
+ ], ...(ngDevMode ? [{ debugName: "areaContextItems" }] : []));
730
+ constructor() {
731
+ effect(() => {
732
+ const levelId = this.levelId();
733
+ if (!levelId) {
734
+ return;
735
+ }
736
+ this.facade.setLevelId(levelId);
737
+ this.facade.getLayout();
738
+ });
739
+ effect(() => {
740
+ const currentLayout = this.layout();
741
+ this.expandedGroups.update((current) => {
742
+ const next = { ...current };
743
+ const existingGroupIds = new Set();
744
+ let changed = false;
745
+ this.getAllGroups(currentLayout).forEach((group) => {
746
+ existingGroupIds.add(group.id);
747
+ if (next[group.id] === undefined) {
748
+ next[group.id] = true;
749
+ changed = true;
750
+ }
751
+ });
752
+ Object.keys(next).forEach((groupId) => {
753
+ if (!existingGroupIds.has(groupId)) {
754
+ delete next[groupId];
755
+ changed = true;
756
+ }
757
+ });
758
+ return changed ? next : current;
759
+ });
760
+ });
761
+ }
762
+ ngOnDestroy() {
763
+ this.facade.resetState();
764
+ }
765
+ areaItems(area) {
766
+ return this.layout()[area];
767
+ }
768
+ trackItem(item) {
769
+ return `${item.type}:${item.id}`;
770
+ }
771
+ isGroupExpanded(groupId) {
772
+ return this.expandedGroups()[groupId] ?? true;
773
+ }
774
+ toggleGroup(groupId) {
775
+ this.expandedGroups.update((groups) => ({
776
+ ...groups,
777
+ [groupId]: !groups[groupId],
778
+ }));
779
+ }
780
+ onModuleDragStart(event, module, source) {
781
+ this.stopEventPropagation(event);
782
+ const payload = {
783
+ kind: 'module',
784
+ source,
785
+ modules: [
786
+ {
787
+ id: module.id,
788
+ name: module.name,
789
+ order: module.order,
790
+ },
791
+ ],
792
+ };
793
+ this.dragPayload.set(payload);
794
+ this.setDragTransferData(this.getNativeDragEvent(event), payload, module.id);
795
+ }
796
+ onGroupDragStart(event, group, area) {
797
+ this.stopEventPropagation(event);
798
+ const payload = {
799
+ kind: 'group',
800
+ source: { type: 'area', area },
801
+ groups: [group],
802
+ };
803
+ this.dragPayload.set(payload);
804
+ this.setDragTransferData(this.getNativeDragEvent(event), payload, group.id);
805
+ }
806
+ onDragEnd() {
807
+ // Intentionally keep payload until an explicit drop/cancel path.
808
+ // Some browsers/directives emit dragend before drop.
809
+ }
810
+ onDropToArea(event, area, index) {
811
+ this.stopDropEvent(event);
812
+ this.handleDrop({ type: 'area', area }, index, event);
813
+ }
814
+ onDropToGroup(event, area, groupId, index) {
815
+ this.stopDropEvent(event);
816
+ this.handleDrop({ type: 'group', area, groupId }, index, event);
817
+ }
818
+ allowNativeDrop(event) {
819
+ event.preventDefault();
820
+ }
821
+ onNativeAreaDrop(event, area) {
822
+ this.stopDropEvent(event);
823
+ const index = this.resolveAreaDropIndex(event, area);
824
+ this.handleDrop({ type: 'area', area }, index, event);
825
+ }
826
+ onNativeGroupDrop(event, area, groupId) {
827
+ this.stopDropEvent(event);
828
+ const group = this.findGroup(this.layout(), area, groupId);
829
+ const index = group?.modules.length ?? 0;
830
+ this.handleDrop({ type: 'group', area, groupId }, index, event);
831
+ }
832
+ onNativeGroupModuleDrop(event, area, groupId, moduleIndex) {
833
+ this.stopDropEvent(event);
834
+ const index = this.resolveSiblingDropIndex(event, moduleIndex);
835
+ this.handleDrop({ type: 'group', area, groupId }, index, event);
836
+ }
837
+ onAreaContextMenu(event, area, contextMenu) {
838
+ event.preventDefault();
839
+ this.createGroupArea.set(area);
840
+ contextMenu.show(event);
841
+ }
842
+ openCreateGroupDialog(area) {
843
+ const ref = this.modal.openModal(WorkspaceBuilderCreateGroupDialog, 'dialog', {
844
+ header: this.transloco.translate('workspace-builder.create-group'),
845
+ styleClass: '!w-[28rem]',
846
+ height: 'auto',
847
+ appendTo: 'body',
848
+ dismissableMask: true,
849
+ data: { area },
850
+ inputValues: { area },
851
+ });
852
+ ref.onClose.subscribe((result) => {
853
+ const title = result?.title?.trim();
854
+ if (!title) {
855
+ return;
856
+ }
857
+ const order = this.layout()[area].length + 1;
858
+ this.facade.createGroup(title, area, order).subscribe({
859
+ next: () => {
860
+ // Persist full layout after group creation.
861
+ this.triggerAutoSave({ notifyOnSuccess: false });
862
+ this.ts.success(this.getSuccessMessage(WorkspaceBuilderActionKey.CreateGroup, 'workspace-builder.group-create-success'));
863
+ },
864
+ error: () => {
865
+ this.ts.error(this.getErrorMessage(WorkspaceBuilderActionKey.CreateGroup, 'workspace-builder.group-create-error'));
866
+ },
867
+ });
868
+ });
869
+ }
870
+ startRenameGroup(group) {
871
+ this.editingGroupId.set(group.id);
872
+ this.editingGroupTitle.set(group.name);
873
+ }
874
+ saveRenameGroup(groupId) {
875
+ const title = this.editingGroupTitle().trim();
876
+ if (!title) {
877
+ return;
878
+ }
879
+ this.facade.renameGroup(groupId, title).subscribe({
880
+ next: () => {
881
+ this.ts.success(this.getSuccessMessage(WorkspaceBuilderActionKey.UpdateGroup, 'workspace-builder.group-update-success'));
882
+ this.cancelRenameGroup();
883
+ },
884
+ error: () => {
885
+ this.ts.error(this.getErrorMessage(WorkspaceBuilderActionKey.UpdateGroup, 'workspace-builder.group-update-error'));
886
+ },
887
+ });
888
+ }
889
+ cancelRenameGroup() {
890
+ this.editingGroupId.set(null);
891
+ this.editingGroupTitle.set('');
892
+ }
893
+ onSaveClick() {
894
+ this.triggerAutoSave({ notifyOnSuccess: true, force: true });
895
+ }
896
+ onAutoSaveToggleChange(value) {
897
+ const isEnabled = value ?? false;
898
+ this.autoSaveEnabled.set(isEnabled);
899
+ if (!isEnabled) {
900
+ this.autoSaveQueued = false;
901
+ this.autoSaveQueuedNotifySuccess = false;
902
+ }
903
+ }
904
+ deleteGroup(groupId, event) {
905
+ if (!event) {
906
+ return;
907
+ }
908
+ this.confirmationService.confirmDelete({
909
+ event: event,
910
+ type: 'popup',
911
+ accept: () => this.executeDeleteGroup(groupId),
912
+ reject: () => { },
913
+ });
914
+ }
915
+ executeDeleteGroup(groupId) {
916
+ this.facade.deleteGroup(groupId).subscribe({
917
+ next: () => {
918
+ // Persist layout after group removal so backend stores the no-group shape.
919
+ this.triggerAutoSave({ notifyOnSuccess: false });
920
+ this.ts.success(this.getSuccessMessage(WorkspaceBuilderActionKey.DeleteGroup, 'workspace-builder.group-delete-success'));
921
+ },
922
+ error: () => {
923
+ this.ts.error(this.getErrorMessage(WorkspaceBuilderActionKey.DeleteGroup, 'workspace-builder.group-delete-error'));
924
+ },
925
+ });
926
+ }
927
+ removeModuleFromGroup(area, groupId, moduleId) {
928
+ const nextLayout = this.cloneLayout(this.layout());
929
+ const group = this.findGroup(nextLayout, area, groupId);
930
+ if (!group) {
931
+ return;
932
+ }
933
+ const moduleIndex = group.modules.findIndex((module) => module.id === moduleId);
934
+ if (moduleIndex === -1) {
935
+ return;
936
+ }
937
+ const [module] = group.modules.splice(moduleIndex, 1);
938
+ const areaItems = nextLayout[area];
939
+ const groupIndex = areaItems.findIndex((item) => item.type === 'group' && item.id === groupId);
940
+ if (groupIndex === -1) {
941
+ return;
942
+ }
943
+ areaItems.splice(groupIndex + 1, 0, {
944
+ type: 'module',
945
+ id: module.id,
946
+ name: module.name,
947
+ order: 0,
948
+ });
949
+ this.patchLayoutAndAutoSave(nextLayout);
950
+ }
951
+ handleDrop(target, targetIndex, event) {
952
+ const payload = this.resolveDropPayload(event);
953
+ if (!payload) {
954
+ return;
955
+ }
956
+ if (payload.kind === 'group' && target.type === 'group') {
957
+ // Prevent nested groups.
958
+ this.dragPayload.set(null);
959
+ return;
960
+ }
961
+ const nextLayout = this.cloneLayout(this.layout());
962
+ if (payload.kind === 'group') {
963
+ this.moveGroups(nextLayout, payload, target, targetIndex);
964
+ }
965
+ else {
966
+ this.moveModules(nextLayout, payload, target, targetIndex);
967
+ }
968
+ this.patchLayoutAndAutoSave(nextLayout);
969
+ this.dragPayload.set(null);
970
+ }
971
+ patchLayoutAndAutoSave(layout) {
972
+ this.facade.patchLayout(layout).subscribe({
973
+ next: () => {
974
+ this.triggerAutoSave({ notifyOnSuccess: true });
975
+ },
976
+ });
977
+ }
978
+ triggerAutoSave(options) {
979
+ const notifyOnSuccess = options?.notifyOnSuccess ?? false;
980
+ const shouldAutoSave = options?.force || this.autoSaveEnabled();
981
+ if (!shouldAutoSave) {
982
+ return;
983
+ }
984
+ if (this.validationErrors().length > 0) {
985
+ return;
986
+ }
987
+ if (this.autoSaveInFlight) {
988
+ this.autoSaveQueued = true;
989
+ this.autoSaveQueuedNotifySuccess =
990
+ this.autoSaveQueuedNotifySuccess || notifyOnSuccess;
991
+ return;
992
+ }
993
+ this.autoSaveInFlight = true;
994
+ this.facade
995
+ .saveLayout()
996
+ .pipe(finalize(() => {
997
+ this.autoSaveInFlight = false;
998
+ if (this.autoSaveQueued) {
999
+ const queuedNotifyOnSuccess = this.autoSaveQueuedNotifySuccess;
1000
+ this.autoSaveQueued = false;
1001
+ this.autoSaveQueuedNotifySuccess = false;
1002
+ this.triggerAutoSave({ notifyOnSuccess: queuedNotifyOnSuccess });
1003
+ }
1004
+ }))
1005
+ .subscribe({
1006
+ next: () => {
1007
+ if (notifyOnSuccess) {
1008
+ this.ts.success(this.getSuccessMessage(WorkspaceBuilderActionKey.SaveLayout, 'workspace-builder.layout-update-success'));
1009
+ }
1010
+ },
1011
+ error: () => {
1012
+ this.ts.error(this.getErrorMessage(WorkspaceBuilderActionKey.SaveLayout, 'workspace-builder.layout-update-error'));
1013
+ },
1014
+ });
1015
+ }
1016
+ resolveDropPayload(event) {
1017
+ const fromTransfer = this.getDropPayloadFromTransfer(event);
1018
+ if (fromTransfer) {
1019
+ return fromTransfer;
1020
+ }
1021
+ return this.dragPayload();
1022
+ }
1023
+ getDropPayloadFromTransfer(event) {
1024
+ const transfer = this.getNativeDragEvent(event)?.dataTransfer;
1025
+ const raw = transfer?.getData?.(DRAG_PAYLOAD_MIME);
1026
+ if (!raw) {
1027
+ return null;
1028
+ }
1029
+ try {
1030
+ const parsed = JSON.parse(raw);
1031
+ if (parsed?.kind === 'module' || parsed?.kind === 'group') {
1032
+ return parsed;
1033
+ }
1034
+ }
1035
+ catch {
1036
+ return null;
1037
+ }
1038
+ return null;
1039
+ }
1040
+ setDragTransferData(event, payload, plainTextFallback) {
1041
+ const transfer = event?.dataTransfer;
1042
+ if (!transfer) {
1043
+ return;
1044
+ }
1045
+ transfer.setData(DRAG_PAYLOAD_MIME, JSON.stringify(payload));
1046
+ transfer.setData('text/plain', plainTextFallback);
1047
+ }
1048
+ stopDropEvent(event) {
1049
+ const nativeEvent = this.unwrapEvent(event);
1050
+ nativeEvent?.preventDefault?.();
1051
+ nativeEvent?.stopPropagation?.();
1052
+ event?.preventDefault?.();
1053
+ event?.stopPropagation?.();
1054
+ }
1055
+ stopEventPropagation(event) {
1056
+ this.unwrapEvent(event)?.stopPropagation?.();
1057
+ event?.stopPropagation?.();
1058
+ }
1059
+ getNativeDragEvent(event) {
1060
+ const nativeEvent = this.unwrapEvent(event);
1061
+ if (!nativeEvent || !('dataTransfer' in nativeEvent)) {
1062
+ return null;
1063
+ }
1064
+ return nativeEvent;
1065
+ }
1066
+ unwrapEvent(event) {
1067
+ if (!event) {
1068
+ return null;
1069
+ }
1070
+ const candidate = event?.originalEvent ?? event?.event;
1071
+ return (candidate ?? event);
1072
+ }
1073
+ resolveAreaDropIndex(event, area) {
1074
+ const y = event.clientY;
1075
+ const items = this.layout()[area];
1076
+ for (let index = 0; index < items.length; index += 1) {
1077
+ const element = this.getTopLevelItemElement(event, area, index);
1078
+ if (!element) {
1079
+ continue;
1080
+ }
1081
+ const rect = element.getBoundingClientRect();
1082
+ if (y < rect.top + rect.height / 2) {
1083
+ return index;
1084
+ }
1085
+ }
1086
+ return items.length;
1087
+ }
1088
+ resolveSiblingDropIndex(event, itemIndex) {
1089
+ const target = event.currentTarget;
1090
+ if (!target) {
1091
+ return itemIndex;
1092
+ }
1093
+ const rect = target.getBoundingClientRect();
1094
+ const shouldInsertAfter = event.clientY >= rect.top + rect.height / 2;
1095
+ return shouldInsertAfter ? itemIndex + 1 : itemIndex;
1096
+ }
1097
+ getTopLevelItemElement(event, area, itemIndex) {
1098
+ const root = event.currentTarget ?? null;
1099
+ if (!root) {
1100
+ return null;
1101
+ }
1102
+ const selector = area === 'main'
1103
+ ? `[data-area="main"][data-item-index="${itemIndex}"]`
1104
+ : `[data-area="more"][data-item-index="${itemIndex}"]`;
1105
+ return root.querySelector(selector);
1106
+ }
1107
+ moveGroups(layout, payload, target, targetIndex) {
1108
+ if (target.type !== 'area') {
1109
+ return;
1110
+ }
1111
+ const sourceArea = payload.source.area;
1112
+ const targetArea = target.area;
1113
+ const groupIds = new Set(payload.groups.map((group) => group.id));
1114
+ if (sourceArea === targetArea) {
1115
+ const original = layout[sourceArea];
1116
+ const moving = original.filter((item) => item.type === 'group' && groupIds.has(item.id));
1117
+ if (moving.length === 0) {
1118
+ return;
1119
+ }
1120
+ const removedBefore = original
1121
+ .slice(0, targetIndex)
1122
+ .filter((item) => item.type === 'group' && groupIds.has(item.id)).length;
1123
+ const remaining = original.filter((item) => !(item.type === 'group' && groupIds.has(item.id)));
1124
+ const insertIndex = this.clampIndex(targetIndex - removedBefore, remaining.length);
1125
+ layout[sourceArea] = [
1126
+ ...remaining.slice(0, insertIndex),
1127
+ ...moving,
1128
+ ...remaining.slice(insertIndex),
1129
+ ];
1130
+ return;
1131
+ }
1132
+ const sourceList = layout[sourceArea];
1133
+ const moving = sourceList.filter((item) => item.type === 'group' && groupIds.has(item.id));
1134
+ if (moving.length === 0) {
1135
+ return;
1136
+ }
1137
+ layout[sourceArea] = sourceList.filter((item) => !(item.type === 'group' && groupIds.has(item.id)));
1138
+ const targetList = layout[targetArea];
1139
+ const insertIndex = this.clampIndex(targetIndex, targetList.length);
1140
+ targetList.splice(insertIndex, 0, ...moving);
1141
+ }
1142
+ moveModules(layout, payload, target, targetIndex) {
1143
+ const moduleIds = new Set(payload.modules.map((module) => module.id));
1144
+ if (payload.source.type === 'area' &&
1145
+ target.type === 'area' &&
1146
+ payload.source.area === target.area) {
1147
+ const area = payload.source.area;
1148
+ const original = layout[area];
1149
+ const moving = original.filter((item) => item.type === 'module' && moduleIds.has(item.id));
1150
+ if (moving.length === 0) {
1151
+ return;
1152
+ }
1153
+ const removedBefore = original
1154
+ .slice(0, targetIndex)
1155
+ .filter((item) => item.type === 'module' && moduleIds.has(item.id)).length;
1156
+ const remaining = original.filter((item) => !(item.type === 'module' && moduleIds.has(item.id)));
1157
+ const insertIndex = this.clampIndex(targetIndex - removedBefore, remaining.length);
1158
+ layout[area] = [
1159
+ ...remaining.slice(0, insertIndex),
1160
+ ...moving,
1161
+ ...remaining.slice(insertIndex),
1162
+ ];
1163
+ return;
1164
+ }
1165
+ if (payload.source.type === 'group' &&
1166
+ target.type === 'group' &&
1167
+ payload.source.area === target.area &&
1168
+ payload.source.groupId === target.groupId) {
1169
+ const group = this.findGroup(layout, target.area, target.groupId);
1170
+ if (!group) {
1171
+ return;
1172
+ }
1173
+ const original = group.modules;
1174
+ const moving = original.filter((module) => moduleIds.has(module.id));
1175
+ if (moving.length === 0) {
1176
+ return;
1177
+ }
1178
+ const removedBefore = original
1179
+ .slice(0, targetIndex)
1180
+ .filter((module) => moduleIds.has(module.id)).length;
1181
+ const remaining = original.filter((module) => !moduleIds.has(module.id));
1182
+ const insertIndex = this.clampIndex(targetIndex - removedBefore, remaining.length);
1183
+ group.modules = [
1184
+ ...remaining.slice(0, insertIndex),
1185
+ ...moving,
1186
+ ...remaining.slice(insertIndex),
1187
+ ];
1188
+ return;
1189
+ }
1190
+ const extracted = this.extractModulesFromSource(layout, payload.source, moduleIds);
1191
+ if (extracted.length === 0) {
1192
+ return;
1193
+ }
1194
+ if (target.type === 'area') {
1195
+ const areaList = layout[target.area];
1196
+ const insertIndex = this.clampIndex(targetIndex, areaList.length);
1197
+ const topLevelModules = extracted.map((module) => ({
1198
+ type: 'module',
1199
+ id: module.id,
1200
+ name: module.name,
1201
+ order: 0,
1202
+ }));
1203
+ areaList.splice(insertIndex, 0, ...topLevelModules);
1204
+ return;
1205
+ }
1206
+ const targetGroup = this.findGroup(layout, target.area, target.groupId);
1207
+ if (!targetGroup) {
1208
+ return;
1209
+ }
1210
+ const insertIndex = this.clampIndex(targetIndex, targetGroup.modules.length);
1211
+ targetGroup.modules.splice(insertIndex, 0, ...extracted);
1212
+ }
1213
+ extractModulesFromSource(layout, source, moduleIds) {
1214
+ if (source.type === 'area') {
1215
+ const areaList = layout[source.area];
1216
+ const moving = areaList
1217
+ .filter((item) => item.type === 'module' && moduleIds.has(item.id))
1218
+ .map((module) => ({
1219
+ id: module.id,
1220
+ name: module.name,
1221
+ order: module.order,
1222
+ }));
1223
+ layout[source.area] = areaList.filter((item) => !(item.type === 'module' && moduleIds.has(item.id)));
1224
+ return moving;
1225
+ }
1226
+ const group = this.findGroup(layout, source.area, source.groupId);
1227
+ if (!group) {
1228
+ return [];
1229
+ }
1230
+ const moving = group.modules.filter((module) => moduleIds.has(module.id));
1231
+ group.modules = group.modules.filter((module) => !moduleIds.has(module.id));
1232
+ return moving;
1233
+ }
1234
+ findGroup(layout, area, groupId) {
1235
+ const found = layout[area].find((item) => item.type === 'group' && item.id === groupId);
1236
+ return found ?? null;
1237
+ }
1238
+ clampIndex(index, length) {
1239
+ return Math.max(0, Math.min(index, length));
1240
+ }
1241
+ getAllGroups(layout) {
1242
+ return [...layout.main, ...layout.more].filter((item) => item.type === 'group');
1243
+ }
1244
+ cloneLayout(layout) {
1245
+ return {
1246
+ levelId: layout.levelId,
1247
+ main: layout.main.map((item) => this.cloneItem(item)),
1248
+ more: layout.more.map((item) => this.cloneItem(item)),
1249
+ };
1250
+ }
1251
+ cloneItem(item) {
1252
+ if (item.type === 'group') {
1253
+ return {
1254
+ type: 'group',
1255
+ id: item.id,
1256
+ name: item.name,
1257
+ order: item.order,
1258
+ modules: item.modules.map((module) => ({
1259
+ id: module.id,
1260
+ name: module.name,
1261
+ order: module.order,
1262
+ })),
1263
+ };
1264
+ }
1265
+ return {
1266
+ type: 'module',
1267
+ id: item.id,
1268
+ name: item.name,
1269
+ order: item.order,
1270
+ };
1271
+ }
1272
+ getSuccessMessage(key, fallbackTranslationKey) {
1273
+ const apiMessage = this.facade.getSuccessMessage(key)?.trim();
1274
+ if (apiMessage) {
1275
+ return apiMessage;
1276
+ }
1277
+ return this.transloco.translate(fallbackTranslationKey);
1278
+ }
1279
+ getErrorMessage(key, fallbackTranslationKey) {
1280
+ const apiMessage = this.facade.getErrorMessage(key)?.trim();
1281
+ if (apiMessage) {
1282
+ return apiMessage;
1283
+ }
1284
+ return this.transloco.translate(fallbackTranslationKey);
1285
+ }
1286
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: WorkspaceBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
1287
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.3", type: WorkspaceBuilder, isStandalone: true, selector: "mt-workspace-builder", inputs: { levelId: { classPropertyName: "levelId", publicName: "levelId", isSignal: true, isRequired: false, transformFunction: null } }, providers: [DialogService], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'workspace-builder'\">\r\n <p-contextMenu\r\n #areaContextMenu\r\n [model]=\"areaContextItems()\"\r\n appendTo=\"body\"\r\n />\r\n\r\n <mt-page [title]=\"t('title')\" avatarIcon=\"layout.layout-grid-01\">\r\n <ng-template #headerEnd>\r\n <div class=\"flex items-center gap-3\">\r\n <mt-toggle-field\r\n class=\"min-w-[9rem]\"\r\n [label]=\"t('auto-save')\"\r\n [ngModel]=\"autoSaveEnabled()\"\r\n size=\"small\"\r\n (ngModelChange)=\"onAutoSaveToggleChange($event)\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n icon=\"general.save-02\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [loading]=\"isSavingLayout()\"\r\n [disabled]=\"isSavingLayout() || validationErrors().length > 0\"\r\n (onClick)=\"onSaveClick()\"\r\n />\r\n </div>\r\n </ng-template>\r\n\r\n <div class=\"grid gap-4\">\r\n @if (validationErrors().length > 0) {\r\n <div\r\n class=\"rounded-lg border border-red-300 bg-red-50 p-3 text-red-700\"\r\n >\r\n <div class=\"mb-2 font-semibold\">{{ t(\"validation-title\") }}</div>\r\n <ul class=\"m-0 list-disc ps-4\">\r\n @for (error of validationErrors(); track error) {\r\n <li>{{ error }}</li>\r\n }\r\n </ul>\r\n </div>\r\n }\r\n\r\n <div class=\"workspace-areas-grid grid gap-4\">\r\n @for (area of areas; track area) {\r\n <mt-card\r\n [paddingless]=\"true\"\r\n class=\"min-h-[24rem] overflow-hidden border border-surface-300\"\r\n (contextmenu)=\"onAreaContextMenu($event, area, areaContextMenu)\"\r\n (dragover)=\"allowNativeDrop($event)\"\r\n (drop)=\"onNativeAreaDrop($event, area)\"\r\n >\r\n <header class=\"border-b border-surface-200 px-4 py-3 font-semibold\">\r\n {{ t(area) }}\r\n </header>\r\n\r\n <div\r\n class=\"workspace-area-body grid min-h-full content-start gap-2 p-3\"\r\n (dragover)=\"allowNativeDrop($event)\"\r\n (drop)=\"onNativeAreaDrop($event, area)\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"onDropToArea($event, area, areaItems(area).length)\"\r\n >\r\n <div\r\n class=\"drop-slot\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"onDropToArea($event, area, 0)\"\r\n ></div>\r\n\r\n @for (\r\n item of areaItems(area);\r\n track trackItem(item);\r\n let i = $index\r\n ) {\r\n @if (item.type === \"module\") {\r\n <div\r\n class=\"flex min-h-10 items-center gap-2 rounded-lg border border-surface-300 bg-surface-0 px-2.5 py-2\"\r\n [attr.data-area]=\"area\"\r\n [attr.data-item-index]=\"i\"\r\n [pDraggable]=\"dropScope\"\r\n [dragHandle]=\"'.drag-handle'\"\r\n (onDragStart)=\"\r\n onModuleDragStart($event, item, {\r\n type: 'area',\r\n area: area,\r\n })\r\n \"\r\n (onDragEnd)=\"onDragEnd()\"\r\n >\r\n <mt-button\r\n class=\"drag-handle\"\r\n styleClass=\"drag-handle cursor-move\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n size=\"small\"\r\n icon=\"general.menu-05\"\r\n />\r\n <span class=\"min-w-0 flex-1 truncate\">{{ item.name }}</span>\r\n </div>\r\n } @else {\r\n <div\r\n class=\"grid gap-2 rounded-lg border border-surface-300 bg-surface-0 px-2.5 py-2\"\r\n [attr.data-area]=\"area\"\r\n [attr.data-item-index]=\"i\"\r\n [pDraggable]=\"dropScope\"\r\n [dragHandle]=\"'.group-drag-handle'\"\r\n (onDragStart)=\"onGroupDragStart($event, item, area)\"\r\n (onDragEnd)=\"onDragEnd()\"\r\n (dragover)=\"allowNativeDrop($event)\"\r\n (drop)=\"onNativeGroupDrop($event, area, item.id)\"\r\n >\r\n <div\r\n class=\"group-header flex items-center gap-2 rounded-md px-1 py-0.5\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"\r\n onDropToGroup(\r\n $event,\r\n area,\r\n item.id,\r\n item.modules.length\r\n )\r\n \"\r\n >\r\n <mt-button\r\n class=\"expand-btn\"\r\n [icon]=\"\r\n isGroupExpanded(item.id)\r\n ? 'arrow.chevron-up'\r\n : 'arrow.chevron-down'\r\n \"\r\n [text]=\"true\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n (onClick)=\"toggleGroup(item.id)\"\r\n />\r\n <mt-button\r\n class=\"group-drag-handle\"\r\n styleClass=\"group-drag-handle cursor-move\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n size=\"small\"\r\n icon=\"general.menu-05\"\r\n />\r\n\r\n @if (editingGroupId() === item.id) {\r\n <mt-text-field\r\n [(ngModel)]=\"editingGroupTitle\"\r\n class=\"min-w-[12rem] flex-1\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n [text]=\"true\"\r\n severity=\"success\"\r\n size=\"small\"\r\n (onClick)=\"saveRenameGroup(item.id)\"\r\n />\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n [text]=\"true\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n (onClick)=\"cancelRenameGroup()\"\r\n />\r\n } @else {\r\n <span class=\"min-w-0 flex-1 truncate\">{{\r\n item.name\r\n }}</span>\r\n <mt-button\r\n icon=\"general.edit-05\"\r\n [tooltip]=\"t('rename-group')\"\r\n [text]=\"true\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n (onClick)=\"startRenameGroup(item)\"\r\n />\r\n }\r\n\r\n <mt-button\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"t('delete-group')\"\r\n [text]=\"true\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n (onClick)=\"deleteGroup(item.id, $event)\"\r\n />\r\n </div>\r\n\r\n @if (isGroupExpanded(item.id)) {\r\n <div\r\n class=\"group-modules grid gap-2 border-t border-dashed border-surface-300 pt-2\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"\r\n onDropToGroup(\r\n $event,\r\n area,\r\n item.id,\r\n item.modules.length\r\n )\r\n \"\r\n >\r\n <div\r\n class=\"drop-slot\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"onDropToGroup($event, area, item.id, 0)\"\r\n ></div>\r\n\r\n @for (\r\n module of item.modules;\r\n track module.id;\r\n let j = $index\r\n ) {\r\n <div\r\n class=\"flex min-h-10 items-center gap-2 rounded-lg border border-surface-300 bg-surface-0 px-2.5 py-2 ms-2\"\r\n [pDraggable]=\"dropScope\"\r\n [dragHandle]=\"'.drag-handle'\"\r\n (dragover)=\"allowNativeDrop($event)\"\r\n (drop)=\"\r\n onNativeGroupModuleDrop($event, area, item.id, j)\r\n \"\r\n (onDragStart)=\"\r\n onModuleDragStart($event, module, {\r\n type: 'group',\r\n area: area,\r\n groupId: item.id,\r\n })\r\n \"\r\n (onDragEnd)=\"onDragEnd()\"\r\n >\r\n <mt-button\r\n class=\"drag-handle\"\r\n styleClass=\"drag-handle cursor-move\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n size=\"small\"\r\n icon=\"general.menu-05\"\r\n />\r\n <span class=\"min-w-0 flex-1 truncate\">{{\r\n module.name\r\n }}</span>\r\n <mt-button\r\n icon=\"general.minus\"\r\n [tooltip]=\"t('remove')\"\r\n [text]=\"true\"\r\n severity=\"warn\"\r\n size=\"small\"\r\n (onClick)=\"\r\n removeModuleFromGroup(area, item.id, module.id)\r\n \"\r\n />\r\n </div>\r\n\r\n <div\r\n class=\"drop-slot\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"\r\n onDropToGroup($event, area, item.id, j + 1)\r\n \"\r\n ></div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"drop-slot\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"onDropToArea($event, area, i + 1)\"\r\n ></div>\r\n }\r\n\r\n @if (areaItems(area).length === 0) {\r\n <div\r\n class=\"empty-area rounded-lg border border-dashed border-surface-300 p-4 text-center text-surface-500\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"onDropToArea($event, area, 0)\"\r\n >\r\n {{ t(\"empty-area\") }}\r\n </div>\r\n }\r\n </div>\r\n </mt-card>\r\n }\r\n </div>\r\n </div>\r\n </mt-page>\r\n</ng-container>\r\n", styles: [".workspace-areas-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}@media(max-width:960px){.workspace-areas-grid{grid-template-columns:minmax(0,1fr)}}.drop-slot{height:.5rem;border:1px dashed transparent;border-radius:9999px;background:transparent;transition:all .15s ease}.workspace-area-body.p-droppable-enter{background:var(--p-primary-50, #eff6ff)}.empty-area.p-droppable-enter,.drop-slot.p-droppable-enter{border-color:var(--p-primary-400, #60a5fa);background:var(--p-primary-50, #eff6ff)}.group-header.p-droppable-enter,.group-modules.p-droppable-enter{background:var(--p-primary-50, #eff6ff)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i2$1.Draggable, selector: "[pDraggable]", inputs: ["pDraggable", "dragEffect", "dragHandle", "pDraggableDisabled"], outputs: ["onDragStart", "onDragEnd", "onDrag"] }, { kind: "directive", type: i2$1.Droppable, selector: "[pDroppable]", inputs: ["pDroppable", "pDroppableDisabled", "dropEffect"], outputs: ["onDragEnter", "onDragLeave", "onDrop"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "icon", "iconPosition"] }, { kind: "component", type: ToggleField, selector: "mt-toggle-field", inputs: ["label", "labelPosition", "placeholder", "readonly", "pInputs", "required", "toggleShape", "size", "icon", "descriptionCard"], outputs: ["onChange"] }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: i3$1.ContextMenu, selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo", "motionOptions"], outputs: ["onShow", "onHide"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1288
+ }
1289
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: WorkspaceBuilder, decorators: [{
1290
+ type: Component,
1291
+ args: [{ selector: 'mt-workspace-builder', standalone: true, imports: [
1292
+ CommonModule,
1293
+ FormsModule,
1294
+ TranslocoDirective,
1295
+ DragDropModule,
1296
+ Button,
1297
+ Page,
1298
+ Card,
1299
+ TextField,
1300
+ ToggleField,
1301
+ ContextMenuModule,
1302
+ ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [DialogService], template: "<ng-container *transloco=\"let t; prefix: 'workspace-builder'\">\r\n <p-contextMenu\r\n #areaContextMenu\r\n [model]=\"areaContextItems()\"\r\n appendTo=\"body\"\r\n />\r\n\r\n <mt-page [title]=\"t('title')\" avatarIcon=\"layout.layout-grid-01\">\r\n <ng-template #headerEnd>\r\n <div class=\"flex items-center gap-3\">\r\n <mt-toggle-field\r\n class=\"min-w-[9rem]\"\r\n [label]=\"t('auto-save')\"\r\n [ngModel]=\"autoSaveEnabled()\"\r\n size=\"small\"\r\n (ngModelChange)=\"onAutoSaveToggleChange($event)\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n icon=\"general.save-02\"\r\n severity=\"primary\"\r\n size=\"small\"\r\n [loading]=\"isSavingLayout()\"\r\n [disabled]=\"isSavingLayout() || validationErrors().length > 0\"\r\n (onClick)=\"onSaveClick()\"\r\n />\r\n </div>\r\n </ng-template>\r\n\r\n <div class=\"grid gap-4\">\r\n @if (validationErrors().length > 0) {\r\n <div\r\n class=\"rounded-lg border border-red-300 bg-red-50 p-3 text-red-700\"\r\n >\r\n <div class=\"mb-2 font-semibold\">{{ t(\"validation-title\") }}</div>\r\n <ul class=\"m-0 list-disc ps-4\">\r\n @for (error of validationErrors(); track error) {\r\n <li>{{ error }}</li>\r\n }\r\n </ul>\r\n </div>\r\n }\r\n\r\n <div class=\"workspace-areas-grid grid gap-4\">\r\n @for (area of areas; track area) {\r\n <mt-card\r\n [paddingless]=\"true\"\r\n class=\"min-h-[24rem] overflow-hidden border border-surface-300\"\r\n (contextmenu)=\"onAreaContextMenu($event, area, areaContextMenu)\"\r\n (dragover)=\"allowNativeDrop($event)\"\r\n (drop)=\"onNativeAreaDrop($event, area)\"\r\n >\r\n <header class=\"border-b border-surface-200 px-4 py-3 font-semibold\">\r\n {{ t(area) }}\r\n </header>\r\n\r\n <div\r\n class=\"workspace-area-body grid min-h-full content-start gap-2 p-3\"\r\n (dragover)=\"allowNativeDrop($event)\"\r\n (drop)=\"onNativeAreaDrop($event, area)\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"onDropToArea($event, area, areaItems(area).length)\"\r\n >\r\n <div\r\n class=\"drop-slot\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"onDropToArea($event, area, 0)\"\r\n ></div>\r\n\r\n @for (\r\n item of areaItems(area);\r\n track trackItem(item);\r\n let i = $index\r\n ) {\r\n @if (item.type === \"module\") {\r\n <div\r\n class=\"flex min-h-10 items-center gap-2 rounded-lg border border-surface-300 bg-surface-0 px-2.5 py-2\"\r\n [attr.data-area]=\"area\"\r\n [attr.data-item-index]=\"i\"\r\n [pDraggable]=\"dropScope\"\r\n [dragHandle]=\"'.drag-handle'\"\r\n (onDragStart)=\"\r\n onModuleDragStart($event, item, {\r\n type: 'area',\r\n area: area,\r\n })\r\n \"\r\n (onDragEnd)=\"onDragEnd()\"\r\n >\r\n <mt-button\r\n class=\"drag-handle\"\r\n styleClass=\"drag-handle cursor-move\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n size=\"small\"\r\n icon=\"general.menu-05\"\r\n />\r\n <span class=\"min-w-0 flex-1 truncate\">{{ item.name }}</span>\r\n </div>\r\n } @else {\r\n <div\r\n class=\"grid gap-2 rounded-lg border border-surface-300 bg-surface-0 px-2.5 py-2\"\r\n [attr.data-area]=\"area\"\r\n [attr.data-item-index]=\"i\"\r\n [pDraggable]=\"dropScope\"\r\n [dragHandle]=\"'.group-drag-handle'\"\r\n (onDragStart)=\"onGroupDragStart($event, item, area)\"\r\n (onDragEnd)=\"onDragEnd()\"\r\n (dragover)=\"allowNativeDrop($event)\"\r\n (drop)=\"onNativeGroupDrop($event, area, item.id)\"\r\n >\r\n <div\r\n class=\"group-header flex items-center gap-2 rounded-md px-1 py-0.5\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"\r\n onDropToGroup(\r\n $event,\r\n area,\r\n item.id,\r\n item.modules.length\r\n )\r\n \"\r\n >\r\n <mt-button\r\n class=\"expand-btn\"\r\n [icon]=\"\r\n isGroupExpanded(item.id)\r\n ? 'arrow.chevron-up'\r\n : 'arrow.chevron-down'\r\n \"\r\n [text]=\"true\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n (onClick)=\"toggleGroup(item.id)\"\r\n />\r\n <mt-button\r\n class=\"group-drag-handle\"\r\n styleClass=\"group-drag-handle cursor-move\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n size=\"small\"\r\n icon=\"general.menu-05\"\r\n />\r\n\r\n @if (editingGroupId() === item.id) {\r\n <mt-text-field\r\n [(ngModel)]=\"editingGroupTitle\"\r\n class=\"min-w-[12rem] flex-1\"\r\n />\r\n <mt-button\r\n [label]=\"t('save')\"\r\n [text]=\"true\"\r\n severity=\"success\"\r\n size=\"small\"\r\n (onClick)=\"saveRenameGroup(item.id)\"\r\n />\r\n <mt-button\r\n [label]=\"t('cancel')\"\r\n [text]=\"true\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n (onClick)=\"cancelRenameGroup()\"\r\n />\r\n } @else {\r\n <span class=\"min-w-0 flex-1 truncate\">{{\r\n item.name\r\n }}</span>\r\n <mt-button\r\n icon=\"general.edit-05\"\r\n [tooltip]=\"t('rename-group')\"\r\n [text]=\"true\"\r\n severity=\"secondary\"\r\n size=\"small\"\r\n (onClick)=\"startRenameGroup(item)\"\r\n />\r\n }\r\n\r\n <mt-button\r\n icon=\"general.trash-01\"\r\n [tooltip]=\"t('delete-group')\"\r\n [text]=\"true\"\r\n severity=\"danger\"\r\n size=\"small\"\r\n (onClick)=\"deleteGroup(item.id, $event)\"\r\n />\r\n </div>\r\n\r\n @if (isGroupExpanded(item.id)) {\r\n <div\r\n class=\"group-modules grid gap-2 border-t border-dashed border-surface-300 pt-2\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"\r\n onDropToGroup(\r\n $event,\r\n area,\r\n item.id,\r\n item.modules.length\r\n )\r\n \"\r\n >\r\n <div\r\n class=\"drop-slot\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"onDropToGroup($event, area, item.id, 0)\"\r\n ></div>\r\n\r\n @for (\r\n module of item.modules;\r\n track module.id;\r\n let j = $index\r\n ) {\r\n <div\r\n class=\"flex min-h-10 items-center gap-2 rounded-lg border border-surface-300 bg-surface-0 px-2.5 py-2 ms-2\"\r\n [pDraggable]=\"dropScope\"\r\n [dragHandle]=\"'.drag-handle'\"\r\n (dragover)=\"allowNativeDrop($event)\"\r\n (drop)=\"\r\n onNativeGroupModuleDrop($event, area, item.id, j)\r\n \"\r\n (onDragStart)=\"\r\n onModuleDragStart($event, module, {\r\n type: 'group',\r\n area: area,\r\n groupId: item.id,\r\n })\r\n \"\r\n (onDragEnd)=\"onDragEnd()\"\r\n >\r\n <mt-button\r\n class=\"drag-handle\"\r\n styleClass=\"drag-handle cursor-move\"\r\n severity=\"secondary\"\r\n variant=\"outlined\"\r\n size=\"small\"\r\n icon=\"general.menu-05\"\r\n />\r\n <span class=\"min-w-0 flex-1 truncate\">{{\r\n module.name\r\n }}</span>\r\n <mt-button\r\n icon=\"general.minus\"\r\n [tooltip]=\"t('remove')\"\r\n [text]=\"true\"\r\n severity=\"warn\"\r\n size=\"small\"\r\n (onClick)=\"\r\n removeModuleFromGroup(area, item.id, module.id)\r\n \"\r\n />\r\n </div>\r\n\r\n <div\r\n class=\"drop-slot\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"\r\n onDropToGroup($event, area, item.id, j + 1)\r\n \"\r\n ></div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <div\r\n class=\"drop-slot\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"onDropToArea($event, area, i + 1)\"\r\n ></div>\r\n }\r\n\r\n @if (areaItems(area).length === 0) {\r\n <div\r\n class=\"empty-area rounded-lg border border-dashed border-surface-300 p-4 text-center text-surface-500\"\r\n [pDroppable]=\"dropScope\"\r\n (onDrop)=\"onDropToArea($event, area, 0)\"\r\n >\r\n {{ t(\"empty-area\") }}\r\n </div>\r\n }\r\n </div>\r\n </mt-card>\r\n }\r\n </div>\r\n </div>\r\n </mt-page>\r\n</ng-container>\r\n", styles: [".workspace-areas-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}@media(max-width:960px){.workspace-areas-grid{grid-template-columns:minmax(0,1fr)}}.drop-slot{height:.5rem;border:1px dashed transparent;border-radius:9999px;background:transparent;transition:all .15s ease}.workspace-area-body.p-droppable-enter{background:var(--p-primary-50, #eff6ff)}.empty-area.p-droppable-enter,.drop-slot.p-droppable-enter{border-color:var(--p-primary-400, #60a5fa);background:var(--p-primary-50, #eff6ff)}.group-header.p-droppable-enter,.group-modules.p-droppable-enter{background:var(--p-primary-50, #eff6ff)}\n"] }]
1303
+ }], ctorParameters: () => [], propDecorators: { levelId: [{ type: i0.Input, args: [{ isSignal: true, alias: "levelId", required: false }] }] } });
1304
+
1305
+ /**
1306
+ * Generated bundle index. Do not edit.
1307
+ */
1308
+
1309
+ export { CreateWorkspaceBuilderGroup, DeleteWorkspaceBuilderGroup, GetWorkspaceBuilderLayout, PatchWorkspaceBuilderLayoutLocally, RenameWorkspaceBuilderGroup, ResetWorkspaceBuilderState, SaveWorkspaceBuilderLayout, SetWorkspaceBuilderLevelId, WorkspaceBuilder, WorkspaceBuilderActionKey, WorkspaceBuilderFacade, WorkspaceBuilderState };
1310
+ //# sourceMappingURL=masterteam-workspace-builder.mjs.map