typo3-typescript-sources 14.3.5 → 14.3.6
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.
- package/.last-synced-tag +1 -1
- package/README.md +1 -1
- package/backend/form-engine/element/slug-element.ts +13 -5
- package/backend/layout-module/sticky-language-header.ts +186 -0
- package/backend/live-search/element/result/item/item.ts +2 -0
- package/backend/live-search/element/result/result-detail-container.ts +14 -1
- package/backend/module-docheader.ts +58 -0
- package/backend/page-wizard/steps/position-step.ts +5 -0
- package/backend/tree/page-position-select.ts +25 -0
- package/backend/wizard/finisher/event-finisher.ts +62 -0
- package/backend/wizard/wizard.ts +99 -3
- package/dashboard/dashboard-wizard.ts +86 -0
- package/dashboard/dashboard.ts +28 -76
- package/dashboard/wizard/finisher/dashboard-wizard-submission-service.ts +43 -0
- package/dashboard/wizard/steps/preset-step.ts +164 -0
- package/dashboard/wizard/steps/title-step.ts +90 -0
- package/form/backend/form-editor/core.ts +22 -9
- package/form/backend/form-editor/inspector-component.ts +61 -38
- package/form/backend/form-editor/stage-component.ts +2 -2
- package/form/backend/form-wizard/form-wizard.ts +1 -0
- package/form/backend/form-wizard/steps/settings-step.ts +0 -10
- package/package.json +2 -2
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the TYPO3 CMS project.
|
|
3
|
+
*
|
|
4
|
+
* It is free software; you can redistribute it and/or modify it under
|
|
5
|
+
* the terms of the GNU General Public License, either version 2
|
|
6
|
+
* of the License, or any later version.
|
|
7
|
+
*
|
|
8
|
+
* For the full copyright and license information, please read the
|
|
9
|
+
* LICENSE.txt file that was distributed with this source code.
|
|
10
|
+
*
|
|
11
|
+
* The TYPO3 project - inspiring people to share!
|
|
12
|
+
*/
|
|
13
|
+
import { customElement, query, state } from 'lit/decorators.js';
|
|
14
|
+
import { html, LitElement, type PropertyValues, type TemplateResult } from 'lit';
|
|
15
|
+
import type { SubmissionServiceInterface } from '@typo3/backend/wizard/finisher/submission-service-interface';
|
|
16
|
+
import type { WizardStepInterface } from '@typo3/backend/wizard/steps/wizard-step-interface';
|
|
17
|
+
import type { DataStore, Wizard } from '@typo3/backend/wizard/wizard';
|
|
18
|
+
import { AutoAdvanceEvent } from '@typo3/backend/wizard/events/auto-advance-event';
|
|
19
|
+
import PresetStep from '@typo3/dashboard/wizard/steps/preset-step';
|
|
20
|
+
import TitleStep from '@typo3/dashboard/wizard/steps/title-step';
|
|
21
|
+
import { DashboardWizardSubmissionService } from '@typo3/dashboard/wizard/finisher/dashboard-wizard-submission-service';
|
|
22
|
+
|
|
23
|
+
export interface DashboardWizardDataStore extends DataStore {
|
|
24
|
+
preset?: string;
|
|
25
|
+
title?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type DashboardWizardDataStoreKey = keyof DashboardWizardDataStore;
|
|
29
|
+
|
|
30
|
+
export interface DashboardWizardContext {
|
|
31
|
+
wizard: Wizard;
|
|
32
|
+
getStoreData: <T extends DashboardWizardDataStoreKey>(key: T) => NoInfer<DashboardWizardDataStore[T]>;
|
|
33
|
+
setStoreData: <T extends DashboardWizardDataStoreKey>(key: T, value: NoInfer<DashboardWizardDataStore[T]>) => void;
|
|
34
|
+
clearStoreData: <T extends DashboardWizardDataStoreKey>(key: T) => void;
|
|
35
|
+
getDataStore: () => Readonly<DashboardWizardDataStore>;
|
|
36
|
+
dispatchAutoAdvance: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@customElement('typo3-dashboard-wizard')
|
|
40
|
+
export class DashboardWizard extends LitElement {
|
|
41
|
+
@state() steps: WizardStepInterface[] = [];
|
|
42
|
+
@state() submissionService: SubmissionServiceInterface;
|
|
43
|
+
|
|
44
|
+
@query('typo3-backend-wizard') wizard!: Wizard;
|
|
45
|
+
|
|
46
|
+
private context: DashboardWizardContext;
|
|
47
|
+
|
|
48
|
+
protected override firstUpdated(_changedProperties: PropertyValues): void {
|
|
49
|
+
super.firstUpdated(_changedProperties);
|
|
50
|
+
|
|
51
|
+
this.context = {
|
|
52
|
+
wizard: this.wizard,
|
|
53
|
+
getStoreData: this.wizard.getStoreData.bind(this.wizard),
|
|
54
|
+
setStoreData: this.wizard.setStoreData.bind(this.wizard),
|
|
55
|
+
clearStoreData: this.wizard.clearStoreData.bind(this.wizard),
|
|
56
|
+
getDataStore: this.wizard.getDataStore.bind(this.wizard),
|
|
57
|
+
dispatchAutoAdvance: () => this.wizard.dispatchEvent(new AutoAdvanceEvent())
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
this.steps = [
|
|
61
|
+
new PresetStep(this.context),
|
|
62
|
+
new TitleStep(this.context),
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
this.submissionService = new DashboardWizardSubmissionService(this.context);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected override createRenderRoot(): HTMLElement | DocumentFragment {
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected override render(): TemplateResult {
|
|
73
|
+
return html`
|
|
74
|
+
<typo3-backend-wizard .steps="${this.steps}"
|
|
75
|
+
.submissionService="${this.submissionService}"
|
|
76
|
+
skip-summary
|
|
77
|
+
></typo3-backend-wizard>
|
|
78
|
+
`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
declare global {
|
|
83
|
+
interface HTMLElementTagNameMap {
|
|
84
|
+
'typo3-dashboard-wizard': DashboardWizard;
|
|
85
|
+
}
|
|
86
|
+
}
|
package/dashboard/dashboard.ts
CHANGED
|
@@ -22,7 +22,7 @@ import '@typo3/backend/element/icon-element';
|
|
|
22
22
|
import AjaxRequest from '@typo3/core/ajax/ajax-request';
|
|
23
23
|
import ClientStorage from '@typo3/backend/storage/client';
|
|
24
24
|
import { delay } from '@typo3/core/lit-helper';
|
|
25
|
-
import Modal, { type ModalElement } from '@typo3/backend/modal';
|
|
25
|
+
import Modal, { type ModalElement, Size } from '@typo3/backend/modal';
|
|
26
26
|
import { SeverityEnum } from '@typo3/backend/enum/severity';
|
|
27
27
|
import { AjaxResponse } from '@typo3/core/ajax/ajax-response';
|
|
28
28
|
import { Categories, type DataCategoriesInterface, type NewRecordWizardItemSelectedEventInterface } from '@typo3/backend/new-record-wizard';
|
|
@@ -49,7 +49,7 @@ interface DashboardInterface {
|
|
|
49
49
|
widgetPositions: Record<number, DashboardWidgetPosition[]>
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
interface DashboardPresetInterface {
|
|
52
|
+
export interface DashboardPresetInterface {
|
|
53
53
|
identifier: string;
|
|
54
54
|
title: string;
|
|
55
55
|
description: string;
|
|
@@ -154,7 +154,7 @@ class DashboardWidgetRefreshEvent extends Event {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
class DashboardAddEvent extends Event {
|
|
157
|
+
export class DashboardAddEvent extends Event {
|
|
158
158
|
static readonly eventName = 'typo3:dashboard:dashboard:add';
|
|
159
159
|
|
|
160
160
|
constructor(
|
|
@@ -446,6 +446,8 @@ export class Dashboard extends LitElement {
|
|
|
446
446
|
this.mql = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
447
447
|
this.mqListener(this.mql);
|
|
448
448
|
this.mql.addEventListener('change', this.mqListener);
|
|
449
|
+
|
|
450
|
+
document.addEventListener(DashboardAddEvent.eventName, this.relayAddDashboardEvent);
|
|
449
451
|
}
|
|
450
452
|
|
|
451
453
|
public override disconnectedCallback() {
|
|
@@ -456,6 +458,8 @@ export class Dashboard extends LitElement {
|
|
|
456
458
|
|
|
457
459
|
this.mql?.removeEventListener('change', this.mqListener);
|
|
458
460
|
this.mql = null;
|
|
461
|
+
|
|
462
|
+
document.removeEventListener(DashboardAddEvent.eventName, this.relayAddDashboardEvent);
|
|
459
463
|
}
|
|
460
464
|
|
|
461
465
|
protected readonly mqListener = (mql: MediaQueryList|MediaQueryListEvent): void => {
|
|
@@ -496,6 +500,16 @@ export class Dashboard extends LitElement {
|
|
|
496
500
|
`;
|
|
497
501
|
}
|
|
498
502
|
|
|
503
|
+
/**
|
|
504
|
+
* Relays the cross-frame add broadcast (dispatched on `document` by the
|
|
505
|
+
* wizard finisher) onto the element, so it is handled alongside the other
|
|
506
|
+
* dashboard events. A fresh event is created because an in-flight event
|
|
507
|
+
* cannot be re-dispatched.
|
|
508
|
+
*/
|
|
509
|
+
private readonly relayAddDashboardEvent = (event: DashboardAddEvent): void => {
|
|
510
|
+
this.dispatchEvent(new DashboardAddEvent(event.preset, event.title));
|
|
511
|
+
};
|
|
512
|
+
|
|
499
513
|
private async load(): Promise<void> {
|
|
500
514
|
this.loading = true;
|
|
501
515
|
this.dashboards = await this.fetchDashboards();
|
|
@@ -514,11 +528,6 @@ export class Dashboard extends LitElement {
|
|
|
514
528
|
}
|
|
515
529
|
}
|
|
516
530
|
|
|
517
|
-
private async fetchPresets(): Promise<DashboardPresetInterface[]> {
|
|
518
|
-
const data = await this.fetchData(TYPO3.settings.ajaxUrls.dashboard_presets_get);
|
|
519
|
-
return Object.values(data);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
531
|
private async fetchCategories(): Promise<Categories> {
|
|
523
532
|
const data = await this.fetchData(TYPO3.settings.ajaxUrls.dashboard_categories_get);
|
|
524
533
|
return Categories.fromData(data as DataCategoriesInterface);
|
|
@@ -538,79 +547,19 @@ export class Dashboard extends LitElement {
|
|
|
538
547
|
}
|
|
539
548
|
|
|
540
549
|
private async createDashboard(): Promise<void> {
|
|
541
|
-
|
|
542
|
-
const filteredPresets = presets.filter(preset => preset.showInWizard);
|
|
543
|
-
|
|
544
|
-
const content = html`
|
|
545
|
-
<form>
|
|
546
|
-
<div class="form-group">
|
|
547
|
-
<label class="form-label" for="dashboard-form-add-title">${labels.get('dashboard.title')}</label>
|
|
548
|
-
<input class="form-control" id="dashboard-form-add-title" type="text" name="title" required="required">
|
|
549
|
-
</div>
|
|
550
|
-
<div class="dashboard-modal-items">
|
|
551
|
-
${repeat(filteredPresets, (preset: DashboardPresetInterface) => preset.identifier, (preset: DashboardPresetInterface, index: number) => html`
|
|
552
|
-
<div class="dashboard-modal-item">
|
|
553
|
-
<input
|
|
554
|
-
type="radio"
|
|
555
|
-
name="preset"
|
|
556
|
-
value=${preset.identifier}
|
|
557
|
-
class="dashboard-modal-item-checkbox"
|
|
558
|
-
id="dashboard-form-add-preset-${preset.identifier}"
|
|
559
|
-
?checked=${index === 0}
|
|
560
|
-
>
|
|
561
|
-
<label for="dashboard-form-add-preset-${preset.identifier}" class="dashboard-modal-item-block">
|
|
562
|
-
<span class="dashboard-modal-item-icon">
|
|
563
|
-
<typo3-backend-icon identifier=${preset.icon} size="medium"></typo3-backend-icon>
|
|
564
|
-
</span>
|
|
565
|
-
<span class="dashboard-modal-item-details">
|
|
566
|
-
<span class="dashboard-modal-item-title">${preset.title}</span>
|
|
567
|
-
<span class="dashboard-modal-item-description">${preset.description}</span>
|
|
568
|
-
</span>
|
|
569
|
-
</label>
|
|
570
|
-
</div>
|
|
571
|
-
`)}
|
|
572
|
-
</div>
|
|
573
|
-
</form>
|
|
574
|
-
`;
|
|
550
|
+
await topLevelModuleImport('@typo3/dashboard/dashboard-wizard.js');
|
|
575
551
|
|
|
576
552
|
Modal.advanced({
|
|
577
553
|
type: Modal.types.default,
|
|
578
554
|
title: labels.get('dashboard.add'),
|
|
579
|
-
size:
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
callback: (currentModal: ModalElement): void => {
|
|
583
|
-
|
|
584
|
-
currentModal.addEventListener('typo3-modal-shown', (): void => {
|
|
585
|
-
(currentModal.querySelector('#dashboard-form-add-title') as HTMLInputElement)?.focus();
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
currentModal.querySelector('form').addEventListener('submit', (e: Event): void => {
|
|
589
|
-
e.preventDefault();
|
|
590
|
-
const form = e.target as HTMLFormElement;
|
|
591
|
-
const formData = new FormData(form);
|
|
592
|
-
this.dispatchEvent(new DashboardAddEvent(
|
|
593
|
-
formData.get('preset') as string,
|
|
594
|
-
formData.get('title') as string,
|
|
595
|
-
));
|
|
596
|
-
currentModal.hideModal();
|
|
597
|
-
});
|
|
598
|
-
|
|
555
|
+
size: {
|
|
556
|
+
width: Size.default,
|
|
557
|
+
height: Size.small,
|
|
599
558
|
},
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
name: 'cancel',
|
|
605
|
-
trigger: (e, modal) => modal.hideModal(),
|
|
606
|
-
},
|
|
607
|
-
{
|
|
608
|
-
text: labels.get('dashboard.add.button.ok'),
|
|
609
|
-
btnClass: 'btn-primary',
|
|
610
|
-
name: 'save',
|
|
611
|
-
trigger: (e, modal) => modal.querySelector('form').requestSubmit(),
|
|
612
|
-
},
|
|
613
|
-
]
|
|
559
|
+
severity: SeverityEnum.notice,
|
|
560
|
+
staticBackdrop: true,
|
|
561
|
+
content: html`<typo3-dashboard-wizard></typo3-dashboard-wizard>`,
|
|
562
|
+
buttons: [],
|
|
614
563
|
});
|
|
615
564
|
}
|
|
616
565
|
|
|
@@ -1566,6 +1515,9 @@ declare global {
|
|
|
1566
1515
|
'typo3-dashboard': Dashboard;
|
|
1567
1516
|
'typo3-dashboard-widget': DashboardWidget;
|
|
1568
1517
|
}
|
|
1518
|
+
interface DocumentEventMap {
|
|
1519
|
+
[DashboardAddEvent.eventName]: DashboardAddEvent;
|
|
1520
|
+
}
|
|
1569
1521
|
interface HTMLElementEventMap {
|
|
1570
1522
|
[DashboardWidgetContentRenderedEvent.eventName]: DashboardWidgetContentRenderedEvent;
|
|
1571
1523
|
[DashboardWidgetMoveIntendEvent.eventName]: DashboardWidgetMoveIntendEvent;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the TYPO3 CMS project.
|
|
3
|
+
*
|
|
4
|
+
* It is free software; you can redistribute it and/or modify it under
|
|
5
|
+
* the terms of the GNU General Public License, either version 2
|
|
6
|
+
* of the License, or any later version.
|
|
7
|
+
*
|
|
8
|
+
* For the full copyright and license information, please read the
|
|
9
|
+
* LICENSE.txt file that was distributed with this source code.
|
|
10
|
+
*
|
|
11
|
+
* The TYPO3 project - inspiring people to share!
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { SubmissionServiceInterface } from '@typo3/backend/wizard/finisher/submission-service-interface';
|
|
15
|
+
import type { FinisherResult } from '@typo3/backend/wizard/finisher/finisher-result';
|
|
16
|
+
import type { DashboardWizardContext } from '@typo3/dashboard/dashboard-wizard';
|
|
17
|
+
import { DashboardAddEvent } from '@typo3/dashboard/dashboard';
|
|
18
|
+
import labels from '~labels/dashboard.messages';
|
|
19
|
+
|
|
20
|
+
export class DashboardWizardSubmissionService implements SubmissionServiceInterface {
|
|
21
|
+
|
|
22
|
+
constructor(private readonly context: DashboardWizardContext) {
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async execute(): Promise<FinisherResult> {
|
|
26
|
+
const { preset, title } = this.context.getDataStore();
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
success: true,
|
|
30
|
+
finisher: {
|
|
31
|
+
identifier: 'event',
|
|
32
|
+
module: '@typo3/backend/wizard/finisher/event-finisher.js',
|
|
33
|
+
data: {
|
|
34
|
+
event: new DashboardAddEvent(preset ?? '', title ?? ''),
|
|
35
|
+
},
|
|
36
|
+
labels: {
|
|
37
|
+
successTitle: labels.get('dashboard.wizard.success.title'),
|
|
38
|
+
successDescription: labels.get('dashboard.wizard.success.message'),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the TYPO3 CMS project.
|
|
3
|
+
*
|
|
4
|
+
* It is free software; you can redistribute it and/or modify it under
|
|
5
|
+
* the terms of the GNU General Public License, either version 2
|
|
6
|
+
* of the License, or any later version.
|
|
7
|
+
*
|
|
8
|
+
* For the full copyright and license information, please read the
|
|
9
|
+
* LICENSE.txt file that was distributed with this source code.
|
|
10
|
+
*
|
|
11
|
+
* The TYPO3 project - inspiring people to share!
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { html, type TemplateResult } from 'lit';
|
|
15
|
+
import { live } from 'lit/directives/live.js';
|
|
16
|
+
import { Task, TaskStatus } from '@lit/task';
|
|
17
|
+
import type { WizardStepInterface } from '@typo3/backend/wizard/steps/wizard-step-interface';
|
|
18
|
+
import type { WizardStepValueInterface } from '@typo3/backend/wizard/steps/wizard-step-value-interface';
|
|
19
|
+
import type { WizardStepSummaryInterface } from '@typo3/backend/wizard/steps/wizard-step-summary-interface';
|
|
20
|
+
import type { WizardStepAfterRenderInterface } from '@typo3/backend/wizard/steps/wizard-step-after-render-interface';
|
|
21
|
+
import type { SummaryItem } from '@typo3/backend/wizard/steps/summary-item-interface';
|
|
22
|
+
import AjaxRequest from '@typo3/core/ajax/ajax-request';
|
|
23
|
+
import type { DashboardWizardContext } from '@typo3/dashboard/dashboard-wizard';
|
|
24
|
+
import type { DashboardPresetInterface } from '@typo3/dashboard/dashboard';
|
|
25
|
+
import labels from '~labels/dashboard.messages';
|
|
26
|
+
|
|
27
|
+
export class PresetStep implements WizardStepInterface, WizardStepValueInterface, WizardStepSummaryInterface, WizardStepAfterRenderInterface {
|
|
28
|
+
readonly key = 'preset';
|
|
29
|
+
readonly title = labels.get('dashboard.wizard.preset.title');
|
|
30
|
+
readonly autoAdvance = true;
|
|
31
|
+
|
|
32
|
+
private task: Task<[], DashboardPresetInterface[]>;
|
|
33
|
+
private selectedPreset: string | null = null;
|
|
34
|
+
private hasDispatchedAutoAdvance: boolean = false;
|
|
35
|
+
|
|
36
|
+
constructor(private readonly context: DashboardWizardContext) {
|
|
37
|
+
this.initPresetsTask();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public isComplete(): boolean {
|
|
41
|
+
return this.getValue() !== null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public render(): TemplateResult {
|
|
45
|
+
if (this.task.status === TaskStatus.INITIAL) {
|
|
46
|
+
this.task.run();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return this.task.render({
|
|
50
|
+
complete: (presets: DashboardPresetInterface[]) => {
|
|
51
|
+
let shouldAutoAdvance = false;
|
|
52
|
+
|
|
53
|
+
// Initialize from store, otherwise preselect the first preset
|
|
54
|
+
if (this.getValue() === null) {
|
|
55
|
+
const storedValue = this.context.getStoreData(this.key);
|
|
56
|
+
if (storedValue != null) {
|
|
57
|
+
this.setValue(storedValue);
|
|
58
|
+
} else if (presets.length > 0) {
|
|
59
|
+
this.setValue(presets[0].identifier);
|
|
60
|
+
shouldAutoAdvance = presets.length === 1;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (shouldAutoAdvance && !this.hasDispatchedAutoAdvance) {
|
|
65
|
+
this.hasDispatchedAutoAdvance = true;
|
|
66
|
+
this.context.dispatchAutoAdvance();
|
|
67
|
+
return this.context.wizard.renderLoader();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return html`
|
|
71
|
+
<div class="dashboard-wizard-preset">
|
|
72
|
+
<h2 class="h4">${labels.get('dashboard.wizard.preset.headline')}</h2>
|
|
73
|
+
<div class="form-check-card-container">
|
|
74
|
+
${presets.map((preset: DashboardPresetInterface) => html`
|
|
75
|
+
<div class="form-check form-check-type-card">
|
|
76
|
+
<input
|
|
77
|
+
class="form-check-input"
|
|
78
|
+
type="radio"
|
|
79
|
+
name="preset"
|
|
80
|
+
id="preset-${preset.identifier}"
|
|
81
|
+
value="${preset.identifier}"
|
|
82
|
+
.checked="${live(this.getValue() === preset.identifier)}"
|
|
83
|
+
@change="${() => this.setValue(preset.identifier)}"
|
|
84
|
+
>
|
|
85
|
+
<label class="form-check-label" for="preset-${preset.identifier}">
|
|
86
|
+
<span class="form-check-label-header form-check-label-header-inherit">
|
|
87
|
+
<typo3-backend-icon identifier="${preset.icon}" size="small"></typo3-backend-icon>
|
|
88
|
+
${preset.title}
|
|
89
|
+
</span>
|
|
90
|
+
<span class="form-check-label-body">${preset.description}</span>
|
|
91
|
+
</label>
|
|
92
|
+
</div>
|
|
93
|
+
`)}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
`;
|
|
97
|
+
},
|
|
98
|
+
error: (error: unknown) => this.context.wizard.renderError(labels.get('dashboard.wizard.preset.loadError'), error),
|
|
99
|
+
pending: () => this.context.wizard.renderLoader()
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public reset(): void {
|
|
104
|
+
this.selectedPreset = null;
|
|
105
|
+
this.context.clearStoreData(this.key);
|
|
106
|
+
this.initPresetsTask();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public getValue(): string {
|
|
110
|
+
return this.selectedPreset;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public setValue(value: string): void {
|
|
114
|
+
this.selectedPreset = value;
|
|
115
|
+
this.context.wizard.requestUpdate();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public beforeAdvance(): void {
|
|
119
|
+
this.context.setStoreData(this.key, this.getValue());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public getSummaryData(): SummaryItem[] {
|
|
123
|
+
const selectedIdentifier = this.context.getStoreData(this.key);
|
|
124
|
+
if (!selectedIdentifier || !this.task.value) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const selectedPreset = this.task.value.find((preset: DashboardPresetInterface) => preset.identifier === selectedIdentifier);
|
|
129
|
+
if (!selectedPreset) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return [{
|
|
134
|
+
label: this.title,
|
|
135
|
+
value: html`
|
|
136
|
+
<typo3-backend-icon identifier="${selectedPreset.icon}" size="small" class="me-1"></typo3-backend-icon>
|
|
137
|
+
${selectedPreset.title}
|
|
138
|
+
`
|
|
139
|
+
}];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
public async afterRender(): Promise<void> {
|
|
143
|
+
// Presets load asynchronously; signal readiness so the wizard moves focus
|
|
144
|
+
// (onto the selected preset) only once they are rendered.
|
|
145
|
+
try {
|
|
146
|
+
await this.task.taskComplete;
|
|
147
|
+
} catch {
|
|
148
|
+
// ignore — the error state is rendered by the step
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private initPresetsTask(): void {
|
|
153
|
+
this.task = new Task(this.context.wizard, {
|
|
154
|
+
task: async (): Promise<DashboardPresetInterface[]> => {
|
|
155
|
+
const response = await new AjaxRequest(TYPO3.settings.ajaxUrls.dashboard_presets_get).get({ cache: 'no-cache' });
|
|
156
|
+
const data = await response.resolve();
|
|
157
|
+
return Object.values(data as Record<string, DashboardPresetInterface>).filter((preset: DashboardPresetInterface) => preset.showInWizard);
|
|
158
|
+
},
|
|
159
|
+
autoRun: false
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default PresetStep;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of the TYPO3 CMS project.
|
|
3
|
+
*
|
|
4
|
+
* It is free software; you can redistribute it and/or modify it under
|
|
5
|
+
* the terms of the GNU General Public License, either version 2
|
|
6
|
+
* of the License, or any later version.
|
|
7
|
+
*
|
|
8
|
+
* For the full copyright and license information, please read the
|
|
9
|
+
* LICENSE.txt file that was distributed with this source code.
|
|
10
|
+
*
|
|
11
|
+
* The TYPO3 project - inspiring people to share!
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { html, type TemplateResult } from 'lit';
|
|
15
|
+
import { live } from 'lit/directives/live.js';
|
|
16
|
+
import type { WizardStepInterface } from '@typo3/backend/wizard/steps/wizard-step-interface';
|
|
17
|
+
import type { WizardStepValueInterface } from '@typo3/backend/wizard/steps/wizard-step-value-interface';
|
|
18
|
+
import type { WizardStepSummaryInterface } from '@typo3/backend/wizard/steps/wizard-step-summary-interface';
|
|
19
|
+
import type { SummaryItem } from '@typo3/backend/wizard/steps/summary-item-interface';
|
|
20
|
+
import type { DashboardWizardContext } from '@typo3/dashboard/dashboard-wizard';
|
|
21
|
+
import labels from '~labels/dashboard.messages';
|
|
22
|
+
|
|
23
|
+
export class TitleStep implements WizardStepInterface, WizardStepValueInterface, WizardStepSummaryInterface {
|
|
24
|
+
readonly key = 'title';
|
|
25
|
+
readonly title = labels.get('dashboard.wizard.title.title');
|
|
26
|
+
readonly autoAdvance = false;
|
|
27
|
+
|
|
28
|
+
private titleValue: string = '';
|
|
29
|
+
|
|
30
|
+
constructor(private readonly context: DashboardWizardContext) {
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public isComplete(): boolean {
|
|
34
|
+
return this.getValue().trim() !== '';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public render(): TemplateResult {
|
|
38
|
+
if (this.titleValue === '') {
|
|
39
|
+
this.titleValue = this.context.getStoreData(this.key) ?? '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return html`
|
|
43
|
+
<div class="dashboard-wizard-title">
|
|
44
|
+
<div class="form-group">
|
|
45
|
+
<label class="form-label" for="dashboard-wizard-title-input">${labels.get('dashboard.title')}</label>
|
|
46
|
+
<input
|
|
47
|
+
type="text"
|
|
48
|
+
id="dashboard-wizard-title-input"
|
|
49
|
+
class="form-control"
|
|
50
|
+
required="required"
|
|
51
|
+
.value="${live(this.titleValue)}"
|
|
52
|
+
@input="${(event: InputEvent) => this.setValue((event.target as HTMLInputElement).value)}"
|
|
53
|
+
>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public reset(): void {
|
|
60
|
+
this.titleValue = '';
|
|
61
|
+
this.context.clearStoreData(this.key);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public getValue(): string {
|
|
65
|
+
return this.titleValue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public setValue(value: string): void {
|
|
69
|
+
this.titleValue = value;
|
|
70
|
+
this.context.wizard.requestUpdate();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public beforeAdvance(): void {
|
|
74
|
+
this.context.setStoreData(this.key, this.getValue());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public getSummaryData(): SummaryItem[] {
|
|
78
|
+
const title = this.context.getStoreData(this.key);
|
|
79
|
+
if (!title) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return [{
|
|
84
|
+
label: this.title,
|
|
85
|
+
value: title
|
|
86
|
+
}];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default TitleStep;
|
|
@@ -720,17 +720,30 @@ function extendModel<D extends object, T extends ModelData<D>>(
|
|
|
720
720
|
modelToExtend.set(pathPrefix, modelExtension, disablePublishersOnSet);
|
|
721
721
|
} else {
|
|
722
722
|
const _modelExtension = { ...modelExtension } as Record<string, T | [] | Record<string, never>>;
|
|
723
|
-
for (const key of Object.keys(_modelExtension)) {
|
|
724
|
-
const path = (pathPrefix === '') ? key : pathPrefix + '.' + key;
|
|
725
723
|
|
|
726
|
-
|
|
724
|
+
// A "leaf map" is an object whose values are all scalars (e.g. select
|
|
725
|
+
// options or finisher recipients keyed by their value). Its keys may
|
|
726
|
+
// contain dots (e.g. email addresses used as recipient keys) and must
|
|
727
|
+
// therefore be stored as a whole, otherwise Model.set() would split the
|
|
728
|
+
// key into a nested object path and thus corrupt the data.
|
|
729
|
+
const isLeafMap = pathPrefix !== '' && Object.values(_modelExtension).every(
|
|
730
|
+
(value) => value === null || typeof value !== 'object'
|
|
731
|
+
);
|
|
727
732
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
733
|
+
if (isLeafMap) {
|
|
734
|
+
modelToExtend.on(pathPrefix, 'core/formElement/somePropertyChanged');
|
|
735
|
+
modelToExtend.set(pathPrefix, modelExtension, disablePublishersOnSet);
|
|
736
|
+
} else {
|
|
737
|
+
for (const key of Object.keys(_modelExtension)) {
|
|
738
|
+
const path = (pathPrefix === '') ? key : pathPrefix + '.' + key;
|
|
739
|
+
|
|
740
|
+
modelToExtend.on(path, 'core/formElement/somePropertyChanged');
|
|
741
|
+
|
|
742
|
+
if (_modelExtension[key] !== null && (typeof (_modelExtension[key]) === 'object' || Array.isArray(_modelExtension[key]))) {
|
|
743
|
+
extendModel(modelToExtend, _modelExtension[key], path, disablePublishersOnSet);
|
|
744
|
+
} else {
|
|
745
|
+
modelToExtend.set(path, _modelExtension[key], disablePublishersOnSet);
|
|
746
|
+
}
|
|
734
747
|
}
|
|
735
748
|
}
|
|
736
749
|
}
|