mirror of
https://github.com/nocobase/nocobase.git
synced 2026-08-29 02:03:53 +08:00
Merge branch 'main' into next
This commit is contained in:
@@ -225,6 +225,108 @@ describe('ensureFormValueDrivenDataScopeClear', () => {
|
||||
expect(onChange).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
it('does not clear a popup form field when data scope depends on the external parent item', () => {
|
||||
const emitter = new EventEmitter();
|
||||
const formBlock = {
|
||||
uid: 'form-1',
|
||||
disposed: false,
|
||||
emitter,
|
||||
context: {
|
||||
form: {},
|
||||
formValues: { staff_m2o: null },
|
||||
},
|
||||
};
|
||||
|
||||
const onChange = vi.fn();
|
||||
const model: any = {
|
||||
disposed: false,
|
||||
props: {
|
||||
value: { id: 10 },
|
||||
onChange,
|
||||
},
|
||||
context: {
|
||||
blockModel: formBlock,
|
||||
},
|
||||
};
|
||||
|
||||
const ctx: any = {
|
||||
model,
|
||||
flowKey: 'selectSettings',
|
||||
};
|
||||
|
||||
const filter = {
|
||||
logic: '$and',
|
||||
items: [{ path: 'orgId', operator: '$eq', value: '{{ ctx.item.parentItem.value.org_m2o.id }}' }],
|
||||
};
|
||||
|
||||
ensureFormValueDrivenDataScopeClear(ctx, filter);
|
||||
|
||||
emitter.emit('formValuesChange', {
|
||||
changedPaths: [['staff_m2o']],
|
||||
allValues: { staff_m2o: { id: 10 } },
|
||||
});
|
||||
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('maps popup current item dependencies to the popup form root', () => {
|
||||
const emitter = new EventEmitter();
|
||||
const formBlock = {
|
||||
uid: 'form-1',
|
||||
disposed: false,
|
||||
emitter,
|
||||
context: {
|
||||
form: {},
|
||||
formValues: {
|
||||
org_m2o: { id: 1 },
|
||||
staff_m2o: { id: 10 },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const onChange = vi.fn();
|
||||
const model: any = {
|
||||
disposed: false,
|
||||
props: {
|
||||
value: { id: 10 },
|
||||
onChange,
|
||||
},
|
||||
context: {
|
||||
blockModel: formBlock,
|
||||
},
|
||||
};
|
||||
|
||||
const ctx: any = {
|
||||
model,
|
||||
flowKey: 'selectSettings',
|
||||
};
|
||||
|
||||
const filter = {
|
||||
logic: '$and',
|
||||
items: [{ path: 'orgId', operator: '$eq', value: '{{ ctx.item.value.org_m2o.id }}' }],
|
||||
};
|
||||
|
||||
ensureFormValueDrivenDataScopeClear(ctx, filter);
|
||||
|
||||
emitter.emit('formValuesChange', {
|
||||
changedPaths: [['staff_m2o']],
|
||||
allValues: {
|
||||
org_m2o: { id: 1 },
|
||||
staff_m2o: { id: 10 },
|
||||
},
|
||||
});
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
|
||||
emitter.emit('formValuesChange', {
|
||||
changedPaths: [['org_m2o']],
|
||||
allValues: {
|
||||
org_m2o: { id: 2 },
|
||||
staff_m2o: { id: 10 },
|
||||
},
|
||||
});
|
||||
expect(onChange).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
it('does not clear a row field when a sibling field changes but the item dependency value is unchanged', () => {
|
||||
const emitter = new EventEmitter();
|
||||
const formBlock = {
|
||||
|
||||
@@ -105,9 +105,6 @@ function resolveItemDependencyPath(ctx: FlowContext, depPath: NamePath): DataSco
|
||||
ctx,
|
||||
parseFieldIndexEntries((ctx.model as any)?.context?.fieldIndex ?? (ctx as any)?.fieldIndex),
|
||||
);
|
||||
if (!entries.length) {
|
||||
return wildcardDeps();
|
||||
}
|
||||
|
||||
let parentDepth = 0;
|
||||
let cursor = [...depPath];
|
||||
@@ -121,6 +118,10 @@ function resolveItemDependencyPath(ctx: FlowContext, depPath: NamePath): DataSco
|
||||
return wildcardDeps();
|
||||
}
|
||||
|
||||
if (!entries.length) {
|
||||
return parentDepth === 0 ? resolveRootItemDependencyPath(cursor) : emptyDeps();
|
||||
}
|
||||
|
||||
if (parentDepth === entries.length) {
|
||||
return resolveRootItemDependencyPath(cursor);
|
||||
}
|
||||
|
||||
+406
-44
@@ -98,6 +98,138 @@ type CalendarPopupActionOptions = {
|
||||
persist?: boolean;
|
||||
};
|
||||
|
||||
type CalendarPopupSettingsOptions = {
|
||||
preferActionSettings?: boolean;
|
||||
};
|
||||
|
||||
type CalendarPopupActionKey = 'quickCreateAction' | 'eventViewAction';
|
||||
type CalendarPopupTemplateContextFlags = {
|
||||
hasFilterByTk: boolean;
|
||||
hasSourceId: boolean;
|
||||
};
|
||||
type CalendarPopupTemplateActionScene = 'record' | 'collection' | 'both' | undefined;
|
||||
type CalendarPopupTemplateRow = {
|
||||
useModel?: unknown;
|
||||
filterByTk?: unknown;
|
||||
sourceId?: unknown;
|
||||
};
|
||||
type CalendarPopupSettingsStepKey = 'quickCreatePopupSettings' | 'eventPopupSettings';
|
||||
|
||||
const CALENDAR_POPUP_SETTINGS_STEP_ACTIONS: Record<CalendarPopupSettingsStepKey, CalendarPopupActionKey> = {
|
||||
quickCreatePopupSettings: 'quickCreateAction',
|
||||
eventPopupSettings: 'eventViewAction',
|
||||
};
|
||||
|
||||
const POPUP_TEMPLATE_SETTING_KEYS = [
|
||||
'uid',
|
||||
'dataSourceKey',
|
||||
'collectionName',
|
||||
'associationName',
|
||||
'filterByTk',
|
||||
'sourceId',
|
||||
'popupTemplateUid',
|
||||
'popupTemplateMode',
|
||||
'popupTemplateContext',
|
||||
'popupTemplateHasFilterByTk',
|
||||
'popupTemplateHasSourceId',
|
||||
];
|
||||
|
||||
const clearPopupTemplateParams = (params: Record<string, any>) => {
|
||||
delete params.popupTemplateUid;
|
||||
delete params.popupTemplateContext;
|
||||
delete params.popupTemplateHasFilterByTk;
|
||||
delete params.popupTemplateHasSourceId;
|
||||
delete params.popupTemplateUseModel;
|
||||
delete params.popupTemplateMode;
|
||||
delete params.associationName;
|
||||
delete params.filterByTk;
|
||||
delete params.sourceId;
|
||||
delete params.uid;
|
||||
};
|
||||
|
||||
const clearPopupRecordScopeParams = (params: Record<string, any>) => {
|
||||
delete params.filterByTk;
|
||||
delete params.sourceId;
|
||||
};
|
||||
|
||||
const normalizePopupTemplateString = (value: unknown): string => {
|
||||
if (typeof value === 'string') {
|
||||
return value.trim();
|
||||
}
|
||||
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
return String(value).trim();
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const resolveCalendarPopupTemplateActionScene = (
|
||||
flowEngine: any,
|
||||
useModel: unknown,
|
||||
): CalendarPopupTemplateActionScene => {
|
||||
const useKey = normalizePopupTemplateString(useModel);
|
||||
if (!useKey) {
|
||||
return undefined;
|
||||
}
|
||||
const ModelClass = flowEngine?.getModelClass?.(useKey);
|
||||
const isScene = ModelClass?._isScene;
|
||||
if (typeof isScene !== 'function') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const isRecord = !!isScene.call(ModelClass, 'record');
|
||||
const isCollection = !!isScene.call(ModelClass, 'collection');
|
||||
if (isRecord && isCollection) {
|
||||
return 'both';
|
||||
}
|
||||
if (isRecord) {
|
||||
return 'record';
|
||||
}
|
||||
if (isCollection) {
|
||||
return 'collection';
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const inferCalendarPopupTemplateContextFlags = (
|
||||
flowEngine: any,
|
||||
template: CalendarPopupTemplateRow,
|
||||
): CalendarPopupTemplateContextFlags => {
|
||||
const scene = resolveCalendarPopupTemplateActionScene(flowEngine, template?.useModel);
|
||||
const filterByTk = normalizePopupTemplateString(template?.filterByTk);
|
||||
const sourceId = normalizePopupTemplateString(template?.sourceId);
|
||||
const isCollectionOnly = scene === 'collection';
|
||||
const isRecordOnly = scene === 'record';
|
||||
|
||||
let hasFilterByTk = false;
|
||||
if (filterByTk) {
|
||||
hasFilterByTk = !(isCollectionOnly && filterByTk.includes('ctx.record'));
|
||||
} else if (isRecordOnly) {
|
||||
hasFilterByTk = true;
|
||||
}
|
||||
|
||||
let hasSourceId = false;
|
||||
if (sourceId) {
|
||||
hasSourceId = !(isCollectionOnly && sourceId.includes('ctx.resource'));
|
||||
}
|
||||
|
||||
return { hasFilterByTk, hasSourceId };
|
||||
};
|
||||
|
||||
const replaceCalendarActionOpenViewParams = (action: any, params: Record<string, any>) => {
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
action.stepParams = action.stepParams || {};
|
||||
action.stepParams.popupSettings = action.stepParams.popupSettings || {};
|
||||
action.stepParams.popupSettings.openView = { ...params };
|
||||
action.emitter?.emit?.('onStepParamsChanged');
|
||||
};
|
||||
|
||||
const isCalendarPopupTemplateCopyMode = (params?: Record<string, any>) => {
|
||||
return params?.popupTemplateContext === true;
|
||||
};
|
||||
|
||||
const DRAG_HANDLER_TOOLBAR_ITEMS = [
|
||||
{
|
||||
key: 'drag-handler',
|
||||
@@ -301,6 +433,10 @@ export class CalendarBlockModel extends CollectionBlockModel {
|
||||
return this.subModels?.eventViewAction as any;
|
||||
}
|
||||
|
||||
getPopupAction(actionKey: CalendarPopupActionKey) {
|
||||
return actionKey === 'quickCreateAction' ? this.getQuickCreateAction() : this.getEventViewAction();
|
||||
}
|
||||
|
||||
getPopupSettingsDefaults(actionUid?: string) {
|
||||
return {
|
||||
mode: normalizeEventOpenMode(this.props?.eventOpenMode),
|
||||
@@ -316,9 +452,130 @@ export class CalendarBlockModel extends CollectionBlockModel {
|
||||
return actionKey === 'quickCreateAction' ? 'quickCreatePopupSettings' : 'eventPopupSettings';
|
||||
}
|
||||
|
||||
getStoredPopupSettings(actionKey: 'quickCreateAction' | 'eventViewAction') {
|
||||
getPopupActionSettings(action: any) {
|
||||
return action?.getStepParams?.('popupSettings', 'openView') || {};
|
||||
}
|
||||
|
||||
getRawStoredPopupSettings(actionKey: CalendarPopupActionKey) {
|
||||
const propKey = this.getPopupSettingsPropKey(actionKey);
|
||||
return this.props?.[propKey] || {};
|
||||
if (this.props?.[propKey]) {
|
||||
return this.props[propKey];
|
||||
}
|
||||
|
||||
try {
|
||||
return super.getStepParams('calendarSettings', propKey) || {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
getStoredPopupSettings(actionKey: 'quickCreateAction' | 'eventViewAction') {
|
||||
return this.getRawStoredPopupSettings(actionKey);
|
||||
}
|
||||
|
||||
getPopupSettingsStepParams(stepKey: CalendarPopupSettingsStepKey) {
|
||||
const actionKey = CALENDAR_POPUP_SETTINGS_STEP_ACTIONS[stepKey];
|
||||
const action = this.getPopupAction(actionKey);
|
||||
return this.getPopupSettings(action, actionKey, action?.uid);
|
||||
}
|
||||
|
||||
getStepParams(flowKey: string, stepKey: string): any | undefined;
|
||||
getStepParams(flowKey: string): Record<string, any> | undefined;
|
||||
getStepParams(): any;
|
||||
getStepParams(flowKey?: string, stepKey?: string): any {
|
||||
if (flowKey === 'calendarSettings' && stepKey && stepKey in CALENDAR_POPUP_SETTINGS_STEP_ACTIONS) {
|
||||
return this.getPopupSettingsStepParams(stepKey as CalendarPopupSettingsStepKey);
|
||||
}
|
||||
|
||||
if (flowKey === 'calendarSettings' && !stepKey) {
|
||||
const params = super.getStepParams(flowKey) || {};
|
||||
return {
|
||||
...params,
|
||||
quickCreatePopupSettings: this.getPopupSettingsStepParams('quickCreatePopupSettings'),
|
||||
eventPopupSettings: this.getPopupSettingsStepParams('eventPopupSettings'),
|
||||
};
|
||||
}
|
||||
|
||||
if (flowKey && stepKey) {
|
||||
return super.getStepParams(flowKey, stepKey);
|
||||
}
|
||||
if (flowKey) {
|
||||
return super.getStepParams(flowKey);
|
||||
}
|
||||
return super.getStepParams();
|
||||
}
|
||||
|
||||
async preparePopupSettingsForFlowSettings(flowKey?: string, stepKey?: string) {
|
||||
if (flowKey && flowKey !== 'calendarSettings') {
|
||||
return;
|
||||
}
|
||||
|
||||
const stepKeys =
|
||||
stepKey && stepKey in CALENDAR_POPUP_SETTINGS_STEP_ACTIONS
|
||||
? [stepKey as CalendarPopupSettingsStepKey]
|
||||
: (Object.keys(CALENDAR_POPUP_SETTINGS_STEP_ACTIONS) as CalendarPopupSettingsStepKey[]);
|
||||
|
||||
for (const currentStepKey of stepKeys) {
|
||||
const actionKey = CALENDAR_POPUP_SETTINGS_STEP_ACTIONS[currentStepKey];
|
||||
const action = await this.ensurePopupAction(actionKey);
|
||||
await this.syncPopupActionSettings(action, actionKey);
|
||||
}
|
||||
}
|
||||
|
||||
async openFlowSettings(options?: Parameters<CollectionBlockModel['openFlowSettings']>[0]) {
|
||||
await this.preparePopupSettingsForFlowSettings(options?.flowKey, options?.stepKey);
|
||||
return super.openFlowSettings(options);
|
||||
}
|
||||
|
||||
async openStepSettingsDialog(flowKey: string, stepKey: string) {
|
||||
await this.preparePopupSettingsForFlowSettings(flowKey, stepKey);
|
||||
return super.openStepSettingsDialog(flowKey, stepKey);
|
||||
}
|
||||
|
||||
clearStoredPopupSettings(actionKey: 'quickCreateAction' | 'eventViewAction') {
|
||||
const propKey = this.getPopupSettingsPropKey(actionKey);
|
||||
this.setProps({
|
||||
[propKey]: undefined,
|
||||
});
|
||||
this.setStepParams('calendarSettings', {
|
||||
[propKey]: {},
|
||||
});
|
||||
}
|
||||
|
||||
hasPopupTemplateState(popupSettings?: Record<string, any>) {
|
||||
if (!popupSettings || typeof popupSettings !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const popupTemplateUid =
|
||||
typeof popupSettings.popupTemplateUid === 'string'
|
||||
? popupSettings.popupTemplateUid.trim()
|
||||
: popupSettings.popupTemplateUid;
|
||||
|
||||
return !!popupTemplateUid || popupSettings.popupTemplateContext === true;
|
||||
}
|
||||
|
||||
mergePopupTemplateSettings(baseSettings: Record<string, any>, popupSettings?: Record<string, any>) {
|
||||
if (!this.hasPopupTemplateState(popupSettings)) {
|
||||
return baseSettings;
|
||||
}
|
||||
|
||||
const nextSettings = { ...baseSettings };
|
||||
POPUP_TEMPLATE_SETTING_KEYS.forEach((key) => {
|
||||
if (Object.prototype.hasOwnProperty.call(popupSettings, key)) {
|
||||
nextSettings[key] = popupSettings[key];
|
||||
}
|
||||
});
|
||||
|
||||
if (popupSettings?.popupTemplateContext === true) {
|
||||
delete nextSettings.popupTemplateUid;
|
||||
delete nextSettings.popupTemplateHasFilterByTk;
|
||||
delete nextSettings.popupTemplateHasSourceId;
|
||||
} else if (popupSettings?.popupTemplateUid) {
|
||||
delete nextSettings.popupTemplateContext;
|
||||
}
|
||||
|
||||
return nextSettings;
|
||||
}
|
||||
|
||||
normalizePopupSettings(actionKey: 'quickCreateAction' | 'eventViewAction', popupSettings?: Record<string, any>) {
|
||||
@@ -331,27 +588,65 @@ export class CalendarBlockModel extends CollectionBlockModel {
|
||||
|
||||
if (
|
||||
popupTemplateUidProvided &&
|
||||
(popupTemplateUid === undefined || popupTemplateUid === null || popupTemplateUid === '')
|
||||
(popupTemplateUid === undefined || popupTemplateUid === null || popupTemplateUid === '') &&
|
||||
!isCalendarPopupTemplateCopyMode(nextParams)
|
||||
) {
|
||||
clearPopupTemplateParams(nextParams);
|
||||
}
|
||||
if (
|
||||
popupTemplateUidProvided &&
|
||||
(popupTemplateUid === undefined || popupTemplateUid === null || popupTemplateUid === '') &&
|
||||
isCalendarPopupTemplateCopyMode(nextParams)
|
||||
) {
|
||||
delete nextParams.popupTemplateUid;
|
||||
delete nextParams.popupTemplateContext;
|
||||
delete nextParams.popupTemplateHasFilterByTk;
|
||||
delete nextParams.popupTemplateHasSourceId;
|
||||
delete nextParams.uid;
|
||||
}
|
||||
|
||||
// Quick create is collection-scene; keep it isolated from record-scoped template state.
|
||||
if (actionKey === 'quickCreateAction' && nextParams.popupTemplateHasFilterByTk) {
|
||||
delete nextParams.popupTemplateUid;
|
||||
delete nextParams.popupTemplateContext;
|
||||
delete nextParams.popupTemplateHasFilterByTk;
|
||||
delete nextParams.popupTemplateHasSourceId;
|
||||
delete nextParams.uid;
|
||||
if (
|
||||
actionKey === 'quickCreateAction' &&
|
||||
(nextParams.popupTemplateHasFilterByTk === true || nextParams.popupTemplateHasSourceId === true)
|
||||
) {
|
||||
clearPopupTemplateParams(nextParams);
|
||||
} else if (actionKey === 'quickCreateAction') {
|
||||
clearPopupRecordScopeParams(nextParams);
|
||||
}
|
||||
|
||||
delete nextParams.popupTemplateUseModel;
|
||||
|
||||
return nextParams;
|
||||
}
|
||||
|
||||
async normalizeQuickCreatePopupTemplateContext(
|
||||
actionKey: CalendarPopupActionKey,
|
||||
popupSettings?: Record<string, any>,
|
||||
defaultUid?: string,
|
||||
) {
|
||||
const nextSettings = { ...(popupSettings || {}) };
|
||||
const templateUid =
|
||||
typeof nextSettings.popupTemplateUid === 'string'
|
||||
? nextSettings.popupTemplateUid.trim()
|
||||
: nextSettings.popupTemplateUid;
|
||||
if (actionKey !== 'quickCreateAction' || !templateUid) {
|
||||
return nextSettings;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await this.context?.api?.resource?.('flowModelTemplates')?.get?.({ filterByTk: templateUid });
|
||||
const template = res?.data?.data || {};
|
||||
const templateContext = inferCalendarPopupTemplateContextFlags(this.flowEngine, template);
|
||||
if (templateContext.hasFilterByTk || templateContext.hasSourceId) {
|
||||
clearPopupTemplateParams(nextSettings);
|
||||
if (defaultUid) {
|
||||
nextSettings.uid = defaultUid;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Keep current params when template metadata cannot be loaded.
|
||||
}
|
||||
|
||||
return nextSettings;
|
||||
}
|
||||
|
||||
setPopupSettings(actionKey: 'quickCreateAction' | 'eventViewAction', popupSettings?: Record<string, any>) {
|
||||
const propKey = this.getPopupSettingsPropKey(actionKey);
|
||||
const normalized = this.normalizePopupSettings(actionKey, popupSettings);
|
||||
@@ -361,17 +656,40 @@ export class CalendarBlockModel extends CollectionBlockModel {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
getPopupSettings(action: any, actionKey: 'quickCreateAction' | 'eventViewAction', actionUid?: string) {
|
||||
getPopupSettings(
|
||||
action: any,
|
||||
actionKey: 'quickCreateAction' | 'eventViewAction',
|
||||
actionUid?: string,
|
||||
options: CalendarPopupSettingsOptions = {},
|
||||
) {
|
||||
const defaults = this.getPopupSettingsDefaults(action?.uid || actionUid);
|
||||
const currentParams = this.getStoredPopupSettings(actionKey);
|
||||
|
||||
return {
|
||||
const actionParams = this.getPopupActionSettings(action);
|
||||
const currentParams =
|
||||
options.preferActionSettings !== false && Object.keys(actionParams).length > 0
|
||||
? actionParams
|
||||
: this.getStoredPopupSettings(actionKey);
|
||||
const popupSettings = {
|
||||
...defaults,
|
||||
...currentParams,
|
||||
uid: currentParams.uid || defaults.uid,
|
||||
collectionName: currentParams.collectionName || defaults.collectionName,
|
||||
dataSourceKey: currentParams.dataSourceKey || defaults.dataSourceKey,
|
||||
};
|
||||
const normalizeWithDefaults = (settings: Record<string, any>) => {
|
||||
const normalized = this.normalizePopupSettings(actionKey, settings);
|
||||
return {
|
||||
...normalized,
|
||||
uid: normalized.uid || defaults.uid,
|
||||
collectionName: normalized.collectionName || defaults.collectionName,
|
||||
dataSourceKey: normalized.dataSourceKey || defaults.dataSourceKey,
|
||||
};
|
||||
};
|
||||
|
||||
if (options.preferActionSettings === false) {
|
||||
return normalizeWithDefaults(popupSettings);
|
||||
}
|
||||
|
||||
return normalizeWithDefaults(this.mergePopupTemplateSettings(popupSettings, actionParams));
|
||||
}
|
||||
|
||||
async syncPopupActionSettings(
|
||||
@@ -383,17 +701,40 @@ export class CalendarBlockModel extends CollectionBlockModel {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextSettings = this.getPopupSettings(action, actionKey, action?.uid);
|
||||
const currentParams = action.getStepParams?.('popupSettings', 'openView') || {};
|
||||
if (JSON.stringify(currentParams) === JSON.stringify(nextSettings)) {
|
||||
const nextSettings = this.getPopupSettings(action, actionKey, action?.uid, {
|
||||
preferActionSettings: !options.persist,
|
||||
});
|
||||
const checkedSettings = await this.normalizeQuickCreatePopupTemplateContext(actionKey, nextSettings, action?.uid);
|
||||
const currentParams = this.getPopupActionSettings(action);
|
||||
if (JSON.stringify(currentParams) === JSON.stringify(checkedSettings)) {
|
||||
return;
|
||||
}
|
||||
|
||||
action.setStepParams('popupSettings', 'openView', nextSettings);
|
||||
await this.setPopupActionSettings(action, actionKey, checkedSettings, options);
|
||||
}
|
||||
|
||||
async setPopupActionSettings(
|
||||
action: any,
|
||||
actionKey: 'quickCreateAction' | 'eventViewAction',
|
||||
popupSettings?: Record<string, any>,
|
||||
options: CalendarPopupActionOptions = {},
|
||||
) {
|
||||
if (!action) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const normalized = this.normalizePopupSettings(actionKey, popupSettings);
|
||||
const nextSettings = await this.normalizeQuickCreatePopupTemplateContext(actionKey, normalized, action?.uid);
|
||||
replaceCalendarActionOpenViewParams(action, nextSettings);
|
||||
|
||||
if (options.persist && this.context.flowSettingsEnabled && action?.saveStepParams) {
|
||||
if (action?.save) {
|
||||
await action.save();
|
||||
}
|
||||
await action.saveStepParams();
|
||||
}
|
||||
|
||||
return nextSettings;
|
||||
}
|
||||
|
||||
async loadPopupAction(actionKey: 'quickCreateAction' | 'eventViewAction') {
|
||||
@@ -426,10 +767,6 @@ export class CalendarBlockModel extends CollectionBlockModel {
|
||||
action = this.subModels?.[actionKey] as any;
|
||||
}
|
||||
|
||||
if (options.persist && this.context.flowSettingsEnabled && action?.save) {
|
||||
await action.save();
|
||||
}
|
||||
|
||||
await this.syncPopupActionSettings(action, actionKey, options);
|
||||
|
||||
return action;
|
||||
@@ -458,11 +795,6 @@ export class CalendarBlockModel extends CollectionBlockModel {
|
||||
target: this.context?.layoutContentElement,
|
||||
};
|
||||
|
||||
if (typeof this.context?.openView === 'function' && action.uid) {
|
||||
await this.context.openView(action.uid, inputArgs);
|
||||
return;
|
||||
}
|
||||
|
||||
await action.dispatchEvent('click', inputArgs, { debounce: true });
|
||||
}
|
||||
|
||||
@@ -483,11 +815,6 @@ export class CalendarBlockModel extends CollectionBlockModel {
|
||||
target: this.context?.layoutContentElement,
|
||||
};
|
||||
|
||||
if (typeof this.context?.openView === 'function' && action.uid) {
|
||||
await this.context.openView(action.uid, inputArgs);
|
||||
return;
|
||||
}
|
||||
|
||||
await action.dispatchEvent('click', inputArgs, { debounce: true });
|
||||
}
|
||||
}
|
||||
@@ -828,16 +1155,34 @@ CalendarBlockModel.registerFlow({
|
||||
async defaultParams(ctx) {
|
||||
const model = ctx.model as CalendarBlockModel;
|
||||
const action = await model.ensurePopupAction('quickCreateAction');
|
||||
return model.getPopupSettings(action, 'quickCreateAction', action?.uid);
|
||||
const popupSettings = model.getPopupSettings(action, 'quickCreateAction', action?.uid);
|
||||
return model.normalizeQuickCreatePopupTemplateContext('quickCreateAction', popupSettings, action?.uid);
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
const model = ctx.model as CalendarBlockModel;
|
||||
model.setPopupSettings('quickCreateAction', params);
|
||||
const action = typeof model.getQuickCreateAction === 'function' ? model.getQuickCreateAction() : undefined;
|
||||
if (action) {
|
||||
await model.setPopupActionSettings?.(action, 'quickCreateAction', params);
|
||||
} else {
|
||||
model.setPopupSettings('quickCreateAction', params);
|
||||
}
|
||||
},
|
||||
async beforeParamsSave(ctx, params) {
|
||||
async beforeParamsSave(ctx, params, previousParams) {
|
||||
const model = ctx.model as CalendarBlockModel;
|
||||
model.setPopupSettings('quickCreateAction', params);
|
||||
await model.ensurePopupAction('quickCreateAction', { persist: true });
|
||||
const action = await model.ensurePopupAction('quickCreateAction');
|
||||
const storedParams =
|
||||
typeof model.getPopupActionSettings === 'function'
|
||||
? model.getPopupActionSettings(action)
|
||||
: action?.getStepParams?.('popupSettings', 'openView') || {};
|
||||
await model
|
||||
.getAction?.('openView')
|
||||
?.beforeParamsSave?.(ctx, params, Object.keys(storedParams).length > 0 ? storedParams : previousParams || {});
|
||||
if (typeof model.setPopupActionSettings === 'function') {
|
||||
await model.setPopupActionSettings(action, 'quickCreateAction', params, { persist: true });
|
||||
} else {
|
||||
model.setPopupSettings('quickCreateAction', params);
|
||||
}
|
||||
model.clearStoredPopupSettings?.('quickCreateAction');
|
||||
},
|
||||
},
|
||||
eventPopupSettings: {
|
||||
@@ -850,12 +1195,29 @@ CalendarBlockModel.registerFlow({
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
const model = ctx.model as CalendarBlockModel;
|
||||
model.setPopupSettings('eventViewAction', params);
|
||||
const action = typeof model.getEventViewAction === 'function' ? model.getEventViewAction() : undefined;
|
||||
if (action) {
|
||||
await model.setPopupActionSettings?.(action, 'eventViewAction', params);
|
||||
} else {
|
||||
model.setPopupSettings('eventViewAction', params);
|
||||
}
|
||||
},
|
||||
async beforeParamsSave(ctx, params) {
|
||||
async beforeParamsSave(ctx, params, previousParams) {
|
||||
const model = ctx.model as CalendarBlockModel;
|
||||
model.setPopupSettings('eventViewAction', params);
|
||||
await model.ensurePopupAction('eventViewAction', { persist: true });
|
||||
const action = await model.ensurePopupAction('eventViewAction');
|
||||
const storedParams =
|
||||
typeof model.getPopupActionSettings === 'function'
|
||||
? model.getPopupActionSettings(action)
|
||||
: action?.getStepParams?.('popupSettings', 'openView') || {};
|
||||
await model
|
||||
.getAction?.('openView')
|
||||
?.beforeParamsSave?.(ctx, params, Object.keys(storedParams).length > 0 ? storedParams : previousParams || {});
|
||||
if (typeof model.setPopupActionSettings === 'function') {
|
||||
await model.setPopupActionSettings(action, 'eventViewAction', params, { persist: true });
|
||||
} else {
|
||||
model.setPopupSettings('eventViewAction', params);
|
||||
}
|
||||
model.clearStoredPopupSettings?.('eventViewAction');
|
||||
},
|
||||
},
|
||||
showLunar: {
|
||||
|
||||
+481
-53
@@ -292,6 +292,92 @@ describe('calendarPopupModels', () => {
|
||||
expect(step?.hideInSettings?.({ model } as any)).toBe(true);
|
||||
});
|
||||
|
||||
it('should normalize quick create popup settings default params before rendering settings', async () => {
|
||||
const flow: any = (CalendarBlockModel as any).globalFlowRegistry.getFlow('calendarSettings');
|
||||
const step = flow?.steps?.quickCreatePopupSettings;
|
||||
const action = { uid: 'quick-action-uid' };
|
||||
const popupSettings = { popupTemplateUid: 'record-template-uid', uid: 'record-template-target-uid' };
|
||||
const normalizedSettings = { uid: 'quick-action-uid' };
|
||||
const model = {
|
||||
ensurePopupAction: vi.fn().mockResolvedValue(action),
|
||||
getPopupSettings: vi.fn(() => popupSettings),
|
||||
normalizeQuickCreatePopupTemplateContext: vi.fn().mockResolvedValue(normalizedSettings),
|
||||
};
|
||||
|
||||
await expect(step?.defaultParams?.({ model } as any)).resolves.toBe(normalizedSettings);
|
||||
expect(model.getPopupSettings).toHaveBeenCalledWith(action, 'quickCreateAction', action.uid);
|
||||
expect(model.normalizeQuickCreatePopupTemplateContext).toHaveBeenCalledWith(
|
||||
'quickCreateAction',
|
||||
popupSettings,
|
||||
action.uid,
|
||||
);
|
||||
});
|
||||
|
||||
it('should expose popup settings from hidden actions instead of stale calendar step params', () => {
|
||||
const model = Object.create(CalendarBlockModel.prototype) as CalendarBlockModel;
|
||||
Object.defineProperty(model, 'collection', {
|
||||
value: {
|
||||
name: 'events',
|
||||
dataSourceKey: 'main',
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(model, 'props', {
|
||||
value: {},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(model, 'stepParams', {
|
||||
value: {
|
||||
calendarSettings: {
|
||||
quickCreatePopupSettings: {
|
||||
uid: 'stale-template-target-uid',
|
||||
popupTemplateUid: 'stale-template-uid',
|
||||
},
|
||||
eventPopupSettings: {
|
||||
uid: 'stale-event-template-target-uid',
|
||||
popupTemplateUid: 'stale-event-template-uid',
|
||||
},
|
||||
},
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(model, 'subModels', {
|
||||
value: {
|
||||
quickCreateAction: {
|
||||
uid: 'quick-create-action-uid',
|
||||
getStepParams: vi.fn(() => ({
|
||||
mode: 'drawer',
|
||||
uid: 'quick-create-action-uid',
|
||||
})),
|
||||
},
|
||||
eventViewAction: {
|
||||
uid: 'event-view-action-uid',
|
||||
getStepParams: vi.fn(() => ({
|
||||
mode: 'dialog',
|
||||
uid: 'event-view-action-uid',
|
||||
})),
|
||||
},
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
expect(model.getStepParams('calendarSettings', 'quickCreatePopupSettings')).toMatchObject({
|
||||
mode: 'drawer',
|
||||
uid: 'quick-create-action-uid',
|
||||
collectionName: 'events',
|
||||
dataSourceKey: 'main',
|
||||
});
|
||||
expect(model.getStepParams('calendarSettings', 'quickCreatePopupSettings')).not.toMatchObject({
|
||||
popupTemplateUid: 'stale-template-uid',
|
||||
});
|
||||
expect(model.getStepParams('calendarSettings', 'eventPopupSettings')).toMatchObject({
|
||||
mode: 'dialog',
|
||||
uid: 'event-view-action-uid',
|
||||
collectionName: 'events',
|
||||
dataSourceKey: 'main',
|
||||
});
|
||||
});
|
||||
|
||||
it('should persist popup actions only from popup settings save hooks', async () => {
|
||||
const flow: any = (CalendarBlockModel as any).globalFlowRegistry.getFlow('calendarSettings');
|
||||
const quickCreateStep = flow?.steps?.quickCreatePopupSettings;
|
||||
@@ -310,8 +396,46 @@ describe('calendarPopupModels', () => {
|
||||
await quickCreateStep.beforeParamsSave({ model } as any, { mode: 'drawer' });
|
||||
await eventStep.beforeParamsSave({ model } as any, { mode: 'dialog' });
|
||||
|
||||
expect(ensurePopupAction).toHaveBeenCalledWith('quickCreateAction', { persist: true });
|
||||
expect(ensurePopupAction).toHaveBeenCalledWith('eventViewAction', { persist: true });
|
||||
expect(ensurePopupAction).toHaveBeenCalledWith('quickCreateAction');
|
||||
expect(ensurePopupAction).toHaveBeenCalledWith('eventViewAction');
|
||||
});
|
||||
|
||||
it('should delegate calendar popup settings save to openView beforeParamsSave', async () => {
|
||||
const flow: any = (CalendarBlockModel as any).globalFlowRegistry.getFlow('calendarSettings');
|
||||
const eventStep = flow?.steps?.eventPopupSettings;
|
||||
const beforeParamsSave = vi.fn(async (_ctx, params) => {
|
||||
delete params.popupTemplateUid;
|
||||
delete params.popupTemplateContext;
|
||||
delete params.popupTemplateHasFilterByTk;
|
||||
delete params.uid;
|
||||
});
|
||||
const setPopupSettings = vi.fn();
|
||||
const hiddenActionParams = {
|
||||
popupTemplateUid: 'hidden-template',
|
||||
uid: 'hidden-template-target',
|
||||
};
|
||||
const ensurePopupAction = vi.fn().mockResolvedValue({
|
||||
uid: 'calendar-action-uid',
|
||||
getStepParams: vi.fn(() => hiddenActionParams),
|
||||
});
|
||||
const params = {
|
||||
mode: 'dialog',
|
||||
popupTemplateUid: undefined,
|
||||
popupTemplateContext: true,
|
||||
popupTemplateHasFilterByTk: true,
|
||||
uid: 'stale-template-target',
|
||||
};
|
||||
const model = {
|
||||
getAction: vi.fn(() => ({ beforeParamsSave })),
|
||||
setPopupSettings,
|
||||
ensurePopupAction,
|
||||
};
|
||||
|
||||
await eventStep.beforeParamsSave({ model } as any, params, { popupTemplateUid: 'template-1' });
|
||||
|
||||
expect(beforeParamsSave).toHaveBeenCalledWith({ model }, params, hiddenActionParams);
|
||||
expect(setPopupSettings).toHaveBeenCalledWith('eventViewAction', { mode: 'dialog' });
|
||||
expect(ensurePopupAction).toHaveBeenCalledWith('eventViewAction');
|
||||
});
|
||||
|
||||
it('should build quick-create formData from the selected slot', () => {
|
||||
@@ -513,22 +637,65 @@ describe('calendarPopupModels', () => {
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const setStepParams = vi.fn();
|
||||
const action = {
|
||||
uid: 'calendar-action-uid',
|
||||
getStepParams: vi.fn(() => ({})),
|
||||
setStepParams,
|
||||
stepParams: {},
|
||||
};
|
||||
|
||||
await model.syncPopupActionSettings(action, 'eventViewAction');
|
||||
|
||||
expect(setStepParams).toHaveBeenCalledWith('popupSettings', 'openView', {
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
pageModelClass: 'ChildPageModel',
|
||||
uid: 'popup-template-target-uid',
|
||||
popupTemplateUid: 'popup-template-uid',
|
||||
popupTemplateHasFilterByTk: true,
|
||||
collectionName: 'events',
|
||||
dataSourceKey: 'main',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should prefer hidden popup action settings over legacy calendar popup props', async () => {
|
||||
const model = Object.create(CalendarBlockModel.prototype) as CalendarBlockModel;
|
||||
Object.defineProperty(model, 'collection', {
|
||||
value: {
|
||||
name: 'events',
|
||||
dataSourceKey: 'main',
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(model, 'props', {
|
||||
value: {
|
||||
eventPopupSettings: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'legacy-template-target',
|
||||
popupTemplateUid: 'legacy-template',
|
||||
},
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const action = {
|
||||
uid: 'calendar-action-uid',
|
||||
getStepParams: vi.fn(() => ({
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
uid: 'calendar-action-uid',
|
||||
})),
|
||||
};
|
||||
|
||||
expect(model.getPopupSettings(action, 'eventViewAction')).toEqual({
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
pageModelClass: 'ChildPageModel',
|
||||
uid: 'popup-template-target-uid',
|
||||
popupTemplateUid: 'popup-template-uid',
|
||||
popupTemplateHasFilterByTk: true,
|
||||
uid: 'calendar-action-uid',
|
||||
collectionName: 'events',
|
||||
dataSourceKey: 'main',
|
||||
});
|
||||
@@ -567,16 +734,55 @@ describe('calendarPopupModels', () => {
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const setStepParams = vi.fn();
|
||||
const action = {
|
||||
uid: 'calendar-quick-create-action-uid',
|
||||
getStepParams: vi.fn(() => ({})),
|
||||
setStepParams,
|
||||
stepParams: {},
|
||||
};
|
||||
|
||||
await model.syncPopupActionSettings(action, 'quickCreateAction');
|
||||
|
||||
expect(setStepParams).toHaveBeenCalledWith('popupSettings', 'openView', {
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
pageModelClass: 'ChildPageModel',
|
||||
uid: 'calendar-quick-create-action-uid',
|
||||
collectionName: 'events',
|
||||
dataSourceKey: 'main',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not expose event popup template params in quick create popup settings', () => {
|
||||
const model = Object.create(CalendarBlockModel.prototype) as CalendarBlockModel;
|
||||
Object.defineProperty(model, 'collection', {
|
||||
value: {
|
||||
name: 'events',
|
||||
dataSourceKey: 'main',
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(model, 'props', {
|
||||
value: {},
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const action = {
|
||||
uid: 'calendar-quick-create-action-uid',
|
||||
getStepParams: vi.fn(() => ({
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
uid: 'event-popup-template-target-uid',
|
||||
popupTemplateUid: 'event-popup-template-uid',
|
||||
filterByTk: '{{ ctx.record.id }}',
|
||||
popupTemplateHasFilterByTk: true,
|
||||
})),
|
||||
};
|
||||
|
||||
expect(model.getPopupSettings(action, 'quickCreateAction')).toEqual({
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
pageModelClass: 'ChildPageModel',
|
||||
@@ -586,6 +792,205 @@ describe('calendarPopupModels', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should clear persisted record-scoped popup template from quick create action', async () => {
|
||||
const model = Object.create(CalendarBlockModel.prototype) as CalendarBlockModel;
|
||||
Object.defineProperty(model, 'collection', {
|
||||
value: {
|
||||
name: 'events',
|
||||
dataSourceKey: 'main',
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(model, 'context', {
|
||||
value: {
|
||||
flowSettingsEnabled: false,
|
||||
api: {
|
||||
resource: vi.fn(() => ({
|
||||
get: vi.fn(async () => ({
|
||||
data: {
|
||||
data: {
|
||||
uid: 'event-template-uid',
|
||||
filterByTk: '{{ ctx.record.id }}',
|
||||
},
|
||||
},
|
||||
})),
|
||||
})),
|
||||
},
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(model, 'props', {
|
||||
value: {},
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const action = {
|
||||
uid: 'calendar-quick-create-action-uid',
|
||||
getStepParams: vi.fn(() => ({
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'event-popup-template-target-uid',
|
||||
popupTemplateUid: 'event-template-uid',
|
||||
collectionName: 'events',
|
||||
dataSourceKey: 'main',
|
||||
})),
|
||||
stepParams: {},
|
||||
};
|
||||
|
||||
await model.syncPopupActionSettings(action, 'quickCreateAction');
|
||||
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
pageModelClass: 'ChildPageModel',
|
||||
uid: 'calendar-quick-create-action-uid',
|
||||
collectionName: 'events',
|
||||
dataSourceKey: 'main',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep collection-scene quick create popup template with historical record default filterByTk', async () => {
|
||||
const model = Object.create(CalendarBlockModel.prototype) as CalendarBlockModel;
|
||||
Object.defineProperty(model, 'collection', {
|
||||
value: {
|
||||
name: 'events',
|
||||
dataSourceKey: 'main',
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(model, 'flowEngine', {
|
||||
value: {
|
||||
getModelClass: vi.fn((use: string) => {
|
||||
if (use !== 'AddNewActionModel') {
|
||||
return undefined;
|
||||
}
|
||||
return class AddNewActionModel {
|
||||
static _isScene(scene: string) {
|
||||
return scene === 'collection';
|
||||
}
|
||||
};
|
||||
}),
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(model, 'context', {
|
||||
value: {
|
||||
flowSettingsEnabled: false,
|
||||
api: {
|
||||
resource: vi.fn(() => ({
|
||||
get: vi.fn(async () => ({
|
||||
data: {
|
||||
data: {
|
||||
uid: 'collection-template-uid',
|
||||
useModel: 'AddNewActionModel',
|
||||
filterByTk: '{{ ctx.record.id }}',
|
||||
},
|
||||
},
|
||||
})),
|
||||
})),
|
||||
},
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(model, 'props', {
|
||||
value: {},
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const action = {
|
||||
uid: 'calendar-quick-create-action-uid',
|
||||
getStepParams: vi.fn(() => ({
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'collection-template-target-uid',
|
||||
popupTemplateUid: 'collection-template-uid',
|
||||
filterByTk: '{{ ctx.record.id }}',
|
||||
collectionName: 'events',
|
||||
dataSourceKey: 'main',
|
||||
})),
|
||||
stepParams: {},
|
||||
};
|
||||
|
||||
await model.syncPopupActionSettings(action, 'quickCreateAction');
|
||||
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
pageModelClass: 'ChildPageModel',
|
||||
uid: 'collection-template-target-uid',
|
||||
popupTemplateUid: 'collection-template-uid',
|
||||
collectionName: 'events',
|
||||
dataSourceKey: 'main',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep calendar popup template copy mode when the template uid is empty', async () => {
|
||||
const model = Object.create(CalendarBlockModel.prototype) as CalendarBlockModel;
|
||||
Object.defineProperty(model, 'collection', {
|
||||
value: {
|
||||
name: 'events',
|
||||
dataSourceKey: 'main',
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(model, 'context', {
|
||||
value: {
|
||||
flowSettingsEnabled: false,
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(model, 'props', {
|
||||
value: {},
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
(model as any).setProps = function setProps(next: Record<string, any>) {
|
||||
this.props = {
|
||||
...(this.props || {}),
|
||||
...next,
|
||||
};
|
||||
};
|
||||
|
||||
const action = {
|
||||
uid: 'calendar-action-uid',
|
||||
getStepParams: vi.fn(() => ({})),
|
||||
stepParams: {},
|
||||
};
|
||||
|
||||
model.setPopupSettings('eventViewAction', {
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
popupTemplateUid: undefined,
|
||||
popupTemplateContext: true,
|
||||
uid: 'copied-popup-uid',
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'template_events',
|
||||
});
|
||||
await model.syncPopupActionSettings(action, 'eventViewAction', { persist: true });
|
||||
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
pageModelClass: 'ChildPageModel',
|
||||
popupTemplateContext: true,
|
||||
uid: 'copied-popup-uid',
|
||||
collectionName: 'template_events',
|
||||
dataSourceKey: 'main',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should clear stale popup template params when event template is removed', async () => {
|
||||
const model = Object.create(CalendarBlockModel.prototype) as CalendarBlockModel;
|
||||
Object.defineProperty(model, 'collection', {
|
||||
@@ -623,7 +1028,6 @@ describe('calendarPopupModels', () => {
|
||||
};
|
||||
};
|
||||
|
||||
const setStepParams = vi.fn();
|
||||
const action = {
|
||||
uid: 'calendar-action-uid',
|
||||
getStepParams: vi.fn(() => ({
|
||||
@@ -635,7 +1039,19 @@ describe('calendarPopupModels', () => {
|
||||
popupTemplateHasFilterByTk: true,
|
||||
popupTemplateHasSourceId: true,
|
||||
})),
|
||||
setStepParams,
|
||||
stepParams: {
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'popup-template-target-uid',
|
||||
popupTemplateUid: 'popup-template-uid',
|
||||
popupTemplateContext: true,
|
||||
popupTemplateHasFilterByTk: true,
|
||||
popupTemplateHasSourceId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
model.setPopupSettings('eventViewAction', {
|
||||
@@ -643,15 +1059,19 @@ describe('calendarPopupModels', () => {
|
||||
size: 'large',
|
||||
popupTemplateUid: undefined,
|
||||
});
|
||||
await model.syncPopupActionSettings(action, 'eventViewAction');
|
||||
await model.syncPopupActionSettings(action, 'eventViewAction', { persist: true });
|
||||
|
||||
expect(setStepParams).toHaveBeenCalledWith('popupSettings', 'openView', {
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
pageModelClass: 'ChildPageModel',
|
||||
uid: 'calendar-action-uid',
|
||||
collectionName: 'events',
|
||||
dataSourceKey: 'main',
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
pageModelClass: 'ChildPageModel',
|
||||
uid: 'calendar-action-uid',
|
||||
collectionName: 'events',
|
||||
dataSourceKey: 'main',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -687,7 +1107,7 @@ describe('calendarPopupModels', () => {
|
||||
const action = {
|
||||
uid: 'u_event_popup',
|
||||
getStepParams: vi.fn(() => ({})),
|
||||
setStepParams: vi.fn(),
|
||||
stepParams: {},
|
||||
save,
|
||||
saveStepParams,
|
||||
};
|
||||
@@ -720,13 +1140,17 @@ describe('calendarPopupModels', () => {
|
||||
|
||||
expect(save).not.toHaveBeenCalled();
|
||||
expect(saveStepParams).not.toHaveBeenCalled();
|
||||
expect(action.setStepParams).toHaveBeenCalledWith('popupSettings', 'openView', {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
pageModelClass: 'ChildPageModel',
|
||||
uid: 'u_event_popup',
|
||||
collectionName: 'events',
|
||||
dataSourceKey: 'main',
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
pageModelClass: 'ChildPageModel',
|
||||
uid: 'u_event_popup',
|
||||
collectionName: 'events',
|
||||
dataSourceKey: 'main',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -828,9 +1252,9 @@ describe('calendarPopupModels', () => {
|
||||
expect(destroy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should open quick-create drawer through flow context openView with selected slot data', async () => {
|
||||
const openView = vi.fn().mockResolvedValue(undefined);
|
||||
const ensurePopupAction = vi.fn().mockResolvedValue({ uid: 'u_quick_create_popup' });
|
||||
it('should open quick-create drawer through the hidden action with selected slot data', async () => {
|
||||
const dispatchEvent = vi.fn().mockResolvedValue(undefined);
|
||||
const ensurePopupAction = vi.fn().mockResolvedValue({ uid: 'u_quick_create_popup', dispatchEvent });
|
||||
const slotInfo = {
|
||||
start: new Date(2026, 3, 20, 9, 30, 0),
|
||||
end: new Date(2026, 3, 20, 10, 30, 0),
|
||||
@@ -840,7 +1264,6 @@ describe('calendarPopupModels', () => {
|
||||
{
|
||||
props: {},
|
||||
context: {
|
||||
openView,
|
||||
layoutContentElement: { id: 'layout-root' },
|
||||
},
|
||||
collection: {
|
||||
@@ -857,26 +1280,28 @@ describe('calendarPopupModels', () => {
|
||||
);
|
||||
|
||||
expect(ensurePopupAction).toHaveBeenCalledWith('quickCreateAction');
|
||||
expect(openView).toHaveBeenCalledWith('u_quick_create_popup', {
|
||||
formData: {
|
||||
startsAt: '2026-04-20 09:30:00',
|
||||
endsAt: '2026-04-20 10:30:00',
|
||||
expect(dispatchEvent).toHaveBeenCalledWith(
|
||||
'click',
|
||||
{
|
||||
formData: {
|
||||
startsAt: '2026-04-20 09:30:00',
|
||||
endsAt: '2026-04-20 10:30:00',
|
||||
},
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'events',
|
||||
target: { id: 'layout-root' },
|
||||
},
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'events',
|
||||
target: { id: 'layout-root' },
|
||||
});
|
||||
expect(openView.mock.calls[0][0]).not.toContain('quickCreateAction');
|
||||
{ debounce: true },
|
||||
);
|
||||
});
|
||||
|
||||
it('should open event drawer through flow context openView with record filter key', async () => {
|
||||
const openView = vi.fn().mockResolvedValue(undefined);
|
||||
const ensurePopupAction = vi.fn().mockResolvedValue({ uid: 'u_event_view_popup' });
|
||||
it('should open event drawer through the hidden action with record filter key', async () => {
|
||||
const dispatchEvent = vi.fn().mockResolvedValue(undefined);
|
||||
const ensurePopupAction = vi.fn().mockResolvedValue({ uid: 'u_event_view_popup', dispatchEvent });
|
||||
|
||||
await CalendarBlockModel.prototype.openEvent.call(
|
||||
{
|
||||
context: {
|
||||
openView,
|
||||
layoutContentElement: { id: 'layout-root' },
|
||||
},
|
||||
collection: {
|
||||
@@ -891,12 +1316,15 @@ describe('calendarPopupModels', () => {
|
||||
);
|
||||
|
||||
expect(ensurePopupAction).toHaveBeenCalledWith('eventViewAction');
|
||||
expect(openView).toHaveBeenCalledWith('u_event_view_popup', {
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'events',
|
||||
filterByTk: 7,
|
||||
target: { id: 'layout-root' },
|
||||
});
|
||||
expect(openView.mock.calls[0][0]).not.toContain('eventViewAction');
|
||||
expect(dispatchEvent).toHaveBeenCalledWith(
|
||||
'click',
|
||||
{
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'events',
|
||||
filterByTk: 7,
|
||||
target: { id: 'layout-root' },
|
||||
},
|
||||
{ debounce: true },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+426
-127
@@ -201,14 +201,13 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
).toBe('zh:At least one option is required');
|
||||
});
|
||||
|
||||
test('card click uses flow context openView with the latest card-item props', async () => {
|
||||
const openView = vi.fn().mockResolvedValue(undefined);
|
||||
const ensureCardViewAction = vi.fn().mockResolvedValue({ uid: 'u_card_view_popup' });
|
||||
test('card click dispatches the hidden action with the latest card-item props', async () => {
|
||||
const dispatchEvent = vi.fn().mockResolvedValue(undefined);
|
||||
const ensureCardViewAction = vi.fn().mockResolvedValue({ uid: 'u_card_view_popup', dispatchEvent });
|
||||
|
||||
await KanbanBlockModel.prototype.openCard.call(
|
||||
{
|
||||
context: {
|
||||
openView,
|
||||
layoutContentElement: { id: 'layout-root' },
|
||||
},
|
||||
subModels: {
|
||||
@@ -229,11 +228,15 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
);
|
||||
|
||||
expect(ensureCardViewAction).toHaveBeenCalledTimes(1);
|
||||
expect(openView).toHaveBeenCalledWith('u_card_view_popup', {
|
||||
mode: 'dialog',
|
||||
filterByTk: 1,
|
||||
target: { id: 'layout-root' },
|
||||
});
|
||||
expect(dispatchEvent).toHaveBeenCalledWith(
|
||||
'click',
|
||||
{
|
||||
mode: 'dialog',
|
||||
filterByTk: 1,
|
||||
target: { id: 'layout-root' },
|
||||
},
|
||||
{ debounce: true },
|
||||
);
|
||||
});
|
||||
|
||||
test('configured select grouping keeps enum colors from the collection field', () => {
|
||||
@@ -264,16 +267,16 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
});
|
||||
|
||||
test('syncPopupAction persists pageModelClass alongside other popup settings', async () => {
|
||||
const setStepParams = vi.fn();
|
||||
const action = {
|
||||
getStepParams: () => ({ mode: 'drawer' }),
|
||||
stepParams: {},
|
||||
};
|
||||
|
||||
await KanbanBlockModel.prototype.syncPopupAction.call(
|
||||
{
|
||||
context: { flowSettingsEnabled: false },
|
||||
},
|
||||
{
|
||||
getStepParams: () => ({ mode: 'drawer' }),
|
||||
setStepParams,
|
||||
},
|
||||
action,
|
||||
{
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
@@ -283,27 +286,31 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(setStepParams).toHaveBeenCalledWith('popupSettings', 'openView', {
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
popupTemplateUid: 'template-1',
|
||||
uid: 'popup-1',
|
||||
pageModelClass: 'PopupPageModel',
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
popupTemplateUid: 'template-1',
|
||||
uid: 'popup-1',
|
||||
pageModelClass: 'PopupPageModel',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('syncPopupAction persists collection context for popup add-block menus', async () => {
|
||||
const setStepParams = vi.fn();
|
||||
const action = {
|
||||
uid: 'card-view-action',
|
||||
getStepParams: () => ({ mode: 'drawer' }),
|
||||
stepParams: {},
|
||||
};
|
||||
|
||||
await KanbanBlockModel.prototype.syncPopupAction.call(
|
||||
{
|
||||
context: { flowSettingsEnabled: false },
|
||||
},
|
||||
{
|
||||
uid: 'card-view-action',
|
||||
getStepParams: () => ({ mode: 'drawer' }),
|
||||
setStepParams,
|
||||
},
|
||||
action,
|
||||
{
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
@@ -313,14 +320,17 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(setStepParams).toHaveBeenCalledWith('popupSettings', 'openView', {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
popupTemplateUid: undefined,
|
||||
uid: 'card-view-action',
|
||||
pageModelClass: undefined,
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'tasks',
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'card-view-action',
|
||||
pageModelClass: undefined,
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'tasks',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -330,7 +340,7 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
const action = {
|
||||
uid: 'kanban-block-quick-create-action',
|
||||
getStepParams: vi.fn(() => ({})),
|
||||
setStepParams: vi.fn(),
|
||||
stepParams: {},
|
||||
save,
|
||||
saveStepParams,
|
||||
};
|
||||
@@ -364,7 +374,18 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
|
||||
expect(save).not.toHaveBeenCalled();
|
||||
expect(saveStepParams).not.toHaveBeenCalled();
|
||||
expect(action.setStepParams).toHaveBeenCalled();
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'kanban-block-quick-create-action',
|
||||
pageModelClass: undefined,
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'tasks',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('persists hidden popup actions only when kanban popup settings are saved', async () => {
|
||||
@@ -517,73 +538,157 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
expect(destroy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('syncPopupAction overwrites removed popup settings with undefined so stale template params do not survive merges', async () => {
|
||||
const setStepParams = vi.fn();
|
||||
test('syncPopupAction replaces removed popup settings so stale template params do not survive', async () => {
|
||||
const action = {
|
||||
uid: 'kanban-quick-create-action',
|
||||
getStepParams: () => ({
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
popupTemplateUid: 'template-1',
|
||||
pageModelClass: 'PopupPageModel',
|
||||
}),
|
||||
stepParams: {
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
popupTemplateUid: 'template-1',
|
||||
pageModelClass: 'PopupPageModel',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await KanbanBlockModel.prototype.syncPopupAction.call(
|
||||
{
|
||||
context: { flowSettingsEnabled: false },
|
||||
},
|
||||
{
|
||||
uid: 'kanban-quick-create-action',
|
||||
getStepParams: () => ({
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
popupTemplateUid: 'template-1',
|
||||
pageModelClass: 'PopupPageModel',
|
||||
}),
|
||||
setStepParams,
|
||||
},
|
||||
action,
|
||||
{
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
},
|
||||
{ persist: true },
|
||||
);
|
||||
|
||||
expect(setStepParams).toHaveBeenCalledWith('popupSettings', 'openView', {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
popupTemplateUid: undefined,
|
||||
pageModelClass: undefined,
|
||||
uid: 'kanban-quick-create-action',
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'kanban-quick-create-action',
|
||||
pageModelClass: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('syncPopupAction drops stale template-derived popup params after the template is cleared', async () => {
|
||||
const setStepParams = vi.fn();
|
||||
const action = {
|
||||
uid: 'kanban-block-1-quick-create-action',
|
||||
getStepParams: () => ({
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
popupTemplateUid: 'template-1',
|
||||
uid: 'template-popup-1',
|
||||
dataSourceKey: 'secondary',
|
||||
collectionName: 'archived_tasks',
|
||||
associationName: 'users.tasks',
|
||||
popupTemplateContext: true,
|
||||
}),
|
||||
stepParams: {
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
popupTemplateUid: 'template-1',
|
||||
uid: 'template-popup-1',
|
||||
dataSourceKey: 'secondary',
|
||||
collectionName: 'archived_tasks',
|
||||
associationName: 'users.tasks',
|
||||
popupTemplateContext: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await KanbanBlockModel.prototype.syncPopupAction.call(
|
||||
{
|
||||
uid: 'kanban-block-1',
|
||||
context: { flowSettingsEnabled: false },
|
||||
},
|
||||
{
|
||||
uid: 'kanban-block-1-quick-create-action',
|
||||
getStepParams: () => ({
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
popupTemplateUid: 'template-1',
|
||||
uid: 'template-popup-1',
|
||||
dataSourceKey: 'secondary',
|
||||
collectionName: 'archived_tasks',
|
||||
associationName: 'users.tasks',
|
||||
popupTemplateContext: true,
|
||||
}),
|
||||
setStepParams,
|
||||
},
|
||||
action,
|
||||
{
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'template-popup-1',
|
||||
},
|
||||
{ persist: true },
|
||||
);
|
||||
|
||||
expect(setStepParams).toHaveBeenCalledWith('popupSettings', 'openView', {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
popupTemplateUid: undefined,
|
||||
pageModelClass: undefined,
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'kanban-block-1-quick-create-action',
|
||||
pageModelClass: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('syncPopupAction keeps popup template copy mode during runtime sync', async () => {
|
||||
const action = {
|
||||
uid: 'kanban-block-1-quick-create-action',
|
||||
getStepParams: () => ({
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'copied-popup-1',
|
||||
dataSourceKey: 'secondary',
|
||||
collectionName: 'template_tasks',
|
||||
popupTemplateContext: true,
|
||||
}),
|
||||
stepParams: {
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'copied-popup-1',
|
||||
dataSourceKey: 'secondary',
|
||||
collectionName: 'template_tasks',
|
||||
popupTemplateContext: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await KanbanBlockModel.prototype.syncPopupAction.call(
|
||||
{
|
||||
uid: 'kanban-block-1',
|
||||
context: { flowSettingsEnabled: false },
|
||||
},
|
||||
action,
|
||||
{
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'tasks',
|
||||
},
|
||||
);
|
||||
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
uid: 'copied-popup-1',
|
||||
pageModelClass: undefined,
|
||||
dataSourceKey: 'secondary',
|
||||
collectionName: 'template_tasks',
|
||||
popupTemplateContext: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -630,7 +735,6 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
const setProps = vi.fn(function (this: any, nextProps) {
|
||||
Object.assign(this.props, nextProps);
|
||||
});
|
||||
const setStepParams = vi.fn();
|
||||
const action = {
|
||||
uid: 'quick-create-action',
|
||||
getStepParams: () => ({
|
||||
@@ -639,7 +743,16 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
popupTemplateUid: 'template-1',
|
||||
uid: 'template-popup-1',
|
||||
}),
|
||||
setStepParams,
|
||||
stepParams: {
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
popupTemplateUid: 'template-1',
|
||||
uid: 'template-popup-1',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const model: any = {
|
||||
props: {
|
||||
@@ -694,16 +807,103 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
|
||||
expect(model.props.popupTargetUid).toBeUndefined();
|
||||
expect(model.stepParams.kanbanSettings.popup.uid).toBeUndefined();
|
||||
expect(setStepParams).toHaveBeenCalledWith('popupSettings', 'openView', {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
popupTemplateUid: undefined,
|
||||
pageModelClass: undefined,
|
||||
uid: 'quick-create-action',
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
pageModelClass: undefined,
|
||||
uid: 'quick-create-action',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(model.ensureQuickCreateAction).toHaveBeenCalledWith({ persist: true });
|
||||
});
|
||||
|
||||
test('popup settings beforeParamsSave keeps copied popup template state', async () => {
|
||||
const flow: any = (KanbanBlockModel as any).globalFlowRegistry.getFlow('kanbanSettings');
|
||||
const step: any = flow?.steps?.popup;
|
||||
const setProps = vi.fn(function (this: any, nextProps) {
|
||||
Object.assign(this.props, nextProps);
|
||||
});
|
||||
const action = {
|
||||
uid: 'quick-create-action',
|
||||
getStepParams: () => ({}),
|
||||
stepParams: {},
|
||||
};
|
||||
const model: any = {
|
||||
props: {
|
||||
popupTemplateUid: 'template-1',
|
||||
popupTargetUid: 'copied-popup-1',
|
||||
},
|
||||
stepParams: {},
|
||||
collection: {
|
||||
dataSourceKey: 'main',
|
||||
name: 'tasks',
|
||||
},
|
||||
context: { flowSettingsEnabled: false },
|
||||
emitter: { emit: vi.fn() },
|
||||
setProps,
|
||||
getPopupTemplateUid() {
|
||||
return this.props.popupTemplateUid;
|
||||
},
|
||||
getPopupTargetUid() {
|
||||
return this.props.popupTargetUid;
|
||||
},
|
||||
getAction: () => ({ beforeParamsSave: vi.fn().mockResolvedValue(undefined) }),
|
||||
ensureQuickCreateAction: vi.fn(async function (this: any, options) {
|
||||
await this.syncQuickCreateAction(action, options);
|
||||
return action;
|
||||
}),
|
||||
syncQuickCreateAction: KanbanBlockModel.prototype.syncQuickCreateAction,
|
||||
syncPopupAction: KanbanBlockModel.prototype.syncPopupAction,
|
||||
getPopupMode: () => 'drawer',
|
||||
getPopupSize: () => 'medium',
|
||||
getPopupPageModelClass: () => undefined,
|
||||
getQuickCreateActionUid: () => 'quick-create-action',
|
||||
uid: 'kanban-block-1',
|
||||
};
|
||||
|
||||
await step.beforeParamsSave(
|
||||
{
|
||||
model,
|
||||
} as any,
|
||||
{
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
popupTemplateUid: undefined,
|
||||
popupTemplateContext: true,
|
||||
uid: 'copied-popup-1',
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'template_tasks',
|
||||
},
|
||||
{
|
||||
popupTemplateUid: 'template-1',
|
||||
uid: 'copied-popup-1',
|
||||
},
|
||||
);
|
||||
|
||||
expect(model.props.popupTargetUid).toBe('copied-popup-1');
|
||||
expect(model.stepParams.kanbanSettings.popup).toMatchObject({
|
||||
popupTemplateContext: true,
|
||||
uid: 'copied-popup-1',
|
||||
collectionName: 'template_tasks',
|
||||
});
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
pageModelClass: undefined,
|
||||
popupTemplateContext: true,
|
||||
uid: 'copied-popup-1',
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'template_tasks',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('card popup getter treats an explicitly cleared item template as higher priority than legacy block props', () => {
|
||||
expect(
|
||||
KanbanBlockModel.prototype.getCardPopupTemplateUid.call({
|
||||
@@ -1804,6 +2004,86 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
expect(syncQuickCreateAction).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('block popup settings default params load hidden quick-create action template params', async () => {
|
||||
const flow: any = (KanbanBlockModel as any).globalFlowRegistry.getFlow('kanbanSettings');
|
||||
const step: any = flow?.steps?.popup;
|
||||
const hiddenActionParams = {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'template-popup-1',
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'template_tasks',
|
||||
popupTemplateContext: true,
|
||||
};
|
||||
const ensureQuickCreateAction = vi.fn().mockResolvedValue({
|
||||
uid: 'quick-create-action',
|
||||
getStepParams: vi.fn(() => hiddenActionParams),
|
||||
});
|
||||
|
||||
const defaultParams = await step.defaultParams({
|
||||
model: {
|
||||
props: {},
|
||||
ensureQuickCreateAction,
|
||||
getPopupMode: () => 'dialog',
|
||||
getPopupSize: () => 'large',
|
||||
getPopupTemplateUid: () => undefined,
|
||||
getPopupTargetUid: () => undefined,
|
||||
getPopupPageModelClass: () => undefined,
|
||||
},
|
||||
} as any);
|
||||
|
||||
expect(ensureQuickCreateAction).toHaveBeenCalledWith();
|
||||
expect(defaultParams).toMatchObject({
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'template-popup-1',
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'template_tasks',
|
||||
popupTemplateContext: true,
|
||||
});
|
||||
expect(defaultParams).not.toHaveProperty('popupTemplateUid');
|
||||
});
|
||||
|
||||
test('block popup settings save delegates previous params from hidden quick-create action', async () => {
|
||||
const flow: any = (KanbanBlockModel as any).globalFlowRegistry.getFlow('kanbanSettings');
|
||||
const step: any = flow?.steps?.popup;
|
||||
const openViewBeforeParamsSave = vi.fn().mockResolvedValue(undefined);
|
||||
const hiddenActionParams = {
|
||||
popupTemplateUid: 'template-1',
|
||||
uid: 'template-popup-1',
|
||||
};
|
||||
const ensureQuickCreateAction = vi.fn().mockResolvedValue({
|
||||
uid: 'quick-create-action',
|
||||
getStepParams: vi.fn(() => hiddenActionParams),
|
||||
});
|
||||
const setProps = vi.fn();
|
||||
|
||||
await step.beforeParamsSave(
|
||||
{
|
||||
model: {
|
||||
props: {},
|
||||
context: { flowSettingsEnabled: false },
|
||||
setProps,
|
||||
ensureQuickCreateAction,
|
||||
getAction: () => ({ beforeParamsSave: openViewBeforeParamsSave }),
|
||||
},
|
||||
} as any,
|
||||
{
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
},
|
||||
{
|
||||
popupTemplateUid: 'settings-step-template',
|
||||
},
|
||||
);
|
||||
|
||||
expect(openViewBeforeParamsSave).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({ mode: 'drawer', size: 'medium' }),
|
||||
hiddenActionParams,
|
||||
);
|
||||
});
|
||||
|
||||
test('kanban model wraps openView popup template selector without changing the global action', async () => {
|
||||
const engine = new FlowEngine();
|
||||
engine.registerModels({ KanbanBlockModel });
|
||||
@@ -1898,18 +2178,18 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
});
|
||||
|
||||
test('syncPopupAction falls back to the popup action uid when the target uid resolves to the kanban block itself', async () => {
|
||||
const setStepParams = vi.fn();
|
||||
const action = {
|
||||
uid: 'kanban-block-1-quick-create-action',
|
||||
getStepParams: () => ({ mode: 'drawer', uid: 'kanban-block-1' }),
|
||||
stepParams: {},
|
||||
};
|
||||
|
||||
await KanbanBlockModel.prototype.syncPopupAction.call(
|
||||
{
|
||||
uid: 'kanban-block-1',
|
||||
context: { flowSettingsEnabled: false },
|
||||
},
|
||||
{
|
||||
uid: 'kanban-block-1-quick-create-action',
|
||||
getStepParams: () => ({ mode: 'drawer', uid: 'kanban-block-1' }),
|
||||
setStepParams,
|
||||
},
|
||||
action,
|
||||
{
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
@@ -1917,47 +2197,56 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(setStepParams).toHaveBeenCalledWith('popupSettings', 'openView', {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'kanban-block-1-quick-create-action',
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'kanban-block-1-quick-create-action',
|
||||
pageModelClass: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('syncPopupAction keeps the popup action uid when there is no external popup target uid', async () => {
|
||||
const setStepParams = vi.fn();
|
||||
const action = {
|
||||
uid: 'kanban-block-1-quick-create-action',
|
||||
getStepParams: () => ({ mode: 'drawer' }),
|
||||
stepParams: {},
|
||||
};
|
||||
|
||||
await KanbanBlockModel.prototype.syncPopupAction.call(
|
||||
{
|
||||
uid: 'kanban-block-1',
|
||||
context: { flowSettingsEnabled: false },
|
||||
},
|
||||
{
|
||||
uid: 'kanban-block-1-quick-create-action',
|
||||
getStepParams: () => ({ mode: 'drawer' }),
|
||||
setStepParams,
|
||||
},
|
||||
action,
|
||||
{
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
},
|
||||
);
|
||||
|
||||
expect(setStepParams).toHaveBeenCalledWith('popupSettings', 'openView', {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'kanban-block-1-quick-create-action',
|
||||
expect(action.stepParams).toEqual({
|
||||
popupSettings: {
|
||||
openView: {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'kanban-block-1-quick-create-action',
|
||||
pageModelClass: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('quick create uses flow context openView with the prefilled form data', async () => {
|
||||
const openView = vi.fn().mockResolvedValue(undefined);
|
||||
const ensureQuickCreateAction = vi.fn().mockResolvedValue({ uid: 'u_quick_create_popup' });
|
||||
test('quick create dispatches the hidden action with the prefilled form data', async () => {
|
||||
const dispatchEvent = vi.fn().mockResolvedValue(undefined);
|
||||
const ensureQuickCreateAction = vi.fn().mockResolvedValue({ uid: 'u_quick_create_popup', dispatchEvent });
|
||||
|
||||
await KanbanBlockModel.prototype.openQuickCreate.call(
|
||||
{
|
||||
context: {
|
||||
openView,
|
||||
layoutContentElement: { id: 'layout-root' },
|
||||
},
|
||||
props: {
|
||||
@@ -1971,21 +2260,24 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
);
|
||||
|
||||
expect(ensureQuickCreateAction).toHaveBeenCalledTimes(1);
|
||||
expect(openView).toHaveBeenCalledWith('u_quick_create_popup', {
|
||||
formData: { status: 'todo' },
|
||||
target: { id: 'layout-root' },
|
||||
});
|
||||
expect(dispatchEvent).toHaveBeenCalledWith(
|
||||
'click',
|
||||
{
|
||||
formData: { status: 'todo' },
|
||||
target: { id: 'layout-root' },
|
||||
},
|
||||
{ debounce: true },
|
||||
);
|
||||
});
|
||||
|
||||
test('quick create falls back to an empty popup shell when the popup action open fails', async () => {
|
||||
const open = vi.fn().mockResolvedValue(undefined);
|
||||
const openView = vi.fn().mockRejectedValue(new Error('open failed'));
|
||||
const ensureQuickCreateAction = vi.fn().mockResolvedValue({ uid: 'u_quick_create_popup' });
|
||||
const dispatchEvent = vi.fn().mockRejectedValue(new Error('open failed'));
|
||||
const ensureQuickCreateAction = vi.fn().mockResolvedValue({ uid: 'u_quick_create_popup', dispatchEvent });
|
||||
|
||||
await KanbanBlockModel.prototype.openQuickCreate.call(
|
||||
{
|
||||
context: {
|
||||
openView,
|
||||
viewer: { open },
|
||||
layoutContentElement: { id: 'layout-root' },
|
||||
},
|
||||
@@ -2004,10 +2296,14 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
);
|
||||
|
||||
expect(ensureQuickCreateAction).toHaveBeenCalledTimes(1);
|
||||
expect(openView).toHaveBeenCalledWith('u_quick_create_popup', {
|
||||
formData: { status: 'todo' },
|
||||
target: { id: 'layout-root' },
|
||||
});
|
||||
expect(dispatchEvent).toHaveBeenCalledWith(
|
||||
'click',
|
||||
{
|
||||
formData: { status: 'todo' },
|
||||
target: { id: 'layout-root' },
|
||||
},
|
||||
{ debounce: true },
|
||||
);
|
||||
expect(open).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'drawer',
|
||||
@@ -2020,13 +2316,12 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
|
||||
test('card click falls back to an empty popup shell when the popup action open fails', async () => {
|
||||
const open = vi.fn().mockResolvedValue(undefined);
|
||||
const openView = vi.fn().mockRejectedValue(new Error('open failed'));
|
||||
const ensureCardViewAction = vi.fn().mockResolvedValue({ uid: 'u_card_view_popup' });
|
||||
const dispatchEvent = vi.fn().mockRejectedValue(new Error('open failed'));
|
||||
const ensureCardViewAction = vi.fn().mockResolvedValue({ uid: 'u_card_view_popup', dispatchEvent });
|
||||
|
||||
await KanbanBlockModel.prototype.openCard.call(
|
||||
{
|
||||
context: {
|
||||
openView,
|
||||
viewer: { open },
|
||||
layoutContentElement: { id: 'layout-root' },
|
||||
},
|
||||
@@ -2047,13 +2342,17 @@ describe('KanbanBlockModel.filterCollection', () => {
|
||||
);
|
||||
|
||||
expect(ensureCardViewAction).toHaveBeenCalledTimes(1);
|
||||
expect(openView).toHaveBeenCalledWith('u_card_view_popup', {
|
||||
mode: 'dialog',
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'tasks',
|
||||
filterByTk: 1,
|
||||
target: { id: 'layout-root' },
|
||||
});
|
||||
expect(dispatchEvent).toHaveBeenCalledWith(
|
||||
'click',
|
||||
{
|
||||
mode: 'dialog',
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'tasks',
|
||||
filterByTk: 1,
|
||||
target: { id: 'layout-root' },
|
||||
},
|
||||
{ debounce: true },
|
||||
);
|
||||
expect(open).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'dialog',
|
||||
|
||||
+133
@@ -102,6 +102,41 @@ describe('KanbanCardItemModel.cardSettings', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('popup default params load hidden card-view action template params', async () => {
|
||||
const flow: any = (KanbanCardItemModel as any).globalFlowRegistry.getFlow('cardSettings');
|
||||
const hiddenActionParams = {
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'template-card-popup',
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'template_tasks',
|
||||
popupTemplateContext: true,
|
||||
};
|
||||
const ensureCardViewAction = vi.fn().mockResolvedValue({
|
||||
uid: 'card-view-action',
|
||||
getStepParams: vi.fn(() => hiddenActionParams),
|
||||
});
|
||||
const cardItem = {
|
||||
props: {},
|
||||
parent: {
|
||||
ensureCardViewAction,
|
||||
},
|
||||
};
|
||||
|
||||
const defaultParams = await flow.steps.popup.defaultParams({ model: cardItem } as any);
|
||||
|
||||
expect(ensureCardViewAction).toHaveBeenCalledWith();
|
||||
expect(defaultParams).toMatchObject({
|
||||
mode: 'drawer',
|
||||
size: 'medium',
|
||||
uid: 'template-card-popup',
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'template_tasks',
|
||||
popupTemplateContext: true,
|
||||
});
|
||||
expect(defaultParams).not.toHaveProperty('popupTemplateUid');
|
||||
});
|
||||
|
||||
test('popup handler clears a stale card popup target uid when removing the popup template', async () => {
|
||||
const flow: any = (KanbanCardItemModel as any).globalFlowRegistry.getFlow('cardSettings');
|
||||
const syncCardViewAction = vi.fn();
|
||||
@@ -196,6 +231,104 @@ describe('KanbanCardItemModel.cardSettings', () => {
|
||||
expect(ensureCardViewAction).toHaveBeenCalledWith({ persist: true });
|
||||
});
|
||||
|
||||
test('popup beforeParamsSave keeps copied card popup template state', async () => {
|
||||
const flow: any = (KanbanCardItemModel as any).globalFlowRegistry.getFlow('cardSettings');
|
||||
const ensureCardViewAction = vi.fn().mockResolvedValue({ uid: 'card-view-action' });
|
||||
const masterBlock = {
|
||||
subModels: {
|
||||
cardViewAction: { uid: 'card-view-action' },
|
||||
},
|
||||
ensureCardViewAction,
|
||||
setProps: vi.fn(),
|
||||
};
|
||||
const masterItem: any = {
|
||||
props: {
|
||||
popupTemplateUid: 'tpl-card',
|
||||
popupTargetUid: 'copied-card-popup',
|
||||
},
|
||||
stepParams: {},
|
||||
emitter: { emit: vi.fn() },
|
||||
parent: masterBlock,
|
||||
setProps: vi.fn(function (this: any, nextProps) {
|
||||
Object.assign(this.props, nextProps);
|
||||
}),
|
||||
getAction: () => ({ beforeParamsSave: vi.fn().mockResolvedValue(undefined) }),
|
||||
};
|
||||
|
||||
await flow.steps.popup.beforeParamsSave(
|
||||
{
|
||||
model: masterItem,
|
||||
} as any,
|
||||
{
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
popupTemplateUid: undefined,
|
||||
popupTemplateContext: true,
|
||||
uid: 'copied-card-popup',
|
||||
dataSourceKey: 'main',
|
||||
collectionName: 'template_tasks',
|
||||
},
|
||||
{
|
||||
popupTemplateUid: 'tpl-card',
|
||||
uid: 'copied-card-popup',
|
||||
},
|
||||
);
|
||||
|
||||
expect(masterItem.props.popupTargetUid).toBe('copied-card-popup');
|
||||
expect(masterItem.stepParams.cardSettings.popup).toMatchObject({
|
||||
popupTemplateContext: true,
|
||||
uid: 'copied-card-popup',
|
||||
collectionName: 'template_tasks',
|
||||
});
|
||||
expect(ensureCardViewAction).toHaveBeenCalledWith({ persist: true });
|
||||
});
|
||||
|
||||
test('popup beforeParamsSave delegates previous params from hidden card-view action', async () => {
|
||||
const flow: any = (KanbanCardItemModel as any).globalFlowRegistry.getFlow('cardSettings');
|
||||
const openViewBeforeParamsSave = vi.fn().mockResolvedValue(undefined);
|
||||
const hiddenActionParams = {
|
||||
popupTemplateUid: 'tpl-card',
|
||||
uid: 'popup-card-1',
|
||||
};
|
||||
const ensureCardViewAction = vi.fn().mockResolvedValue({
|
||||
uid: 'card-view-action',
|
||||
getStepParams: vi.fn(() => hiddenActionParams),
|
||||
});
|
||||
const masterBlock = {
|
||||
ensureCardViewAction,
|
||||
setProps: vi.fn(),
|
||||
};
|
||||
const masterItem: any = {
|
||||
props: {},
|
||||
stepParams: {},
|
||||
emitter: { emit: vi.fn() },
|
||||
parent: masterBlock,
|
||||
setProps: vi.fn(function (this: any, nextProps) {
|
||||
Object.assign(this.props, nextProps);
|
||||
}),
|
||||
getAction: () => ({ beforeParamsSave: openViewBeforeParamsSave }),
|
||||
};
|
||||
|
||||
await flow.steps.popup.beforeParamsSave(
|
||||
{
|
||||
model: masterItem,
|
||||
} as any,
|
||||
{
|
||||
mode: 'dialog',
|
||||
size: 'large',
|
||||
},
|
||||
{
|
||||
popupTemplateUid: 'settings-step-template',
|
||||
},
|
||||
);
|
||||
|
||||
expect(openViewBeforeParamsSave).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({ mode: 'dialog', size: 'large' }),
|
||||
hiddenActionParams,
|
||||
);
|
||||
});
|
||||
|
||||
test('layout changes persist from card forks and propagate to details items', () => {
|
||||
const flow: any = (KanbanCardItemModel as any).globalFlowRegistry.getFlow('cardSettings');
|
||||
const detailsItem = {
|
||||
|
||||
+152
-56
@@ -88,6 +88,20 @@ type KanbanPopupActionOptions = {
|
||||
persist?: boolean;
|
||||
};
|
||||
|
||||
const POPUP_TEMPLATE_SETTING_KEYS = [
|
||||
'uid',
|
||||
'dataSourceKey',
|
||||
'collectionName',
|
||||
'associationName',
|
||||
'filterByTk',
|
||||
'sourceId',
|
||||
'popupTemplateUid',
|
||||
'popupTemplateMode',
|
||||
'popupTemplateContext',
|
||||
'popupTemplateHasFilterByTk',
|
||||
'popupTemplateHasSourceId',
|
||||
];
|
||||
|
||||
const DRAG_SORT_FIELD_TIP =
|
||||
'Choose the sorting field that matches the current grouping field. Other sorting fields cannot be used for drag sorting.';
|
||||
const DRAG_SORT_FIELD_TIP_EXPR = tExpr(DRAG_SORT_FIELD_TIP, { ns: 'kanban' });
|
||||
@@ -135,6 +149,73 @@ const replaceModelStepParams = (model: any, flowKey: string, stepKey: string, pa
|
||||
model.emitter?.emit?.('onStepParamsChanged');
|
||||
};
|
||||
|
||||
const getModelStepParams = (model: any, flowKey: string, stepKey: string) => {
|
||||
const stepParams = model?.stepParams?.[flowKey]?.[stepKey];
|
||||
if (stepParams) {
|
||||
return stepParams;
|
||||
}
|
||||
|
||||
try {
|
||||
return model?.getStepParams?.(flowKey, stepKey) || {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const hasKanbanPopupTemplateState = (params?: Record<string, any>) => {
|
||||
if (!params || typeof params !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!normalizeKanbanPopupTemplateUid(params.popupTemplateUid) || params.popupTemplateContext === true;
|
||||
};
|
||||
|
||||
const isKanbanPopupTemplateCopyMode = (params?: Record<string, any>) => {
|
||||
return params?.popupTemplateContext === true;
|
||||
};
|
||||
|
||||
const mergeKanbanPopupTemplateSettings = (baseSettings: Record<string, any>, popupSettings?: Record<string, any>) => {
|
||||
if (!hasKanbanPopupTemplateState(popupSettings)) {
|
||||
return baseSettings;
|
||||
}
|
||||
|
||||
const nextSettings = { ...baseSettings };
|
||||
POPUP_TEMPLATE_SETTING_KEYS.forEach((key) => {
|
||||
if (Object.prototype.hasOwnProperty.call(popupSettings, key)) {
|
||||
nextSettings[key] = popupSettings[key];
|
||||
}
|
||||
});
|
||||
|
||||
if (popupSettings?.popupTemplateContext === true) {
|
||||
delete nextSettings.popupTemplateUid;
|
||||
delete nextSettings.popupTemplateHasFilterByTk;
|
||||
delete nextSettings.popupTemplateHasSourceId;
|
||||
} else if (popupSettings?.popupTemplateUid) {
|
||||
delete nextSettings.popupTemplateContext;
|
||||
}
|
||||
|
||||
return nextSettings;
|
||||
};
|
||||
|
||||
const getKanbanPopupActionSettings = (action: any) => getModelStepParams(action, 'popupSettings', 'openView');
|
||||
|
||||
const buildKanbanPopupSettingsFromAction = (baseSettings: Record<string, any>, action: any) => {
|
||||
const actionParams = getKanbanPopupActionSettings(action);
|
||||
if (Object.keys(actionParams).length === 0) {
|
||||
return baseSettings;
|
||||
}
|
||||
|
||||
const popupSettings = {
|
||||
...baseSettings,
|
||||
...actionParams,
|
||||
uid: actionParams.uid || baseSettings.uid,
|
||||
collectionName: actionParams.collectionName || baseSettings.collectionName,
|
||||
dataSourceKey: actionParams.dataSourceKey || baseSettings.dataSourceKey,
|
||||
};
|
||||
|
||||
return mergeKanbanPopupTemplateSettings(popupSettings, actionParams);
|
||||
};
|
||||
|
||||
const setKanbanModelProps = (model: any, props: Record<string, any>) => {
|
||||
model.setProps(props);
|
||||
model._options = model._options || {};
|
||||
@@ -211,13 +292,18 @@ const resolveKanbanPopupTargetUid = ({
|
||||
nextPopupTargetUid,
|
||||
currentPopupTemplateUid,
|
||||
currentPopupTargetUid,
|
||||
popupTemplateCopyMode,
|
||||
}: {
|
||||
nextPopupTemplateUid?: string;
|
||||
nextPopupTargetUid?: string;
|
||||
currentPopupTemplateUid?: string;
|
||||
currentPopupTargetUid?: string;
|
||||
popupTemplateCopyMode?: boolean;
|
||||
}) => {
|
||||
return !nextPopupTemplateUid && currentPopupTemplateUid && nextPopupTargetUid === currentPopupTargetUid
|
||||
return !nextPopupTemplateUid &&
|
||||
!popupTemplateCopyMode &&
|
||||
currentPopupTemplateUid &&
|
||||
nextPopupTargetUid === currentPopupTargetUid
|
||||
? undefined
|
||||
: nextPopupTargetUid;
|
||||
};
|
||||
@@ -242,6 +328,7 @@ const applyKanbanBlockPopupSettings = async (
|
||||
nextPopupTargetUid,
|
||||
currentPopupTemplateUid,
|
||||
currentPopupTargetUid,
|
||||
popupTemplateCopyMode: isKanbanPopupTemplateCopyMode(params),
|
||||
});
|
||||
const normalizedParams = {
|
||||
...params,
|
||||
@@ -867,6 +954,7 @@ export class KanbanBlockModel extends CollectionBlockModel<{
|
||||
pageModelClass?: string;
|
||||
dataSourceKey?: string;
|
||||
collectionName?: string;
|
||||
popupTemplateParams?: Record<string, any>;
|
||||
},
|
||||
syncOptions: KanbanPopupActionOptions = {},
|
||||
) {
|
||||
@@ -882,41 +970,48 @@ export class KanbanBlockModel extends CollectionBlockModel<{
|
||||
const currentParams = action.getStepParams?.('popupSettings', 'openView') || {};
|
||||
const currentPopupTemplateUid = normalizeKanbanPopupTemplateUid(currentParams.popupTemplateUid);
|
||||
const currentUid = normalizeKanbanPopupTargetUid(currentParams.uid);
|
||||
const nextTemplateParams = hasKanbanPopupTemplateState(options.popupTemplateParams)
|
||||
? options.popupTemplateParams
|
||||
: undefined;
|
||||
const currentTemplateParams =
|
||||
!syncOptions.persist && hasKanbanPopupTemplateState(currentParams) ? currentParams : undefined;
|
||||
const templateParams = nextTemplateParams || currentTemplateParams;
|
||||
const templateUid = normalizeKanbanPopupTargetUid(templateParams?.uid);
|
||||
let sanitizedUid = nextUid && nextUid !== this.uid && nextUid !== selfUid ? nextUid : undefined;
|
||||
|
||||
if (!nextPopupTemplateUid && currentPopupTemplateUid && sanitizedUid === currentUid) {
|
||||
sanitizedUid = undefined;
|
||||
}
|
||||
|
||||
const resolvedUid = sanitizedUid || (nextPopupTemplateUid ? currentUid || selfUid : selfUid);
|
||||
const resolvedUid = sanitizedUid || templateUid || (nextPopupTemplateUid ? currentUid || selfUid : selfUid);
|
||||
const nextPageModelClass = options.pageModelClass || undefined;
|
||||
const nextDataSourceKey = options.dataSourceKey || undefined;
|
||||
const nextCollectionName = options.collectionName || undefined;
|
||||
|
||||
if (
|
||||
currentParams.mode === nextMode &&
|
||||
currentParams.size === nextSize &&
|
||||
currentParams.popupTemplateUid === nextPopupTemplateUid &&
|
||||
normalizeKanbanPopupTargetUid(currentParams.uid) === resolvedUid &&
|
||||
currentParams.pageModelClass === nextPageModelClass &&
|
||||
currentParams.dataSourceKey === nextDataSourceKey &&
|
||||
currentParams.collectionName === nextCollectionName
|
||||
) {
|
||||
const nextParams = {
|
||||
...(templateParams || {}),
|
||||
mode: nextMode,
|
||||
size: nextSize,
|
||||
uid: resolvedUid,
|
||||
pageModelClass: nextPageModelClass,
|
||||
...(nextPopupTemplateUid ? { popupTemplateUid: nextPopupTemplateUid } : {}),
|
||||
...(templateParams?.dataSourceKey || nextDataSourceKey
|
||||
? { dataSourceKey: templateParams?.dataSourceKey || nextDataSourceKey }
|
||||
: {}),
|
||||
...(templateParams?.collectionName || nextCollectionName
|
||||
? { collectionName: templateParams?.collectionName || nextCollectionName }
|
||||
: {}),
|
||||
};
|
||||
|
||||
if (!nextPopupTemplateUid && templateParams?.popupTemplateContext === true) {
|
||||
delete nextParams.popupTemplateUid;
|
||||
}
|
||||
|
||||
if (JSON.stringify(currentParams) === JSON.stringify(nextParams)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextParams = {
|
||||
...(nextPopupTemplateUid ? currentParams : {}),
|
||||
mode: nextMode,
|
||||
size: nextSize,
|
||||
popupTemplateUid: nextPopupTemplateUid,
|
||||
uid: resolvedUid,
|
||||
pageModelClass: nextPageModelClass,
|
||||
...(nextDataSourceKey ? { dataSourceKey: nextDataSourceKey } : {}),
|
||||
...(nextCollectionName ? { collectionName: nextCollectionName } : {}),
|
||||
};
|
||||
|
||||
action.setStepParams('popupSettings', 'openView', nextParams);
|
||||
replaceModelStepParams(action, 'popupSettings', 'openView', nextParams);
|
||||
|
||||
if (syncOptions.persist && this.context.flowSettingsEnabled && action?.saveStepParams) {
|
||||
await action.saveStepParams();
|
||||
@@ -934,6 +1029,7 @@ export class KanbanBlockModel extends CollectionBlockModel<{
|
||||
pageModelClass: this.getCardPopupPageModelClass(),
|
||||
dataSourceKey: this.collection?.dataSourceKey,
|
||||
collectionName: this.collection?.name,
|
||||
popupTemplateParams: getModelStepParams(this.subModels?.item, 'cardSettings', 'popup'),
|
||||
},
|
||||
options,
|
||||
);
|
||||
@@ -996,6 +1092,7 @@ export class KanbanBlockModel extends CollectionBlockModel<{
|
||||
pageModelClass: this.getPopupPageModelClass(),
|
||||
dataSourceKey: this.collection?.dataSourceKey,
|
||||
collectionName: this.collection?.name,
|
||||
popupTemplateParams: getModelStepParams(this, 'kanbanSettings', 'popup'),
|
||||
},
|
||||
options,
|
||||
);
|
||||
@@ -1059,16 +1156,6 @@ export class KanbanBlockModel extends CollectionBlockModel<{
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof this.context?.openView === 'function') {
|
||||
await this.context.openView(action.uid, {
|
||||
formData: this.buildQuickCreateFormData(column),
|
||||
...(this.collection?.dataSourceKey ? { dataSourceKey: this.collection.dataSourceKey } : {}),
|
||||
...(this.collection?.name ? { collectionName: this.collection.name } : {}),
|
||||
target: this.context.layoutContentElement,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await action.dispatchEvent(
|
||||
'click',
|
||||
{
|
||||
@@ -1109,17 +1196,6 @@ export class KanbanBlockModel extends CollectionBlockModel<{
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof this.context?.openView === 'function' && action.uid) {
|
||||
await this.context.openView(action.uid, {
|
||||
mode: this.getCardOpenMode(),
|
||||
...(this.collection?.dataSourceKey ? { dataSourceKey: this.collection.dataSourceKey } : {}),
|
||||
...(this.collection?.name ? { collectionName: this.collection.name } : {}),
|
||||
filterByTk,
|
||||
target: this.context.layoutContentElement,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await action.dispatchEvent(
|
||||
'click',
|
||||
{
|
||||
@@ -1527,26 +1603,46 @@ KanbanBlockModel.registerFlow({
|
||||
return !(enabled ?? defaultEnabled);
|
||||
},
|
||||
async defaultParams(ctx) {
|
||||
const model = ctx.model as KanbanBlockModel;
|
||||
const commonParams = await resolveKanbanOpenViewDefaultParams(ctx as any);
|
||||
const action =
|
||||
typeof model.ensureQuickCreateAction === 'function'
|
||||
? await model.ensureQuickCreateAction()
|
||||
: typeof model.getQuickCreateAction === 'function'
|
||||
? model.getQuickCreateAction()
|
||||
: undefined;
|
||||
const popupPageModelClass =
|
||||
typeof (ctx.model as KanbanBlockModel).getPopupPageModelClass === 'function'
|
||||
? (ctx.model as KanbanBlockModel).getPopupPageModelClass()
|
||||
typeof model.getPopupPageModelClass === 'function'
|
||||
? model.getPopupPageModelClass()
|
||||
: ctx.model?.props?.popupPageModelClass;
|
||||
return {
|
||||
...commonParams,
|
||||
mode: (ctx.model as KanbanBlockModel).getPopupMode(),
|
||||
size: (ctx.model as KanbanBlockModel).getPopupSize(),
|
||||
popupTemplateUid: (ctx.model as KanbanBlockModel).getPopupTemplateUid(),
|
||||
pageModelClass: popupPageModelClass || commonParams.pageModelClass,
|
||||
uid: (ctx.model as KanbanBlockModel).getPopupTargetUid(),
|
||||
};
|
||||
return buildKanbanPopupSettingsFromAction(
|
||||
{
|
||||
...commonParams,
|
||||
mode: model.getPopupMode(),
|
||||
size: model.getPopupSize(),
|
||||
popupTemplateUid: model.getPopupTemplateUid(),
|
||||
pageModelClass: popupPageModelClass || commonParams.pageModelClass,
|
||||
uid: model.getPopupTargetUid(),
|
||||
},
|
||||
action,
|
||||
);
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
await applyKanbanBlockPopupSettings(ctx.model as KanbanBlockModel, params, { persist: false });
|
||||
},
|
||||
async beforeParamsSave(ctx, params, previousParams) {
|
||||
await ctx.model?.getAction?.('openView')?.beforeParamsSave?.(ctx, params, previousParams);
|
||||
await applyKanbanBlockPopupSettings(ctx.model as KanbanBlockModel, params, { persist: true });
|
||||
const model = ctx.model as KanbanBlockModel;
|
||||
const action =
|
||||
typeof model.ensureQuickCreateAction === 'function'
|
||||
? await model.ensureQuickCreateAction()
|
||||
: typeof model.getQuickCreateAction === 'function'
|
||||
? model.getQuickCreateAction()
|
||||
: undefined;
|
||||
const storedParams = getKanbanPopupActionSettings(action);
|
||||
await ctx.model
|
||||
?.getAction?.('openView')
|
||||
?.beforeParamsSave?.(ctx, params, Object.keys(storedParams).length > 0 ? storedParams : previousParams);
|
||||
await applyKanbanBlockPopupSettings(model, params, { persist: true });
|
||||
},
|
||||
},
|
||||
pageSize: {
|
||||
|
||||
+122
-12
@@ -67,6 +67,102 @@ const replaceModelStepParams = (model: any, flowKey: string, stepKey: string, pa
|
||||
model.emitter?.emit?.('onStepParamsChanged');
|
||||
};
|
||||
|
||||
const getModelStepParams = (model: any, flowKey: string, stepKey: string) => {
|
||||
const stepParams = model?.stepParams?.[flowKey]?.[stepKey];
|
||||
if (stepParams) {
|
||||
return stepParams;
|
||||
}
|
||||
|
||||
try {
|
||||
return model?.getStepParams?.(flowKey, stepKey) || {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const isKanbanPopupTemplateCopyMode = (params?: Record<string, any>) => {
|
||||
return params?.popupTemplateContext === true;
|
||||
};
|
||||
|
||||
const hasKanbanPopupTemplateState = (params?: Record<string, any>) => {
|
||||
if (!params || typeof params !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!normalizeKanbanPopupTemplateUid(params.popupTemplateUid) || params.popupTemplateContext === true;
|
||||
};
|
||||
|
||||
const POPUP_TEMPLATE_SETTING_KEYS = [
|
||||
'uid',
|
||||
'dataSourceKey',
|
||||
'collectionName',
|
||||
'associationName',
|
||||
'filterByTk',
|
||||
'sourceId',
|
||||
'popupTemplateUid',
|
||||
'popupTemplateMode',
|
||||
'popupTemplateContext',
|
||||
'popupTemplateHasFilterByTk',
|
||||
'popupTemplateHasSourceId',
|
||||
];
|
||||
|
||||
const mergeKanbanCardPopupTemplateSettings = (
|
||||
baseSettings: Record<string, any>,
|
||||
popupSettings?: Record<string, any>,
|
||||
) => {
|
||||
if (!hasKanbanPopupTemplateState(popupSettings)) {
|
||||
return baseSettings;
|
||||
}
|
||||
|
||||
const nextSettings = { ...baseSettings };
|
||||
POPUP_TEMPLATE_SETTING_KEYS.forEach((key) => {
|
||||
if (Object.prototype.hasOwnProperty.call(popupSettings, key)) {
|
||||
nextSettings[key] = popupSettings[key];
|
||||
}
|
||||
});
|
||||
|
||||
if (popupSettings?.popupTemplateContext === true) {
|
||||
delete nextSettings.popupTemplateUid;
|
||||
delete nextSettings.popupTemplateHasFilterByTk;
|
||||
delete nextSettings.popupTemplateHasSourceId;
|
||||
} else if (popupSettings?.popupTemplateUid) {
|
||||
delete nextSettings.popupTemplateContext;
|
||||
}
|
||||
|
||||
return nextSettings;
|
||||
};
|
||||
|
||||
const getKanbanCardPopupActionSettings = (model: any, action?: any) => {
|
||||
const parentModel = getKanbanCardPersistentParentModel(model);
|
||||
return getModelStepParams(action || parentModel?.subModels?.cardViewAction, 'popupSettings', 'openView');
|
||||
};
|
||||
|
||||
const ensureKanbanCardPopupAction = async (model: any) => {
|
||||
const parentModel = getKanbanCardPersistentParentModel(model);
|
||||
if (typeof parentModel?.ensureCardViewAction === 'function') {
|
||||
return await parentModel.ensureCardViewAction();
|
||||
}
|
||||
|
||||
return parentModel?.subModels?.cardViewAction;
|
||||
};
|
||||
|
||||
const buildKanbanCardPopupSettingsFromAction = (model: any, baseSettings: Record<string, any>, action?: any) => {
|
||||
const actionParams = getKanbanCardPopupActionSettings(model, action);
|
||||
if (Object.keys(actionParams).length === 0) {
|
||||
return baseSettings;
|
||||
}
|
||||
|
||||
const popupSettings = {
|
||||
...baseSettings,
|
||||
...actionParams,
|
||||
uid: actionParams.uid || baseSettings.uid,
|
||||
collectionName: actionParams.collectionName || baseSettings.collectionName,
|
||||
dataSourceKey: actionParams.dataSourceKey || baseSettings.dataSourceKey,
|
||||
};
|
||||
|
||||
return mergeKanbanCardPopupTemplateSettings(popupSettings, actionParams);
|
||||
};
|
||||
|
||||
const applyKanbanCardPopupSettings = async (
|
||||
model: any,
|
||||
params: Record<string, any>,
|
||||
@@ -82,7 +178,10 @@ const applyKanbanCardPopupSettings = async (
|
||||
getKanbanCardPopupProp(model, 'popupTargetUid', 'cardPopupTargetUid'),
|
||||
);
|
||||
const resolvedPopupTargetUid =
|
||||
!nextPopupTemplateUid && currentPopupTemplateUid && nextPopupTargetUid === currentPopupTargetUid
|
||||
!nextPopupTemplateUid &&
|
||||
!isKanbanPopupTemplateCopyMode(params) &&
|
||||
currentPopupTemplateUid &&
|
||||
nextPopupTargetUid === currentPopupTargetUid
|
||||
? undefined
|
||||
: nextPopupTargetUid;
|
||||
const normalizedParams = {
|
||||
@@ -256,22 +355,33 @@ KanbanCardItemModel.registerFlow({
|
||||
use: 'openView',
|
||||
async defaultParams(ctx) {
|
||||
const commonParams = await resolveKanbanOpenViewDefaultParams(ctx as any);
|
||||
return {
|
||||
...commonParams,
|
||||
mode: normalizeKanbanCardOpenMode(getKanbanCardPopupProp(ctx.model, 'openMode', 'cardOpenMode')),
|
||||
size: normalizeKanbanPopupSize(getKanbanCardPopupProp(ctx.model, 'popupSize', 'cardPopupSize')),
|
||||
popupTemplateUid: normalizeKanbanPopupTemplateUid(
|
||||
getKanbanCardPopupProp(ctx.model, 'popupTemplateUid', 'cardPopupTemplateUid'),
|
||||
),
|
||||
pageModelClass: getKanbanCardPopupProp(ctx.model, 'pageModelClass', 'cardPopupPageModelClass'),
|
||||
uid: normalizeKanbanPopupTargetUid(getKanbanCardPopupProp(ctx.model, 'popupTargetUid', 'cardPopupTargetUid')),
|
||||
};
|
||||
const action = await ensureKanbanCardPopupAction(ctx.model);
|
||||
return buildKanbanCardPopupSettingsFromAction(
|
||||
ctx.model,
|
||||
{
|
||||
...commonParams,
|
||||
mode: normalizeKanbanCardOpenMode(getKanbanCardPopupProp(ctx.model, 'openMode', 'cardOpenMode')),
|
||||
size: normalizeKanbanPopupSize(getKanbanCardPopupProp(ctx.model, 'popupSize', 'cardPopupSize')),
|
||||
popupTemplateUid: normalizeKanbanPopupTemplateUid(
|
||||
getKanbanCardPopupProp(ctx.model, 'popupTemplateUid', 'cardPopupTemplateUid'),
|
||||
),
|
||||
pageModelClass: getKanbanCardPopupProp(ctx.model, 'pageModelClass', 'cardPopupPageModelClass'),
|
||||
uid: normalizeKanbanPopupTargetUid(
|
||||
getKanbanCardPopupProp(ctx.model, 'popupTargetUid', 'cardPopupTargetUid'),
|
||||
),
|
||||
},
|
||||
action,
|
||||
);
|
||||
},
|
||||
async handler(ctx, params) {
|
||||
await applyKanbanCardPopupSettings(ctx.model, params, { persist: false });
|
||||
},
|
||||
async beforeParamsSave(ctx, params, previousParams) {
|
||||
await ctx.model?.getAction?.('openView')?.beforeParamsSave?.(ctx, params, previousParams);
|
||||
const action = await ensureKanbanCardPopupAction(ctx.model);
|
||||
const storedParams = getKanbanCardPopupActionSettings(ctx.model, action);
|
||||
await ctx.model
|
||||
?.getAction?.('openView')
|
||||
?.beforeParamsSave?.(ctx, params, Object.keys(storedParams).length > 0 ? storedParams : previousParams);
|
||||
await applyKanbanCardPopupSettings(ctx.model, params, { persist: true });
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user