wip add stylesheet-rule-input element + stylesheet-rule-ref element

This commit is contained in:
Mads Rasmussen
2023-12-18 21:58:13 +01:00
parent 2f195579b0
commit 72cbbd462a
14 changed files with 243 additions and 155 deletions

View File

@@ -0,0 +1,5 @@
import './stylesheet-rule-input/stylesheet-rule-input.element.js';
import './stylesheet-rule-ref/stylesheet-rule-ref.element.js';
export * from './stylesheet-rule-input/stylesheet-rule-input.element.js';
export * from './stylesheet-rule-ref/stylesheet-rule-ref.element.js';

View File

@@ -0,0 +1,3 @@
import { manifests as stylesheetRuleInputManifests } from './stylesheet-rule-input/manifests.js';
export const manifests = [...stylesheetRuleInputManifests];

View File

@@ -0,0 +1,12 @@
import { ManifestModal } from '@umbraco-cms/backoffice/extension-registry';
export const UMB_STYLESHEET_RULE_SETTINGS_MODAL_ALIAS = 'Umb.Modal.StylesheetRuleSettings';
const modal: ManifestModal = {
type: 'modal',
alias: UMB_STYLESHEET_RULE_SETTINGS_MODAL_ALIAS,
name: 'Stylesheet Rule Settings Modal',
js: () => import('./stylesheet-rule-settings-modal.element.js'),
};
export const manifests = [modal];

View File

@@ -0,0 +1,145 @@
import { UmbSortableStylesheetRule } from '../../types.js';
import { UMB_STYLESHEET_RULE_SETTINGS_MODAL } from './stylesheet-rule-settings-modal.token.js';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { css, html, customElement, ifDefined, repeat, property } from '@umbraco-cms/backoffice/external/lit';
import { FormControlMixin, UUIComboboxElement, UUIComboboxEvent } from '@umbraco-cms/backoffice/external/uui';
import { UMB_MODAL_MANAGER_CONTEXT_TOKEN, UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
@customElement('umb-stylesheet-rule-input')
export class UmbStylesheetRuleInputElement extends FormControlMixin(UmbLitElement) {
@property({ type: Array, attribute: false })
rules: UmbSortableStylesheetRule[] = [];
#modalManager: UmbModalManagerContext | undefined;
/*
#sorter = new UmbSorterController(this, {
...SORTER_CONFIG,
performItemInsert: ({ item, newIndex }) => {
return this.#findNewSortOrder(item, newIndex) ?? false;
},
performItemRemove: () => {
//defined so the default does not run
return true;
},
});
*/
constructor() {
super();
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (modalContext) => {
this.#modalManager = modalContext;
});
//this.#sorter.setModel(this._rules);
}
protected getFormElement() {
return undefined;
}
#findNewSortOrder(rule: UmbSortableStylesheetRule, newIndex: number) {
const rules = [...this.getRules()].sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
const oldIndex = rules.findIndex((r) => r.name === rule.name);
if (oldIndex === -1) return false;
rules.splice(oldIndex, 1);
rules.splice(newIndex, 0, rule);
this.setRules(rules.map((r, i) => ({ ...r, sortOrder: i })));
return true;
}
setRules(rules: UmbSortableStylesheetRule[]) {
/*
const newRules = rules.map((r, i) => ({ ...r, sortOrder: i }));
this.#rules.next(newRules);
this.sendRulesGetContent();
*/
}
#onChange(event: UUIComboboxEvent) {
event.stopPropagation();
const target = event.target as UUIComboboxElement;
if (typeof target?.value === 'string') {
this.value = target.value;
this.dispatchEvent(new UmbChangeEvent());
}
}
addRule = (rule: UmbSortableStylesheetRule | null = null) => {
if (!this.#modalManager) throw new Error('Modal context not found');
const modalContext = this.#modalManager.open(UMB_STYLESHEET_RULE_SETTINGS_MODAL, {
value: {
rule,
},
});
modalContext?.onSubmit().then((value) => {
console.log(value);
/*
if (result.rule) {
console.log(result.rule);
//this.#context?.setRules([...this._rules, { ...result.rule, sortOrder: this._rules.length }]);
}
*/
});
};
removeRule = (rule: UmbSortableStylesheetRule) => {
//const rules = this._rules?.filter((r) => r.name !== rule.name);
};
render() {
return html`
<uui-ref-list>
${repeat(
this.rules,
(rule) => rule.name + rule.sortOrder,
(rule) => html`
<umb-stylesheet-rule-ref
name=${rule.name}
detail=${rule.selector}
data-umb-rule-name="${ifDefined(rule?.name)}"></umb-stylesheet-rule-ref>
`,
)}
</uui-ref-list>
<uui-button label="Add rule" look="placeholder" @click=${() => this.addRule(null)}>Add</uui-button>
`;
}
static styles = [
css`
:host {
display: block;
}
uui-button {
display: block;
}
`,
];
}
export default UmbStylesheetRuleInputElement;
declare global {
interface HTMLElementTagNameMap {
'umb-stylesheet-rule-input': UmbStylesheetRuleInputElement;
}
}
const SORTER_CONFIG: UmbSorterConfig<UmbSortableStylesheetRule> = {
compareElementToModel: (element: HTMLElement, model: UmbSortableStylesheetRule) => {
return element.getAttribute('data-umb-rule-name') === model.name;
},
querySelectModelToElement: (container: HTMLElement, modelEntry: UmbSortableStylesheetRule) => {
return container.querySelector('data-umb-rule-name[' + modelEntry.name + ']');
},
identifier: 'stylesheet-rules-sorter',
itemSelector: 'umb-stylesheet-rich-text-editor-rule',
containerSelector: '#rules-container',
};

