Browse Source

update

pull/6658/head
Kaleniuk 3 months ago
parent
commit
b46eb09afd
  1. 65
      packages/core/index.html
  2. 1
      packages/core/src/css_composer/model/CssRule.ts
  3. 1
      packages/core/src/dom_components/model/Component.ts
  4. 4
      packages/core/src/domain_abstract/model/ModelWithPatches.ts
  5. 5
      packages/core/src/domain_abstract/model/StyleableModel.ts
  6. 6
      packages/core/src/editor/config/config.ts
  7. 5
      packages/core/src/editor/index.ts
  8. 6
      packages/core/src/editor/model/Editor.ts
  9. 37
      packages/core/src/editor/types.ts
  10. 40
      packages/core/src/patch_manager/index.ts
  11. 7
      packages/core/src/patch_manager/types.ts
  12. 82
      packages/core/test/specs/patch_manager/index.ts
  13. 2
      patch-test.html

65
packages/core/index.html

@ -88,68 +88,13 @@
height: '100%',
fromElement: true,
storageManager: { autoload: 0 },
styleManager: {
sectors: [
{
name: 'General',
open: false,
buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom'],
},
{
name: 'Flex',
open: false,
buildProps: [
'flex-direction',
'flex-wrap',
'justify-content',
'align-items',
'align-content',
'order',
'flex-basis',
'flex-grow',
'flex-shrink',
'align-self',
],
},
{
name: 'Dimension',
open: false,
buildProps: ['width', 'height', 'max-width', 'min-height', 'margin', 'padding'],
},
{
name: 'Typography',
open: false,
buildProps: [
'font-family',
'font-size',
'font-weight',
'letter-spacing',
'color',
'line-height',
'text-shadow',
],
},
{
name: 'Decorations',
open: false,
buildProps: [
'border-radius-c',
'background-color',
'border-radius',
'border',
'box-shadow',
'background',
],
},
{
name: 'Extra',
open: false,
buildProps: ['transition', 'perspective', 'transform'],
},
],
},
});
// editor.on('patch:update', ({ patch }) => console.log('patch:update', patch));
editor.on('patch:undo', ({ patch }) => console.log('patch:undo', patch));
editor.on('patch:redo', ({ patch }) => console.log('patch:redo', patch));
editor.BlockManager.add('testBlock', {
label: 'Block',
attributes: { class: 'gjs-fonts gjs-f-b1' },

1
packages/core/src/css_composer/model/CssRule.ts

@ -97,6 +97,7 @@ const { CSS } = hasWin() ? window : {};
* [Component]: component.html
*/
export default class CssRule extends StyleableModel<CssRuleProperties> {
patchObjectType = 'cssRule';
config: CssRuleProperties;
em?: EditorModel;
opt: any;

1
packages/core/src/dom_components/model/Component.ts

@ -154,6 +154,7 @@ type GetComponentStyleOpts = GetStyleOpts & {
* @module docsjs.Component
*/
export default class Component extends StyleableModel<ComponentProperties> {
patchObjectType = 'component';
/**
* @private
* @ts-ignore */

4
packages/core/src/domain_abstract/model/ModelWithPatches.ts

@ -1,9 +1,9 @@
// src/domain_abstract/model/ModelWithPatches.ts
import Backbone, { ObjectHash } from 'backbone';
import { Model, ObjectHash } from '../../common';
import type { JsonPatch } from '../../utils/jsonDiff';
import { diffObjects } from '../../utils/jsonDiff';
export default class ModelWithPatches<T extends ObjectHash = any> extends Backbone.Model<T> {
export default class ModelWithPatches<T extends ObjectHash = any, S = any> extends Model<T, S> {
patchObjectType = ''; // наприклад 'component'
set(key: any, val?: any, opts?: any) {

5
packages/core/src/domain_abstract/model/StyleableModel.ts

@ -1,5 +1,6 @@
import { isArray, isObject, isString, keys } from 'underscore';
import { Model, ObjectAny, ObjectHash, SetOptions } from '../../common';
import ModelWithPatches from './ModelWithPatches';
import { ObjectAny, ObjectHash, SetOptions } from '../../common';
import ParserHtml from '../../parser/model/ParserHtml';
import Selectors from '../../selector_manager/model/Selectors';
import { shallowDiff } from '../../utils/mixins';
@ -44,7 +45,7 @@ type WithDataResolvers<T> = {
[P in keyof T]?: T[P] | DataResolverProps;
};
export default class StyleableModel<T extends StyleableModelProperties = any> extends Model<T, UpdateStyleOptions> {
export default class StyleableModel<T extends StyleableModelProperties = any> extends ModelWithPatches<T, UpdateStyleOptions> {
em?: EditorModel;
views: StyleableView[] = [];
dataResolverWatchers: ModelDataResolverWatchers<T>;

6
packages/core/src/editor/config/config.ts

@ -24,6 +24,7 @@ import { HTMLGeneratorBuildOptions } from '../../code_manager/model/HtmlGenerato
import { CssGeneratorBuildOptions } from '../../code_manager/model/CssGenerator';
import { ObjectAny } from '../../common';
import { ColorPickerOptions } from '../../utils/ColorPicker';
import type { PatchManagerConfig } from '../../patch_manager/types';
export interface EditorConfig {
/**
@ -306,6 +307,11 @@ export interface EditorConfig {
*/
undoManager?: UndoManagerConfig | boolean;
/**
* Configurations for Patch Manager.
*/
patches?: PatchManagerConfig | boolean;
/**
* Configurations for Asset Manager.
*/

5
packages/core/src/editor/index.ts

@ -75,6 +75,7 @@ import StorageManager, { ProjectData, StorageOptions } from '../storage_manager'
import StyleManager from '../style_manager';
import TraitManager from '../trait_manager';
import UndoManagerModule from '../undo_manager';
import PatchManager from '../patch_manager';
import UtilsModule from '../utils';
import html from '../utils/html';
import defConfig, { EditorConfig, EditorConfigKeys } from './config/config';
@ -152,6 +153,10 @@ export default class Editor implements IBaseModule<EditorConfig> {
get UndoManager(): UndoManagerModule {
return this.em.UndoManager;
}
get Patches(): PatchManager {
return this.em.Patches;
}
get RichTextEditor(): RichTextEditorModule {
return this.em.RichTextEditor;
}

6
packages/core/src/editor/model/Editor.ts

@ -29,6 +29,7 @@ import KeymapsModule from '../../keymaps';
import ModalModule from '../../modal_dialog';
import PanelManager from '../../panels';
import CodeManagerModule from '../../code_manager';
import PatchManager from '../../patch_manager';
import UndoManagerModule from '../../undo_manager';
import RichTextEditorModule from '../../rich_text_editor';
import CommandsModule from '../../commands';
@ -54,6 +55,7 @@ const deps: (new (em: EditorModel) => IModule)[] = [
I18nModule,
KeymapsModule,
UndoManagerModule,
PatchManager,
StorageManager,
DeviceManager,
ParserModule,
@ -178,6 +180,10 @@ export default class EditorModel extends Model {
return this.get('UndoManager');
}
get Patches(): PatchManager {
return this.get('Patches');
}
get RichTextEditor(): RichTextEditorModule {
return this.get('RichTextEditor');
}

37
packages/core/src/editor/types.ts

@ -13,8 +13,17 @@ import { SelectorEvent } from '../selector_manager';
import { StyleManagerEvent } from '../style_manager';
import { EditorConfig } from './config/config';
import EditorModel from './model/Editor';
type GeneralEvent = 'canvasScroll' | 'undo' | 'redo' | 'load' | 'update';
import type { PatchProps } from '../patch_manager/types';
type GeneralEvent =
| 'canvasScroll'
| 'undo'
| 'redo'
| 'load'
| 'update'
| 'patch:update'
| 'patch:undo'
| 'patch:redo';
type EditorBuiltInEvents =
| DataSourceEvent
@ -38,6 +47,9 @@ export type EditorConfigType = EditorConfig & { pStylePrefix?: string };
export type EditorModelParam<T extends keyof EditorModel, N extends number> = Parameters<EditorModel[T]>[N];
export interface EditorEventCallbacks extends BlocksEventCallback, DataSourcesEventCallback {
'patch:update': [{ patch: PatchProps }];
'patch:undo': [{ patch: PatchProps }];
'patch:redo': [{ patch: PatchProps }];
[key: string]: any[];
}
@ -54,6 +66,27 @@ export enum EditorEvents {
*/
update = 'update',
/**
* @event `patch:update` Event triggered when the patch manager produces a new JSON patch with the recorded changes.
* @example
* editor.on('patch:update', ({ patch }) => { ... });
*/
patchUpdate = 'patch:update',
/**
* @event `patch:undo` Patch manager undo executed.
* @example
* editor.on('patch:undo', ({ patch }) => { ... });
*/
patchUndo = 'patch:undo',
/**
* @event `patch:redo` Patch manager redo executed.
* @example
* editor.on('patch:redo', ({ patch }) => { ... });
*/
patchRedo = 'patch:redo',
/**
* @event `undo` Undo executed.
* @example

40
packages/core/src/patch_manager/index.ts

@ -1,6 +1,6 @@
// src/patch_manager/PatchManager.ts
import { genId } from '../utils/id';
import type { PatchProps, JsonPatch } from './types';
import type { PatchManagerConfig, PatchProps, JsonPatch } from './types';
import { ItemManagerModule } from '../abstract/Module';
import { Collection } from '../common';
import type EditorModel from '../editor/model/Editor';
@ -25,10 +25,10 @@ export default class PatchManager extends ItemManagerModule {
onInit(): void {
const cfg = (this.getConfig() as any) ?? {};
const normalized = typeof cfg === 'boolean' ? { enable: cfg } : cfg;
this.init(normalized);
this.init({ enable: true, ...normalized });
}
init(cfg: { enable?: boolean; maxHistory?: number; coalesceMs?: number; debug?: boolean } = {}) {
init(cfg: PatchManagerConfig = {}) {
this.isEnabled = !!cfg.enable;
this.maxHistory = cfg.maxHistory ?? this.maxHistory;
this.coalesceMs = cfg.coalesceMs ?? 0;
@ -63,10 +63,7 @@ export default class PatchManager extends ItemManagerModule {
this.em.trigger('patch:update', { patch });
if (this.debug) {
try {
// eslint-disable-next-line no-console
console.log('[Patches] update', patch);
} catch {}
this.logWithEditor('update', patch);
}
}
@ -111,10 +108,7 @@ export default class PatchManager extends ItemManagerModule {
this.applyJsonPatchList(patch.changes);
this.em.trigger('patch:applied:external', { patch });
if (this.debug) {
try {
// eslint-disable-next-line no-console
console.log('[Patches] applied external', patch);
} catch {}
this.logWithEditor('applied external', patch);
}
} finally {
this.isApplyingExternal = false;
@ -124,7 +118,12 @@ export default class PatchManager extends ItemManagerModule {
undo() {
if (!this.isEnabled || this.index < 0) return;
const patch = this.history[this.index];
this.applyJsonPatchList(patch.reverseChanges);
this.isApplyingExternal = true;
try {
this.applyJsonPatchList(patch.reverseChanges);
} finally {
this.isApplyingExternal = false;
}
this.index--;
this.em.trigger('patch:undo', { patch });
}
@ -132,7 +131,12 @@ export default class PatchManager extends ItemManagerModule {
redo() {
if (!this.isEnabled || this.index >= this.history.length - 1) return;
const patch = this.history[this.index + 1];
this.applyJsonPatchList(patch.changes);
this.isApplyingExternal = true;
try {
this.applyJsonPatchList(patch.changes);
} finally {
this.isApplyingExternal = false;
}
this.index++;
this.em.trigger('patch:redo', { patch });
}
@ -232,5 +236,13 @@ export default class PatchManager extends ItemManagerModule {
this.isApplyingExternal = false;
super.__destroy?.();
}
private logWithEditor(eventName: string, patch: PatchProps) {
try {
this.em.log(`[Patches] ${eventName}`, {
ns: 'patches',
level: 'debug',
patch,
});
} catch {}
}
}

7
packages/core/src/patch_manager/types.ts

@ -15,3 +15,10 @@ export interface PatchProps {
reverseChanges: JsonPatch[]; // инверсия для undo
meta?: Record<string, any>; // user, txnId, etc.
}
export interface PatchManagerConfig {
enable?: boolean;
maxHistory?: number;
coalesceMs?: number;
debug?: boolean;
}

82
packages/core/test/specs/patch_manager/index.ts

@ -0,0 +1,82 @@
import Editor from '../../../src/editor';
import type { PatchProps } from '../../../src/patch_manager/types';
describe('PatchManager', () => {
let editor: Editor;
beforeEach(async () => {
editor = new Editor();
const ready = new Promise<void>((resolve) =>
editor.getModel().once('change:readyLoad', () => resolve()),
);
editor.getModel().initModules();
editor.getModel().loadOnStart();
await ready;
const pm = editor.Patches as any;
pm.history = [];
pm.index = -1;
pm.active = null;
});
afterEach(() => {
editor.destroy();
});
test('records JSON patches for component updates', () => {
const patches: PatchProps[] = [];
editor.on('patch:update', ({ patch }) => {
patches.push(patch);
});
const wrapper = editor.getWrapper()!;
const id = wrapper.getId();
wrapper.set('tagName', 'section');
expect(patches).toHaveLength(1);
expect(patches[0].changes[0].path).toBe(`/component/${id}/tagName`);
});
test('update() batches multiple mutations into a single patch', () => {
const updates: PatchProps[] = [];
editor.on('patch:update', ({ patch }) => updates.push(patch));
const wrapper = editor.getWrapper()!;
editor.Patches.update(() => {
wrapper.set('tagName', 'section');
wrapper.set('attributes', { 'data-test': 'updated' });
});
expect(updates).toHaveLength(1);
expect(updates[0].changes.length).toBeGreaterThanOrEqual(2);
});
test('apply() restores recorded patch data', () => {
let recorded: PatchProps | undefined;
editor.once('patch:update', ({ patch }) => {
recorded = patch;
});
const wrapper = editor.getWrapper()!;
wrapper.set('tagName', 'section');
expect(recorded).toBeDefined();
wrapper.set('tagName', 'span');
editor.Patches.apply(recorded!);
expect(wrapper.get('tagName')).toBe('section');
});
test('undo/redo emit patch events and rollback state', () => {
const undo: PatchProps[] = [];
const redo: PatchProps[] = [];
editor.on('patch:undo', ({ patch }) => {
undo.push(patch);
});
editor.on('patch:redo', ({ patch }) => {
redo.push(patch);
});
const wrapper = editor.getWrapper()!;
const original = wrapper.get('tagName');
wrapper.set('tagName', 'section');
expect(wrapper.get('tagName')).toBe('section');
editor.Patches.undo();
expect(wrapper.get('tagName')).toBe(original);
expect(undo).toHaveLength(1);
editor.Patches.redo();
expect(wrapper.get('tagName')).toBe('section');
expect(redo).toHaveLength(1);
});
});

2
patch-test.html

@ -24,7 +24,7 @@
container: '#gjs',
height: '100%',
headless: true,
patches: { enable: true, debug: true },
patches: { debug: true },
projectData: {
pages: [
{

Loading…
Cancel
Save