@acorex/layout 5.1.5 → 5.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. package/karma.conf.js +32 -0
  2. package/ng-package.json +9 -0
  3. package/package.json +11 -31
  4. package/src/lib/widget-board/editors/widget-size-editor/widget-size.editor.html +6 -0
  5. package/src/lib/widget-board/editors/widget-size-editor/widget-size.editor.ts +55 -0
  6. package/src/lib/widget-board/editors/widget-size-editor/widget-size.module.ts +19 -0
  7. package/src/lib/widget-board/widget-board.component.html +7 -0
  8. package/src/lib/widget-board/widget-board.component.scss +198 -0
  9. package/src/lib/widget-board/widget-board.component.ts +514 -0
  10. package/src/lib/widget-board/widget-board.module.ts +74 -0
  11. package/src/lib/widget-board/widget-config.component.html +16 -0
  12. package/src/lib/widget-board/widget-config.component.ts +99 -0
  13. package/src/lib/widget-board/widget-host.component.html +30 -0
  14. package/src/lib/widget-board/widget-host.component.ts +350 -0
  15. package/src/lib/widget-board/widget-save.component.html +43 -0
  16. package/src/lib/widget-board/widget-save.component.ts +88 -0
  17. package/src/lib/widget-board/widget.class.ts +179 -0
  18. package/{public-api.d.ts → src/public-api.ts} +0 -0
  19. package/src/test.ts +28 -0
  20. package/tsconfig.lib.json +23 -0
  21. package/tsconfig.lib.prod.json +6 -0
  22. package/tsconfig.spec.json +17 -0
  23. package/tslint.json +17 -0
  24. package/acorex-layout.d.ts +0 -5
  25. package/esm2020/acorex-layout.mjs +0 -5
  26. package/esm2020/lib/widget-board/editors/widget-size-editor/widget-size.editor.mjs +0 -46
  27. package/esm2020/lib/widget-board/editors/widget-size-editor/widget-size.module.mjs +0 -21
  28. package/esm2020/lib/widget-board/widget-board.component.mjs +0 -444
  29. package/esm2020/lib/widget-board/widget-board.module.mjs +0 -111
  30. package/esm2020/lib/widget-board/widget-config.component.mjs +0 -82
  31. package/esm2020/lib/widget-board/widget-host.component.mjs +0 -295
  32. package/esm2020/lib/widget-board/widget-save.component.mjs +0 -79
  33. package/esm2020/lib/widget-board/widget.class.mjs +0 -127
  34. package/esm2020/public-api.mjs +0 -7
  35. package/fesm2015/acorex-layout.mjs +0 -1174
  36. package/fesm2015/acorex-layout.mjs.map +0 -1
  37. package/fesm2020/acorex-layout.mjs +0 -1167
  38. package/fesm2020/acorex-layout.mjs.map +0 -1
  39. package/lib/widget-board/editors/widget-size-editor/widget-size.editor.d.ts +0 -19
  40. package/lib/widget-board/editors/widget-size-editor/widget-size.module.d.ts +0 -10
  41. package/lib/widget-board/widget-board.component.d.ts +0 -62
  42. package/lib/widget-board/widget-board.module.d.ts +0 -18
  43. package/lib/widget-board/widget-config.component.d.ts +0 -23
  44. package/lib/widget-board/widget-host.component.d.ts +0 -52
  45. package/lib/widget-board/widget-save.component.d.ts +0 -19
  46. package/lib/widget-board/widget.class.d.ts +0 -57
