Skip to content

Commit a302461

Browse files
egordidenkond0ut
andauthored
feat/update uc modal (#797)
* feat: added grid mode in upload list * feat: added cloudImageEditor and sources in minimal mode * feat: added cloudImageEditorAutoOpen props --------- Co-authored-by: nd0ut <[email protected]>
1 parent 3b08242 commit a302461

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1280
-430
lines changed

abstract/ActivityBlock.js

+10-15
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,6 @@ export class ActivityBlock extends Block {
4949
/** @protected */
5050
initCallback() {
5151
super.initCallback();
52-
if (this.hasAttribute('current-activity')) {
53-
this.sub('*currentActivity', (/** @type {String} */ val) => {
54-
this.setAttribute('current-activity', val);
55-
});
56-
}
5752

5853
// TODO: rename activityType to activityId
5954
if (this.activityType) {
@@ -76,14 +71,6 @@ export class ActivityBlock extends Block {
7671
this.$['*history'] = [];
7772
}
7873
});
79-
80-
if (this.has('*modalActive')) {
81-
this.sub('*modalActive', (modalActive) => {
82-
if (!modalActive && this.activityType === this.$['*currentActivity']) {
83-
this.$['*currentActivity'] = null;
84-
}
85-
});
86-
}
8774
}
8875
}
8976

@@ -160,7 +147,7 @@ export class ActivityBlock extends Block {
160147

161148
if (!hasCurrentActivityInCtx) {
162149
this.$['*currentActivity'] = null;
163-
this.setOrAddState('*modalActive', false);
150+
this.modalManager.closeAll();
164151
}
165152
}
166153

@@ -186,21 +173,29 @@ export class ActivityBlock extends Block {
186173
historyBack() {
187174
/** @type {String[]} */
188175
let history = this.$['*history'];
176+
189177
if (history) {
190178
let nextActivity = history.pop();
179+
191180
while (nextActivity === this.activityType) {
192181
nextActivity = history.pop();
193182
}
183+
194184
let couldOpenActivity = !!nextActivity;
195185
if (nextActivity) {
196186
const nextActivityBlock = [...this.blocksRegistry].find((block) => block.activityType === nextActivity);
197187
couldOpenActivity = /** @type {ActivityBlock} */ (nextActivityBlock)?.couldOpenActivity ?? false;
198188
}
189+
199190
nextActivity = couldOpenActivity ? nextActivity : undefined;
191+
192+
if (nextActivity) this.modalManager.open(nextActivity);
193+
200194
this.$['*currentActivity'] = nextActivity;
201195
this.$['*history'] = history;
196+
202197
if (!nextActivity) {
203-
this.setOrAddState('*modalActive', false);
198+
this.modalManager.closeAll();
204199
}
205200
}
206201
}

abstract/Block.js

+15
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { LocaleManager, localeStateKey } from './LocaleManager.js';
1313
import { l10nProcessor } from './l10nProcessor.js';
1414
import { sharedConfigKey } from './sharedConfigKey.js';
1515
import { A11y } from './a11y.js';
16+
import { ModalManager } from './ModalManager.js';
1617

1718
const TAG_PREFIX = 'uc-';
1819

@@ -180,12 +181,24 @@ export class Block extends BaseComponent {
180181
this.add('*a11y', new A11y());
181182
}
182183

184+
if (!this.has('*modalManager')) {
185+
this.add('*modalManager', new ModalManager(this));
186+
}
187+
183188
this.sub(localeStateKey('locale-id'), (localeId) => {
184189
const direction = getLocaleDirection(localeId);
185190
this.style.direction = direction === 'ltr' ? '' : direction;
186191
});
187192
}
188193

