128 lines
3.5 KiB
TypeScript
128 lines
3.5 KiB
TypeScript
import { SectionsWidget } from './SectionsWidget';
|
|
import { render, createPortal } from 'react-dom';
|
|
import { Fragment, memo, useEffect, useState } from 'react';
|
|
import { Provider as CacheContextProvider, type Cache } from './cache';
|
|
|
|
const EVENT_MOUNT = 'wpj:sections-autocomplete:mount';
|
|
|
|
export type RuntimeProps = {
|
|
runtimeTarget: HTMLElement;
|
|
onInit?: () => void;
|
|
};
|
|
|
|
export type WidgetProps = {
|
|
// Buttons
|
|
onAddSection?: (sectionId: number) => void;
|
|
onAddWithSubsections?: (topsectionId: number) => void;
|
|
|
|
// Row click
|
|
onSelect?: (idSection: number) => void;
|
|
shouldHideAfterSelect?: boolean;
|
|
};
|
|
|
|
export type MountEvent = CustomEvent<{
|
|
mountTarget: HTMLElement;
|
|
} & WidgetProps>;
|
|
|
|
let CacheInitialized = false;
|
|
|
|
export function RuntimeComponent({ options }: { options: RuntimeProps }) {
|
|
const { runtimeTarget, onInit } = options;
|
|
const [portals, setPortals] = useState<MountEvent['detail'][]>([]);
|
|
const [cache, setCache] = useState<Cache>({
|
|
topLevelSections: [],
|
|
_query: {},
|
|
_fetchTopLevelSections: fetchTopLevelSections,
|
|
});
|
|
|
|
function fetchTopLevelSections() {
|
|
if (CacheInitialized) {
|
|
return;
|
|
}
|
|
|
|
const url = new URL('/admin/autocomplete/sections', `${window.location.protocol}//${window.location.host}`);
|
|
fetch(url.href)
|
|
.then(r => r.json())
|
|
.then(sections => {
|
|
setCache(p => ({ ...p, topLevelSections: sections }));
|
|
CacheInitialized = true;
|
|
})
|
|
.catch(alert);
|
|
}
|
|
|
|
useEffect(() => {
|
|
function mountEventHandler(ev: Event | MountEvent) {
|
|
if (ev.type !== EVENT_MOUNT) {
|
|
return;
|
|
}
|
|
const mountEvent = ev as MountEvent;
|
|
|
|
setPortals(previous => [...previous, mountEvent.detail]);
|
|
}
|
|
|
|
runtimeTarget.addEventListener(EVENT_MOUNT, mountEventHandler);
|
|
if (onInit) {
|
|
onInit();
|
|
}
|
|
|
|
return () => {
|
|
runtimeTarget.removeEventListener(EVENT_MOUNT, mountEventHandler);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<CacheContextProvider value={[cache, setCache]}>
|
|
<PortalMounter portals={portals} />
|
|
</CacheContextProvider>
|
|
);
|
|
}
|
|
|
|
const PortalMounter = memo(({ portals }: { portals: MountEvent['detail'][] }) => {
|
|
return (
|
|
<>
|
|
{portals.map((props, index) => (
|
|
<Fragment key={index}>
|
|
{createPortal(<SectionsWidget {...props} />, props.mountTarget, index.toString())}
|
|
</Fragment>
|
|
))}
|
|
</>
|
|
);
|
|
});
|
|
|
|
export function prepareRuntime(runtimeTarget?: HTMLElement) {
|
|
if (!runtimeTarget) {
|
|
const elem = document.createElement('meta');
|
|
document.body.insertAdjacentElement('beforeend', elem);
|
|
|
|
runtimeTarget = elem;
|
|
}
|
|
|
|
runtimeTarget.dataset.sectionsAutocompleteRuntime = '';
|
|
|
|
return new Promise<HTMLElement>((resolve) => {
|
|
const options = { runtimeTarget, onInit: () => { resolve(runtimeTarget!) } } as RuntimeProps;
|
|
|
|
render(
|
|
<RuntimeComponent options={options} />,
|
|
options.runtimeTarget,
|
|
);
|
|
});
|
|
}
|
|
|
|
export function mountAutocomplete(target: HTMLElement, options: WidgetProps = { shouldHideAfterSelect: true }) {
|
|
const event: MountEvent = new CustomEvent(EVENT_MOUNT, {
|
|
detail: {
|
|
mountTarget: target,
|
|
...options,
|
|
},
|
|
bubbles: true,
|
|
});
|
|
|
|
const runtimeElement = document.querySelector('[data-sections-autocomplete-runtime]');
|
|
if (!runtimeElement) {
|
|
throw new Error('SectionsAutocomplete runtime not found');
|
|
}
|
|
|
|
runtimeElement.dispatchEvent(event);
|
|
}
|