View File

@@ -1,14 +1,10 @@
import { RichTextRuleModelSortable } from '../../stylesheet-workspace.context.js';
import { RichTextRuleModelSortable } from '../../workspace/stylesheet-workspace.context.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, customElement, ifDefined, state } from '@umbraco-cms/backoffice/external/lit';
import { css, html, customElement, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
export interface StylesheetRichTextEditorStyleModalValue {
rule: RichTextRuleModelSortable | null;
}
@customElement('umb-stylesheet-rich-text-editor-style-modal')
export default class UmbStylesheetRichTextEditorStyleModalElement extends UmbModalBaseElement<
@customElement('umb-stylesheet-rule-settings-modal')
export default class UmbStylesheetRuleSettingsModalElement extends UmbModalBaseElement<
never,
StylesheetRichTextEditorStyleModalValue
> {
@@ -162,6 +158,6 @@ export default class UmbStylesheetRichTextEditorStyleModalElement extends UmbMod
declare global {
interface HTMLElementTagNameMap {
'umb-stylesheet-rich-text-editor-style-modal': UmbStylesheetRichTextEditorStyleModalElement;
'umb-stylesheet-rule-settings-modal': UmbStylesheetRuleSettingsModalElement;
}
}

View File

@@ -0,0 +1,18 @@
import { UmbSortableStylesheetRule } from '../../types.js';
import { UMB_STYLESHEET_RULE_SETTINGS_MODAL_ALIAS } from './manifests.js';
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
export interface UmbStylesheetRuleSettingsModalValue {
rule: UmbSortableStylesheetRule | null;
}
export const UMB_STYLESHEET_RULE_SETTINGS_MODAL = new UmbModalToken<never, UmbStylesheetRuleSettingsModalValue>(
UMB_STYLESHEET_RULE_SETTINGS_MODAL_ALIAS,
{
modal: {
type: 'sidebar',
size: 'medium',
},
value: { rule: null },
},
);

View File

@@ -0,0 +1,21 @@
import { UUIRefNodeElement } from '@umbraco-cms/backoffice/external/uui';
import { customElement, css } from '@umbraco-cms/backoffice/external/lit';
/**
* @element umb-stylesheet-rule-ref
* @description - Component for displaying a reference to a stylesheet rule
* @extends UUIRefNodeElement
*/
@customElement('umb-stylesheet-rule-ref')
export class UmbStylesheetRuleRefElement extends UUIRefNodeElement {
protected fallbackIcon =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1" /><path d="M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1" /></svg>';
static styles = [...UUIRefNodeElement.styles, css``];
}
declare global {
interface HTMLElementTagNameMap {
'umb-stylesheet-rule-ref': UmbStylesheetRuleRefElement;
}
}

View File

@@ -1,2 +1,5 @@
export * from './repository/index.js';
export { UmbStylesheetTreeRepository } from './tree/index.js';
// Components
export { UmbStylesheetRuleInputElement } from './components/index.js';

View File

@@ -4,6 +4,7 @@ import { manifests as treeManifests } from './tree/manifests.js';
import { manifests as workspaceManifests } from './workspace/manifests.js';
import { manifests as entityActionManifests } from './entity-actions/manifests.js';
import { manifests as collectionManifests } from './collection/manifests.js';
import { manifests as componentManifests } from './components/manifests.js';
export const manifests = [
...repositoryManifests,
@@ -12,4 +13,5 @@ export const manifests = [
...workspaceManifests,
...entityActionManifests,
...collectionManifests,
...componentManifests,
];

View File

@@ -8,3 +8,10 @@ export interface UmbStylesheetDetailModel {
name: string;
content: string;
}
export interface UmbSortableStylesheetRule {
name: string;
selector: string;
styles: string;
sortOrder: number;
}

View File

@@ -1,5 +1,4 @@
import type {
ManifestModal,
ManifestWorkspace,
ManifestWorkspaceAction,
ManifestWorkspaceView,
@@ -76,16 +75,4 @@ const workspaceActions: Array<ManifestWorkspaceAction> = [
},
];
export const UMB_MODAL_TEMPLATING_STYLESHEET_RTF_STYLE_SIDEBAR =
'Umb.Modal.Templating.Stylesheet.RichTextEditorStyle.Sidebar';
const modals: Array<ManifestModal> = [
{
type: 'modal',
alias: UMB_MODAL_TEMPLATING_STYLESHEET_RTF_STYLE_SIDEBAR,
name: 'Rich text editor style modal',
js: () => import('./views/rich-text-rule/stylesheet-workspace-view-rich-text-editor-style-sidebar.element.js'),
},
];
export const manifests = [workspace, ...workspaceViews, ...workspaceActions, ...modals];
export const manifests = [workspace, ...workspaceViews, ...workspaceActions];

View File

@@ -7,13 +7,10 @@ import {
UmbEditableWorkspaceContextBase,
} from '@umbraco-cms/backoffice/workspace';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbArrayState, UmbBooleanState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import { UmbBooleanState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import { loadCodeEditor } from '@umbraco-cms/backoffice/code-editor';
import type { RichTextRuleModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export type RichTextRuleModelSortable = RichTextRuleModel & { sortOrder?: number };
export class UmbStylesheetWorkspaceContext
extends UmbEditableWorkspaceContextBase<UmbStylesheetDetailRepository, UmbStylesheetDetailModel>
implements UmbSaveableWorkspaceContextInterface<UmbStylesheetDetailModel | undefined>
@@ -24,16 +21,11 @@ export class UmbStylesheetWorkspaceContext
readonly content = this.#data.asObservablePart((data) => data?.content);
readonly path = this.#data.asObservablePart((data) => data?.path);
#rules = new UmbArrayState<RichTextRuleModelSortable>([], (rule) => rule.name);
readonly rules = this.#rules.asObservable();
#isCodeEditorReady = new UmbBooleanState(false);
readonly isCodeEditorReady = this.#isCodeEditorReady.asObservable();
constructor(host: UmbControllerHostElement) {
super(host, UMB_STYLESHEET_WORKSPACE_ALIAS, new UmbStylesheetDetailRepository(host));
// TODO: sort close to the server
this.#rules.sortBy((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
this.#loadCodeEditor();
}
@@ -125,17 +117,6 @@ export class UmbStylesheetWorkspaceContext
this.setRules(data?.rules ?? []);
}
findNewSortOrder(rule: RichTextRuleModel, newIndex: number) {
const rules = [...this.getRules()].sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
const oldIndex = rules.findIndex((r) => r.name === rule.name);
if (oldIndex === -1) return false;
rules.splice(oldIndex, 1);
rules.splice(newIndex, 0, rule);
this.setRules(rules.map((r, i) => ({ ...r, sortOrder: i })));
return true;
}
getRules() {
return this.#rules.getValue();
}
@@ -144,12 +125,6 @@ export class UmbStylesheetWorkspaceContext
this.#rules.updateOne(unique, rule);
this.sendRulesGetContent();
}
setRules(rules: RichTextRuleModelSortable[]) {
const newRules = rules.map((r, i) => ({ ...r, sortOrder: i }));
this.#rules.next(newRules);
this.sendRulesGetContent();
}
*/
public destroy(): void {

View File

@@ -30,11 +30,6 @@ export class UmbStylesheetCodeEditorWorkspaceViewElement extends UmbLitElement {
});
}
disconnectedCallback(): void {
super.disconnectedCallback();
this.#stylesheetWorkspaceContext?.sendContentGetRules();
}
#onCodeEditorInput(event: Event) {
const target = event.target as UmbCodeEditorElement;
const value = target.code as string;

View File

@@ -1,61 +1,20 @@
import { RichTextRuleModelSortable, UmbStylesheetWorkspaceContext } from '../../stylesheet-workspace.context.js';
import { UMB_MODAL_TEMPLATING_STYLESHEET_RTF_STYLE_SIDEBAR } from '../../manifests.js';
import { UmbStylesheetRichTextRuleRepository } from '../../../repository/rich-text-rule/index.js';
import { StylesheetRichTextEditorStyleModalValue } from './stylesheet-workspace-view-rich-text-editor-style-sidebar.element.js';
import { UmbStylesheetWorkspaceContext } from '../../stylesheet-workspace.context.js';
import { UmbStylesheetRichTextRuleRepository } from '../../../repository/index.js';
import { UmbSortableStylesheetRule } from '../../../types.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import { UMB_MODAL_MANAGER_CONTEXT_TOKEN, UmbModalManagerContext, UmbModalToken } from '@umbraco-cms/backoffice/modal';
import { RichTextRuleModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import { css, html, customElement, state, ifDefined, repeat } from '@umbraco-cms/backoffice/external/lit';
import './stylesheet-workspace-view-rich-text-editor-rule.element.js';
export const UMB_MODAL_TEMPLATING_STYLESHEET_RTF_STYLE_SIDEBAR_MODAL = new UmbModalToken<
never,
StylesheetRichTextEditorStyleModalValue
>(UMB_MODAL_TEMPLATING_STYLESHEET_RTF_STYLE_SIDEBAR, {
modal: {
type: 'sidebar',
size: 'medium',
},
value: { rule: null },
});
const SORTER_CONFIG: UmbSorterConfig<RichTextRuleModel> = {
compareElementToModel: (element: HTMLElement, model: RichTextRuleModel) => {
return element.getAttribute('data-umb-rule-name') === model.name;
},
querySelectModelToElement: (container: HTMLElement, modelEntry: RichTextRuleModel) => {
return container.querySelector('data-umb-rule-name[' + modelEntry.name + ']');
},
identifier: 'stylesheet-rules-sorter',
itemSelector: 'umb-stylesheet-rich-text-editor-rule',
containerSelector: '#rules-container',
};
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
@customElement('umb-stylesheet-rich-text-rule-workspace-view')
export class UmbStylesheetRichTextRuleWorkspaceViewElement extends UmbLitElement {
@state()
_rules: RichTextRuleModelSortable[] = [];
_rules: UmbSortableStylesheetRule[] = [];
#context?: UmbStylesheetWorkspaceContext;
private _modalContext?: UmbModalManagerContext;
#stylesheetRichTextRuleRepository = new UmbStylesheetRichTextRuleRepository(this);
#sorter = new UmbSorterController(this, {
...SORTER_CONFIG,
performItemInsert: ({ item, newIndex }) => {
return this.#context?.findNewSortOrder(item, newIndex) ?? false;
},
performItemRemove: () => {
//defined so the default does not run
return true;
},
});
constructor() {
super();
@@ -64,10 +23,6 @@ export class UmbStylesheetRichTextRuleWorkspaceViewElement extends UmbLitElement
const unique = this.#context?.getEntityId();
this.#setRules(unique);
});
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => {
this._modalContext = instance;
});
}
async #setRules(unique: string) {
@@ -75,47 +30,26 @@ export class UmbStylesheetRichTextRuleWorkspaceViewElement extends UmbLitElement
if (data) {
this._rules = data.rules ?? [];
this.#sorter.setModel(this._rules);
}
}
openModal = (rule: RichTextRuleModelSortable | null = null) => {
if (!this._modalContext) throw new Error('Modal context not found');
const modal = this._modalContext.open(UMB_MODAL_TEMPLATING_STYLESHEET_RTF_STYLE_SIDEBAR_MODAL, {
value: {
rule,
},
});
modal?.onSubmit().then((result) => {
if (result.rule) {
this.#context?.setRules([...this._rules, { ...result.rule, sortOrder: this._rules.length }]);
}
});
};
removeRule = (rule: RichTextRuleModelSortable) => {
const rules = this._rules?.filter((r) => r.name !== rule.name);
this.#context?.setRules(rules);
};
#onRuleChange(event: UmbChangeEvent) {
event.stopPropagation();
const target = event.target as UmbStylesheetRuleInputElement;
const rules = target.rules;
console.log(rules);
console.log(event);
debugger;
}
render() {
return html` <uui-box headline="Rich text editor styles">
<div id="box-row">
<p id="description">Define the styles that should be available in the rich text editor for this stylesheet.</p>
<div id="rules">
<div id="rules-container">
${repeat(
this._rules,
(rule) => rule?.name ?? '' + rule?.sortOrder ?? '',
(rule) =>
html`<umb-stylesheet-rich-text-editor-rule
.rule=${rule}
data-umb-rule-name="${ifDefined(rule?.name)}"></umb-stylesheet-rich-text-editor-rule>`,
)}
</div>
<uui-button label="Add rule" look="primary" @click=${() => this.openModal(null)}>Add</uui-button>
return html`<uui-box headline="Rich text editor styles">
<umb-property-layout
description="Define the styles that should be available in the rich text editor for this stylesheet.">
<div slot="editor">
<umb-stylesheet-rule-input .rules=${this._rules} @change=${this.#onRuleChange}></umb-stylesheet-rule-input>
</div>
</div>
</umb-property-layout>
</uui-box>`;
}
@@ -127,21 +61,6 @@ export class UmbStylesheetRichTextRuleWorkspaceViewElement extends UmbLitElement
width: 100%;
}
#box-row {
display: flex;
gap: var(--uui-size-layout-1);
}
#description {
margin-top: 0;
flex: 0 0 250px;
}
#rules {
flex: 1 1 auto;
max-width: 600px;
}
uui-box {
margin: var(--uui-size-layout-1);
}
@@ -153,6 +72,6 @@ export default UmbStylesheetRichTextRuleWorkspaceViewElement;
declare global {
interface HTMLElementTagNameMap {
'umb-stylesheet-workspace-view-rich-text-editor': UmbStylesheetRichTextRuleWorkspaceViewElement;
'umb-stylesheet-rich-text-rule-workspace-view': UmbStylesheetRichTextRuleWorkspaceViewElement;
}
}