194+
/**
195+
* @returns {ModalManager}
196+
* @public
197+
*/
198+
get modalManager() {
199+
return this.has('*modalManager') && this.$['*modalManager'];
200+
}
201+
189202
/**
190203
* @private
191204
* @returns {LocaleManager | null}
@@ -238,6 +251,8 @@ export class Block extends BaseComponent {
238251
Data.deleteCtx(this.ctxName);
239252

240253
this.localeManager?.destroy();
254+
255+
this.modalManager && this.modalManager?.destroy();
241256
}
242257

243258
/**

abstract/CTX.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ export const activityBlockCtx = (fnCtx) => ({
88
...blockCtx(),
99
'*currentActivity': null,
1010
'*currentActivityParams': {},
11+
1112
'*history': [],
1213
'*historyBack': null,
1314
'*closeModal': () => {
15+
fnCtx.modalManager.closeAll();
16+
1417
fnCtx.set$({
1518
'*currentActivity': null,
16-
'*modalActive': false,
1719
});
1820
},
1921
});

abstract/ModalManager.js

+234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
//@ts-check
2+
3+
export const ModalEvents = Object.freeze({
4+
ADD: 'modal:add',
5+
DELETE: 'modal:delete',
6+
OPEN: 'modal:open',
7+
CLOSE: 'modal:close',
8+
CLOSE_ALL: 'modal:closeAll',
9+
DESTROY: 'modal:destroy',
10+
});
11+
12+
/** @typedef {string} ModalId */
13+
14+
/** @typedef {import('../blocks/Modal/Modal.js').Modal} ModalNode */
15+
16+
/** @typedef {(data: { id: ModalId; modal: ModalNode }) => void} ModalCb */
17+
18+
/** @typedef {(typeof ModalEvents)[keyof ModalEvents]} ModalEventType */
19+
20+
export class ModalManager {
21+
/**
22+
* @private
23+
* @type {Map<ModalId, ModalNode>}
24+
*/
25+
_modals = new Map();
26+
27+
/**
28+
* @private
29+
* @type {Set<ModalId>}
30+
*/
31+
_activeModals = new Set();
32+
33+
/**
34+
* @private
35+
* @type {Map<ModalEventType, Set<ModalCb>>}
36+
*/
37+
_subscribers = new Map();
38+
39+
/** @param {import('./Block.js').Block} block */
40+
constructor(block) {
41+
this._block = block;
42+
}
43+
44+
/**
45+
* @private
46+
* @param {unknown[]} args
47+
*/
48+
_debugPrint(...args) {
49+
this._block.debugPrint('[modal-manager]', ...args);
50+
}
51+
52+
/**
53+
* Register a modal with the manager
54+
*
55+
* @param {ModalId} id - Unique identifier for the modal
56+
* @param {ModalNode} modal - Modal component instance
57+
*/
58+
registerModal(id, modal) {
59+
this._modals.set(id, modal);
60+
this._notify(ModalEvents.ADD, { id, modal });
61+
}
62+
63+
/** @param {ModalId} id - Unique identifier for the modal */
64+
deleteModal(id) {
65+
if (!this._modals.has(id)) return false;
66+
67+
const modal = this._modals.get(id);
68+
this._modals.delete(id);
69+
this._activeModals.delete(id);
70+
this._notify(ModalEvents.DELETE, { id, modal });
71+
return true;
72+
}
73+
74+
/**
75+
* Open a modal by its ID
76+
*
77+
* @param {ModalId} id - The ID of the modal to open
78+
* @returns {boolean} - Success status
79+
*/
80+
open(id) {
81+
if (!this._modals.has(id)) {
82+
this._debugPrint(`Modal with ID "${id}" not found`);
83+
return false;
84+
}
85+
86+
const modal = this._modals.get(id);
87+
88+
this._activeModals.add(id);
89+
this._notify(ModalEvents.OPEN, { modal, id });
90+
return true;
91+
}
92+
93+
/**
94+
* Close a specific modal by ID
95+
*
96+
* @param {ModalId} id - The ID of the modal to close
97+
* @returns {boolean} - Success status
98+
*/
99+
close(id) {
100+
if (!this._modals.has(id) || !this._activeModals.has(id)) {
101+
this._debugPrint(`Modal with ID "${id}" not found or not active`);
102+
return false;
103+
}
104+
105+
const modal = this._modals.get(id);
106+
107+
this._activeModals.delete(id);
108+
this._notify(ModalEvents.CLOSE, { id, modal });
109+
return true;
110+
}
111+
112+
/**
113+
* Toggle a modal - open if closed, close if open
114+
*
115+
* @param {ModalId} id - The ID of the modal to toggle
116+
* @returns {boolean} - Success status
117+
*/
118+
toggle(id) {
119+
if (!this._modals.has(id)) {
120+
this._debugPrint(`Modal with ID "${id}" not found`);
121+
return false;
122+
}
123+
124+
if (this._activeModals.has(id)) {
125+
return this.close(id);
126+
} else {
127+
return this.open(id);
128+
}
129+
}
130+
131+
/**
132+
* Check if any modals are currently active/open
133+
*
134+
* @returns {boolean} - True if there are any active modals
135+
*/
136+
get hasActiveModals() {
137+
return this._activeModals.size > 0;
138+
}
139+
140+
/**
141+
* Close the most recently opened modal and return to the previous one
142+
*
143+
* @returns {boolean} - Success status
144+
*/
145+
back() {
146+
if (this._activeModals.size === 0) {
147+
this._debugPrint('No active modals to go back from');
148+
return false;
149+
}
150+
151+
// Get the last opened modal
152+
const lastModalId = Array.from(this._activeModals).pop();
153+
return this.close(/** @type {string} */ (lastModalId));
154+
}
155+
156+
/**
157+
* Close all open modals
158+
*
159+
* @returns {number} - Number of modals closed
160+
*/
161+
closeAll() {
162+
const count = this._activeModals.size;
163+
164+
this._activeModals.clear();
165+
this._notify(ModalEvents.CLOSE_ALL, {});
166+
return count;
167+
}
168+
169+
/**
170+
* Subscribe to modal events
171+
*
172+
* @param {ModalEventType} event
173+
* @param {ModalCb} callback
174+
* @returns {() => void}
175+
*/
176+
subscribe(event, callback) {
177+
if (!this._subscribers.has(event)) {
178+
this._subscribers.set(event, new Set());
179+
}
180+
this._subscribers.get(event)?.add(callback);
181+
182+
return () => this.unsubscribe(event, callback);
183+
}
184+
185+
/**
186+
* Unsubscribe from modal events
187+
*
188+
* @param {ModalEventType} event
189+
* @param {ModalCb | undefined} callback
190+
*/
191+
unsubscribe(event, callback) {
192+
if (this._subscribers.has(event)) {
193+
this._subscribers.get(event)?.delete(/** @type {ModalCb} */ (callback));
194+
}
195+
}
196+
197+
/**
198+
* Notify all subscribers of a modal event
199+
*
200+
* @private
201+
* @param {ModalEventType} event - Event name
202+
* @param {{
203+
* id: ModalId;
204+
* modal: ModalNode;
205+
* }
206+
* | object} data
207+
*/
208+
_notify(event, data) {
209+
if (this._subscribers.has(event)) {
210+
for (const callback of this._subscribers.get(event) ?? new Set()) {
211+
try {
212+
callback(
213+
/**
214+
* @type{{
215+
* id: ModalId;
216+
* modal: ModalNode;
217+
* }}
218+
*/ (data),
219+
);
220+
} catch (error) {
221+
this._debugPrint('Error in modal subscriber:', error);
222+
}
223+
}
224+
}
225+
}
226+
227+
/** Destroy the modal manager, clean up resources */
228+
destroy() {
229+
this.closeAll();
230+
this._modals.clear();
231+
this._subscribers.clear();
232+
this._notify(ModalEvents.DESTROY, {});
233+
}
234+
}

0 commit comments

Comments
 (0)