@@ -0,0 +1,514 @@
1
+ import {
2
+ Component,
3
+ OnInit,
4
+ Input,
5
+ ViewEncapsulation,
6
+ ElementRef,
7
+ ViewChild,
8
+ NgZone,
9
+ ChangeDetectionStrategy,
10
+ ChangeDetectorRef,
11
+ Output,
12
+ EventEmitter, ViewChildren, QueryList
13
+ } from '@angular/core';
14
+ import {
15
+ AXWidgetConfig,
16
+ AXWidgetConfigChanged,
17
+ AXWidgetBoardConfigChanged,
18
+ AXWidgetConfigSavedEvent
19
+ } from './widget.class';
20
+ import { AXWidgetHostComponent } from './widget-host.component';
21
+ import { AXHtmlUtil, AXClientRec } from '@acorex/core';
22
+ import { Observable } from 'rxjs';
23
+ import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
24
+
25
+ @Component({
26
+ selector: 'ax-widget-board',
27
+ templateUrl: './widget-board.component.html',
28
+ styleUrls: ['./widget-board.component.scss'],
29
+ host: { class: 'ax widget-board' },
30
+ encapsulation: ViewEncapsulation.None,
31
+ changeDetection: ChangeDetectionStrategy.OnPush
32
+ })
33
+ export class AXWidgetBoardComponent implements OnInit {
34
+
35
+ @ViewChild('container')
36
+ private container: ElementRef<HTMLDivElement>;
37
+
38
+ @ViewChildren(AXWidgetHostComponent)
39
+ private widgetHosts: QueryList<AXWidgetHostComponent>;
40
+
41
+ widgets: AXWidgetConfig[] = [];
42
+
43
+ @Input()
44
+ galleryItems: AXWidgetConfig[] = [];
45
+
46
+ @Input()
47
+ tileSize: number = 80;
48
+
49
+ @Input()
50
+ gapSize: number = 5;
51
+
52
+
53
+ @Input()
54
+ readonly: boolean = false;
55
+
56
+ private newWidget: AXWidgetConfig = null;
57
+
58
+ public rtl: boolean;
59
+
60
+ private _isInEditing: boolean = false;
61
+ public isInEditing(): boolean {
62
+ return this._isInEditing;
63
+ }
64
+ private isDragging: boolean = false;
65
+ private dragItem: HTMLElement;
66
+
67
+ @Input()
68
+ provideValue: (e: any) => any;
69
+
70
+ private readonly DATA_COL = 'data-col';
71
+ private readonly DATA_ROW = 'data-row';
72
+ private readonly DATA_SIZE_X = 'data-size-x';
73
+ private readonly DATA_SIZE_Y = 'data-size-y';
74
+ private readonly DATA_OLD_COL = 'data-old-col';
75
+ private readonly DATA_OLD_ROW = 'data-old-row';
76
+
77
+
78
+ @Output()
79
+ onConfigChanged: EventEmitter<AXWidgetBoardConfigChanged> = new EventEmitter<AXWidgetBoardConfigChanged>();
80
+
81
+ @Output()
82
+ onWidgetSave: EventEmitter<AXWidgetConfigSavedEvent> = new EventEmitter<AXWidgetConfigSavedEvent>();
83
+
84
+ constructor(
85
+ private ref: ElementRef<HTMLDivElement>,
86
+ private zone: NgZone,
87
+ private cdr: ChangeDetectorRef
88
+ ) { }
89
+
90
+
91
+ ngOnInit() {
92
+ if (this.rtl == null) {
93
+ this.rtl = window.getComputedStyle(this.ref.nativeElement, null).getPropertyValue('direction') === 'rtl';
94
+ }
95
+ }
96
+
97
+ ngAfterViewInit() {
98
+ this.zone.runOutsideAngular((c) => {
99
+ const style = document.createElement('style');
100
+ style.type = 'text/css';
101
+ this.ref.nativeElement.appendChild(style);
102
+ // add css data classes
103
+ for (let i = 1; i <= 50; i++) {
104
+ style.innerHTML += `[${this.DATA_COL}="${i}"] { ${this.rtl ? 'right' : 'left'}: ${(i - 1) * (this.tileSize + this.gapSize)}px; }`;
105
+ style.innerHTML += `[${this.DATA_ROW}="${i}"] { top: ${(i - 1) * (this.tileSize + this.gapSize)}px; }`;
106
+ style.innerHTML += `[${this.DATA_SIZE_X}="${i}"] { width: ${(i * this.tileSize) + ((i - 1) * this.gapSize)}px; }`;
107
+ style.innerHTML += `[${this.DATA_SIZE_Y}="${i}"] { height: ${(i * this.tileSize) + ((i - 1) * this.gapSize)}px; }`;
108
+ }
109
+ });
110
+ }
111
+
112
+ private calcGridSize() {
113
+ this.zone.runOutsideAngular(() => {
114
+ const width =
115
+ (Math.max(...this.widgets.map((c) => c.col + c.sizeX - 1))) * (this.tileSize + this.gapSize);
116
+ const height =
117
+ (Math.max(...this.widgets.map((c) => c.row + c.sizeY - 1))) * (this.tileSize + this.gapSize);
118
+ this.container.nativeElement.style.width = width + 'px';
119
+ this.container.nativeElement.style.height = height + 'px';
120
+ });
121
+ }
122
+
123
+ toggleEdit() {
124
+ this._isInEditing ? this.finishEdit() : this.startEdit();
125
+ }
126
+
127
+
128
+
129
+ private dragStart(e: MouseEvent) {
130
+ e.preventDefault();
131
+ e.stopPropagation();
132
+ this.zone.runOutsideAngular(() => {
133
+ if (this._isInEditing && e.which === 1) {
134
+ this.dragItem = e.currentTarget as any;
135
+ this.dragItem.setAttribute('data-x-offset', (this.dragItem.offsetLeft - e.clientX).toString());
136
+ //
137
+ this.dragItem.setAttribute('data-y-offset', (this.dragItem.offsetTop - e.clientY).toString());
138
+ this.dragItem.classList.add('widget-dragging');
139
+ this.dragItem.classList.remove('animate__animated', 'animate__pulse');
140
+ }
141
+ });
142
+ return false;
143
+ }
144
+
145
+ removePositionData() {
146
+ this.zone.runOutsideAngular(() => {
147
+ if (this.dragItem && this._isInEditing && this.dragItem.getAttribute(this.DATA_OLD_COL) == null) {
148
+ this.dragItem.setAttribute(this.DATA_OLD_COL, this.dragItem.getAttribute(this.DATA_COL));
149
+ this.dragItem.setAttribute(this.DATA_OLD_ROW, this.dragItem.getAttribute(this.DATA_ROW));
150
+ this.dragItem.removeAttribute(this.DATA_COL);
151
+ this.dragItem.removeAttribute(this.DATA_ROW);
152
+ }
153
+ });
154
+ }
155
+
156
+ resetPositionData() {
157
+ this.zone.runOutsideAngular(() => {
158
+ if (this.dragItem && this._isInEditing) {
159
+ this.setPosition(this.dragItem, this.dragItem.getAttribute(this.DATA_OLD_COL), this.dragItem.getAttribute(this.DATA_OLD_ROW));
160
+ }
161
+ });
162
+ }
163
+
164
+ private setPosition(element: HTMLElement, col: string, row: string) {
165
+ this.zone.runOutsideAngular(() => {
166
+ element.setAttribute(this.DATA_COL, col);
167
+ element.setAttribute(this.DATA_ROW, row);
168
+ element.removeAttribute(this.DATA_OLD_COL);
169
+ element.removeAttribute(this.DATA_OLD_ROW);
170
+ element.style.removeProperty('top');
171
+ element.style.removeProperty('left');
172
+ const widget = this.widgets.find(c => c['__meta__'].instance.element === element);
173
+ widget.col = Number(col);
174
+ widget.row = Number(row);
175
+ });
176
+ }
177
+
178
+ private drag(e) {
179
+ e.preventDefault();
180
+ e.stopPropagation();
181
+ this.zone.runOutsideAngular(() => {
182
+ if (this.dragItem && this._isInEditing) {
183
+ this.isDragging = true;
184
+ this.addPlaceholder();
185
+ const xOffset = Number(this.dragItem.getAttribute('data-x-offset'));
186
+ const yOffset = Number(this.dragItem.getAttribute('data-y-offset'));
187
+ this.dragItem.style.left = e.clientX + xOffset + 'px';
188
+ this.dragItem.style.top = e.clientY + yOffset + 'px';
189
+ //
190
+ this.detectBestPlacement();
191
+ this.removePositionData();
192
+ }
193
+ });
194
+ return false;
195
+ }
196
+
197
+ private dragEnd(e) {
198
+ this.zone.runOutsideAngular(() => {
199
+ if (this.dragItem && this._isInEditing && this.isDragging) {
200
+ this.dragItem.classList.remove('widget-dragging');
201
+ this.dragItem.classList.add('animate__animated', 'animate__pulse');
202
+ //
203
+ const p = this.container.nativeElement.querySelector('.widget-blank-placeholder');
204
+ if (this.newWidget) {
205
+ const w = {
206
+ uniqueName: this.newWidget.uniqueName,
207
+ component: this.newWidget.component,
208
+ title: this.newWidget.title,
209
+ options: this.newWidget.options,
210
+ sizeX: this.newWidget.sizeX,
211
+ sizeY: this.newWidget.sizeY,
212
+ col: Number(p.getAttribute(this.DATA_COL)),
213
+ row: Number(p.getAttribute(this.DATA_ROW))
214
+ };
215
+
216
+ this.widgets.push(w);
217
+ this.newWidget = null;
218
+ this.container.nativeElement.removeChild(this.dragItem);
219
+ this.zone.run(() => {
220
+ this.cdr.detectChanges();
221
+ this.calcGridSize();
222
+ setTimeout(() => {
223
+ w['__meta__'].instance.element.addEventListener('mousedown', this.dragStart.bind(this), false);
224
+ }, 1000);
225
+ });
226
+ } else {
227
+ if (p) {
228
+ this.setPosition(this.dragItem, p.getAttribute(this.DATA_COL), p.getAttribute(this.DATA_ROW));
229
+ }
230
+ else {
231
+ this.setPosition(this.dragItem, this.dragItem.getAttribute(this.DATA_OLD_COL), this.dragItem.getAttribute(this.DATA_OLD_ROW));
232
+ }
233
+ }
234
+ //
235
+ this.removePlaceholder();
236
+ this.dragItem = null;
237
+ this.calcGridSize();
238
+ this.emitConfigChanged();
239
+ }
240
+ this.isDragging = false;
241
+ });
242
+ }
243
+
244
+ private detectFirstEmptySlot(w: AXWidgetConfig) {
245
+ const xTile = Math.floor(this.ref.nativeElement.parentElement.offsetWidth / this.tileSize);
246
+ for (let j = 1; j <= 100; j++) {
247
+ for (let i = 1; i <= xTile - w.sizeX; i++) {
248
+ const rec = new AXClientRec({
249
+ left: i,
250
+ top: j,
251
+ width: w.sizeX,
252
+ height: w.sizeY
253
+ });
254
+ const el = this.widgets.filter(c => c !== w).find((c) =>
255
+ rec.intersect({
256
+ left: c.col,
257
+ top: c.row,
258
+ width: c.sizeX,
259
+ height: c.sizeY
260
+ })
261
+ );
262
+ if (el == null) {
263
+ w.col = i;
264
+ w.row = j;
265
+ return;
266
+ }
267
+ }
268
+ }
269
+ }
270
+
271
+ private detectBestPlacement() {
272
+ this.zone.runOutsideAngular(() => {
273
+ const p = this.container.nativeElement.querySelector('.widget-blank-placeholder') as HTMLElement;
274
+ let col = Math.ceil(this.dragItem.offsetLeft / this.tileSize);
275
+ if (this.rtl) {
276
+ col = Math.ceil((this.container.nativeElement.clientWidth - (this.dragItem.offsetLeft + this.dragItem.clientWidth)) / this.tileSize);
277
+ }
278
+
279
+ let row = Math.ceil(this.dragItem.offsetTop / this.tileSize);
280
+ if (col < 1) {
281
+ col = 1;
282
+ }
283
+ if (row < 1) {
284
+ row = 1;
285
+ }
286
+ const widgets = Array.from(this.ref.nativeElement.querySelectorAll('.widget-host')).map((c) => c as HTMLElement);
287
+ p.setAttribute(this.DATA_COL, col.toString());
288
+ p.setAttribute(this.DATA_ROW, row.toString());
289
+ const collision = widgets.filter(c => c !== this.dragItem).some(c => AXHtmlUtil.collision(c, this.dragItem));
290
+ if (collision) {
291
+ this.removePlaceholder();
292
+ }
293
+ });
294
+ }
295
+
296
+
297
+
298
+
299
+ private addPlaceholder() {
300
+ this.zone.runOutsideAngular(() => {
301
+ this.removePlaceholder();
302
+ if (this.dragItem) {
303
+ const p = document.createElement('div');
304
+ p.classList.add('widget-blank-placeholder');
305
+ p.setAttribute(this.DATA_COL, this.dragItem.getAttribute(this.DATA_COL));
306
+ p.setAttribute(this.DATA_ROW, this.dragItem.getAttribute(this.DATA_ROW));
307
+ p.setAttribute(this.DATA_SIZE_X, this.dragItem.getAttribute(this.DATA_SIZE_X));
308
+ p.setAttribute(this.DATA_SIZE_Y, this.dragItem.getAttribute(this.DATA_SIZE_Y));
309
+ this.container.nativeElement.appendChild(p);
310
+ }
311
+ });
312
+ }
313
+
314
+ private removePlaceholder() {
315
+ const p = this.container.nativeElement.querySelector('.widget-blank-placeholder');
316
+ p?.remove();
317
+ }
318
+
319
+ public startEdit() {
320
+ this._isInEditing = true;
321
+ this.calcGridSize();
322
+ this.ref.nativeElement.classList.add('grid-background');
323
+ const bg = this.ref.nativeElement as HTMLDivElement;
324
+ bg.style.setProperty('background-position', `${this.rtl ? 'right' : 'left'} top`);
325
+ // tslint:disable-next-line: max-line-length
326
+ const pattern = `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${this.tileSize + this.gapSize}" height="${this.tileSize + this.gapSize}"> <rect style="fill: %23dadada" x="${this.rtl ? this.gapSize : 0}" width="${this.tileSize}" height="${this.tileSize}" y="0"></rect></svg>')`;
327
+ bg.style.setProperty('background-image', pattern);
328
+ //
329
+ const widgets = Array.from(this.ref.nativeElement.querySelectorAll('.widget-host'));
330
+ widgets.forEach((w) => {
331
+ w.addEventListener('mousedown', this.dragStart.bind(this), false);
332
+ });
333
+ this.ref.nativeElement.addEventListener('mousemove', this.drag.bind(this), false);
334
+ this.ref.nativeElement.addEventListener('mouseup', this.dragEnd.bind(this), false);
335
+ }
336
+
337
+ public finishEdit() {
338
+ this.ref.nativeElement.classList.remove('grid-background');
339
+ const bg = this.ref.nativeElement as HTMLDivElement;
340
+ bg.style.removeProperty('background-image');
341
+ //
342
+ this._isInEditing = false;
343
+ const widgets = Array.from(this.ref.nativeElement.querySelectorAll('.widget-host'));
344
+ widgets.forEach((w) => {
345
+ w.removeEventListener('mousedown', this.dragStart.bind(this), false);
346
+ });
347
+ this.ref.nativeElement.removeEventListener('mousemove', this.drag.bind(this), false);
348
+ this.ref.nativeElement.removeEventListener('mouseup', this.dragEnd.bind(this), false);
349
+ this.emitConfigChanged();
350
+ }
351
+
352
+
353
+ addWidget(widget: AXWidgetConfig) {
354
+ this.zone.runOutsideAngular(() => {
355
+ this.newWidget = {
356
+ uniqueName: widget.uniqueName,
357
+ component: widget.component,
358
+ title: widget.title,
359
+ col: 1,
360
+ row: 1,
361
+ sizeX: widget.sizeX,
362
+ sizeY: widget.sizeY,
363
+ options: widget.options,
364
+ props: widget.props
365
+ };
366
+ this.detectFirstEmptySlot(this.newWidget);
367
+ const w = {
368
+ uniqueName: this.newWidget.uniqueName,
369
+ component: this.newWidget.component,
370
+ title: this.newWidget.title,
371
+ options: this.newWidget.options,
372
+ props: this.newWidget.props,
373
+ sizeX: this.newWidget.sizeX,
374
+ sizeY: this.newWidget.sizeY,
375
+ col: this.newWidget.col,
376
+ row: this.newWidget.row
377
+ };
378
+ this.widgets.push(w);
379
+ this.newWidget = null;
380
+ this.zone.run(() => {
381
+ this.cdr.detectChanges();
382
+ this.calcGridSize();
383
+ this.emitConfigChanged();
384
+ setTimeout(() => {
385
+ w['__meta__'].instance.element.addEventListener('mousedown', this.dragStart.bind(this), false);
386
+ }, 1000);
387
+ });
388
+ });
389
+ }
390
+
391
+
392
+ handleOnRemove(w: AXWidgetHostComponent) {
393
+ w.element.classList.add('animate__animated', 'animate__zoomOut');
394
+ w.element.addEventListener('animationend', () => {
395
+ this.widgets = this.widgets.filter((c) => (c as any).__meta__.id !== (w.config as any).__meta__.id);
396
+ this.cdr.detectChanges();
397
+ this.calcGridSize();
398
+ this.emitConfigChanged();
399
+ });
400
+ }
401
+
402
+ handleOnSave(e: AXWidgetConfigSavedEvent) {
403
+ this.onWidgetSave.emit(e);
404
+ }
405
+
406
+ trackByFn(index, item) {
407
+ if (!item['__meta__']?.id) {
408
+ item['__meta__'] = {};
409
+ item['__meta__'].id = AXHtmlUtil.getUID();
410
+ }
411
+ return item['__meta__']?.id;
412
+ }
413
+
414
+
415
+ load(widgets: string | AXWidgetConfig[]): Promise<void> {
416
+ this.clear();
417
+ return new Promise<void>((resolve, reject) => {
418
+ if (widgets) {
419
+ const loadedWidgets: AXWidgetConfig[] = [];
420
+ if (typeof widgets === 'string') {
421
+ try {
422
+ loadedWidgets.push(...JSON.parse(widgets));
423
+ } catch (error) {
424
+ reject('Invalid widget json data!');
425
+ }
426
+ }
427
+ else {
428
+ loadedWidgets.push(...widgets);
429
+ }
430
+ let intervalId: number = -1;
431
+ const loadFunc = () => {
432
+
433
+ if (this.galleryItems && this.galleryItems.length > 0) {
434
+ loadedWidgets.forEach(w => {
435
+ const gitem = this.galleryItems.find(c => c.uniqueName === w.uniqueName);
436
+ if (gitem) {
437
+ w.component = gitem.component;
438
+ if (gitem.props) {
439
+ w.props = JSON.parse(JSON.stringify(gitem.props));
440
+ }
441
+ }
442
+ });
443
+ this.widgets.push(...loadedWidgets);
444
+ window.clearInterval(intervalId);
445
+ this.cdr.detectChanges();
446
+ resolve();
447
+ }
448
+ };
449
+ intervalId = window.setInterval(loadFunc, 200);
450
+ }
451
+ else {
452
+ resolve();
453
+ }
454
+ });
455
+ }
456
+
457
+ clear() {
458
+ if (this.widgets.length) {
459
+ this.widgets = [];
460
+ this.cdr.detectChanges();
461
+ this.emitConfigChanged();
462
+ }
463
+ }
464
+
465
+ save(): Promise<string> {
466
+ const obj = this.widgets.map(c => ({
467
+ uniqueName: c.uniqueName,
468
+ component: c.component,
469
+ title: c.title,
470
+ sizeX: c.sizeX,
471
+ sizeY: c.sizeY,
472
+ col: c.col,
473
+ row: c.row,
474
+ options: c.options,
475
+ props: c.props
476
+ }));
477
+ return Promise.resolve(JSON.stringify(obj));
478
+ }
479
+
480
+ refresh() {
481
+ this.widgetHosts.forEach(host => {
482
+ host.widget.refresh();
483
+ });
484
+ }
485
+
486
+ handleOnConfigChanged(e: AXWidgetConfigChanged) {
487
+ this.cdr.detectChanges();
488
+ this.emitConfigChanged();
489
+ }
490
+
491
+ handleOnResizedChanged(e: AXWidgetConfigChanged) {
492
+ this.detectFirstEmptySlot(e.config);
493
+ this.cdr.detectChanges();
494
+ this.emitConfigChanged();
495
+ }
496
+
497
+ private resizeChangeObserver: any;
498
+ private emitConfigChanged() {
499
+ if (!this.resizeChangeObserver) {
500
+ this.resizeChangeObserver = new Observable();
501
+ Observable.create(observer => {
502
+ this.resizeChangeObserver = observer;
503
+ })
504
+ .pipe(debounceTime(750))
505
+ .pipe(distinctUntilChanged())
506
+ .subscribe(c => {
507
+ this.onConfigChanged.emit({
508
+ component: this
509
+ });
510
+ });
511
+ }
512
+ this.resizeChangeObserver.next(new Date());
513
+ }
514
+ }
@@ -0,0 +1,74 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { AXWidgetBoardComponent } from './widget-board.component';
3
+ import { CommonModule } from '@angular/common';
4
+ import { AXWidgetHostComponent } from './widget-host.component';
5
+ import {
6
+ AXSearchBoxModule,
7
+ AXLoadingModule,
8
+ AXProppertyEditorModule,
9
+ AXPageModule, AXLabelModule,
10
+ AXToolbarModule,
11
+ AXTabStripModule,
12
+ AXMenuModule,
13
+ AXFormGroupModule,
14
+ AXTextAreaModule, AXTextBoxModule, AXFieldsetModule, AXCheckBoxModule, AXValidationModule
15
+ } from '@acorex/components';
16
+ import { RouterModule } from '@angular/router';
17
+ import { AXWidgetConfigComponent } from './widget-config.component';
18
+ import { AXTranslator, AXTranslatorModule } from '@acorex/core';
19
+ import { AXWidgetSizePropertyEditorComponent } from './editors/widget-size-editor/widget-size.editor';
20
+ import { AXWidgetSizePropertyEditorModule } from './editors/widget-size-editor/widget-size.module';
21
+ import { AXWidgetSaveComponent } from './widget-save.component';
22
+
23
+ @NgModule({
24
+ imports: [
25
+ CommonModule,
26
+ AXSearchBoxModule,
27
+ AXLoadingModule,
28
+ RouterModule,
29
+ AXProppertyEditorModule,
30
+ AXPageModule,
31
+ AXToolbarModule,
32
+ AXTranslatorModule,
33
+ AXLabelModule,
34
+ AXTextAreaModule,
35
+ AXFieldsetModule,
36
+ AXTextBoxModule,
37
+ AXFormGroupModule,
38
+ AXTabStripModule,
39
+ AXCheckBoxModule,
40
+ AXMenuModule,
41
+ AXTranslatorModule,
42
+ AXValidationModule,
43
+ RouterModule.forChild([
44
+ {
45
+ component: AXWidgetSizePropertyEditorComponent,
46
+ path: 'ax/editors/widget-size'
47
+ },
48
+ ])
49
+ ],
50
+ exports: [AXWidgetBoardComponent, AXProppertyEditorModule],
51
+ declarations: [AXWidgetBoardComponent, AXWidgetHostComponent, AXWidgetConfigComponent, AXWidgetSaveComponent],
52
+ providers: []
53
+ })
54
+ export class AXWidgetBoardModule {
55
+ /**
56
+ *
57
+ */
58
+ constructor() {
59
+ AXTranslator.load('en', {
60
+ 'widget-board': {
61
+ 'configurable-props': 'Configurable Properties',
62
+ configure: 'Configure Widget'
63
+ }
64
+ });
65
+ AXTranslator.load('fa', {
66
+ 'widget-board': {
67
+ 'configurable-props': 'ویژگی های قابل تنظیم',
68
+ configure: 'پیکر بندی ابزارک'
69
+ }
70
+ });
71
+
72
+ }
73
+
74
+ }
@@ -0,0 +1,16 @@
1
+ <ax-page>
2
+ <ax-page-content>
3
+ <div class="container">
4
+ <ax-validation-form #form>
5
+ <div class="row" *ngFor="let p of displayProps; trackBy: identify">
6
+ <div class="col-12">
7
+ <ax-label>{{p.property.title | trans}}</ax-label>
8
+ <ng-container ax-property-editor-renderer [property]="p" [context]="context" [host]="widget" [validationForm]="form"
9
+ (onValueChange)="handleValueChange($event)">
10
+ </ng-container>
11
+ </div>
12
+ </div>
13
+ </ax-validation-form>
14
+ </div>
15
+ </ax-page-content>
16
+ </ax-page>
@@ -0,0 +1,99 @@
1
+ import { Component, ViewEncapsulation, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild } from '@angular/core';
2
+ import { AXPropertyConfig, AXBasePopupPageComponent, AXMenuItemClickEvent, AXProperyEditorValueChangeEvent, AXValidationFormComponent } from '@acorex/components';
3
+ import { AXButtonItem, AXTranslator } from '@acorex/core';
4
+ import { AXWidgetComponent } from './widget.class';
5
+
6
+
7
+ @Component({
8
+ templateUrl: './widget-config.component.html',
9
+ encapsulation: ViewEncapsulation.None,
10
+ changeDetection: ChangeDetectionStrategy.OnPush
11
+ })
12
+
13
+ export class AXWidgetConfigComponent extends AXBasePopupPageComponent {
14
+
15
+ @ViewChild(AXValidationFormComponent) form: AXValidationFormComponent;
16
+
17
+ constructor(private cdr: ChangeDetectorRef) {
18
+ super();
19
+ }
20
+
21
+
22
+ widget: AXWidgetComponent;
23
+ props: AXPropertyConfig[] = [];
24
+ displayProps: AXPropertyConfig[] = [];
25
+
26
+
27
+ changes: AXPropertyConfig[] = [];
28
+
29
+ context: any = {};
30
+
31
+
32
+ getFooterButtons(): AXButtonItem[] {
33
+ return [
34
+ {
35
+ name: 'okay',
36
+ submitBehavior: true,
37
+ text: AXTranslator.get('common.confirm'),
38
+ style: 'success'
39
+ },
40
+ {
41
+ name: 'cancel',
42
+ cancelBehavior: true,
43
+ text: AXTranslator.get('common.cancel'),
44
+ style: 'danger blank'
45
+ }
46
+ ];
47
+ }
48
+
49
+
50
+
51
+
52
+
53
+ onFooterButtonClick(e: AXMenuItemClickEvent) {
54
+ if (e.name === 'cancel') {
55
+ this.close();
56
+ }
57
+ if (e.name === 'okay') {
58
+ this.form.validate().then(c => {
59
+ if (c.result) {
60
+ this.close(this.changes);
61
+ }
62
+ });
63
+ }
64
+ }
65
+
66
+ handleValueChange(e: AXProperyEditorValueChangeEvent) {
67
+ const prop = this.changes.find(c => c.property.name === e.property.name);
68
+ if (prop) {
69
+ prop.value = e.value;
70
+ }
71
+ else {
72
+ this.changes.push({ property: e.property, value: e.value });
73
+ }
74
+ this.updateContext();
75
+ }
76
+
77
+ ngOnInit() {
78
+ this.displayProps = this.props.filter(c => c.property.visible !== false).sort((a, b) => a.property.order - b.property.order);
79
+ this.updateContext();
80
+ }
81
+
82
+
83
+ private updateContext() {
84
+ const ctx = {};
85
+ this.props.forEach(p => {
86
+ ctx[p.property.name] = p.value;
87
+ });
88
+ this.changes.forEach(p => {
89
+ ctx[p.property.name] = p.value;
90
+ });
91
+ this.context = ctx;
92
+ }
93
+
94
+
95
+ identify(index, item: AXPropertyConfig) {
96
+ return item.property.name;
97
+ }
98
+
99
+ }