Merged PR 168: Add SmartComponent source code + Restrict Events by giving a root at the first render + Added Render function to a namespace
- Add smartcomponent source code to public/ - Restrict Events by giving a root at the first render + Added Render function to a namespace - Add attribute type="button" to all buttons
This commit is contained in:
parent
7f3f6a489a
commit
444b96736a
17 changed files with 132 additions and 24 deletions
2
public/smartcomponent/svg-layout-designer.html
Normal file
2
public/smartcomponent/svg-layout-designer.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
<div id="root">
|
||||
</div>
|
67
public/smartcomponent/svg-layout-designer.ts
Normal file
67
public/smartcomponent/svg-layout-designer.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
namespace SmartBusiness.Web.Components {
|
||||
export class SVGLayoutDesigner extends Components.ComponentBase {
|
||||
|
||||
public constructor(componentInfo: KnockoutComponentTypes.ComponentInfo, params: any) {
|
||||
super(componentInfo, params);
|
||||
// this.$component.id = SVGLayoutDesigner.generateUUID();
|
||||
setTimeout(() => (window as any).SVGLayoutDesigner.Render(this.$component[0]));
|
||||
this.InitEventsListener();
|
||||
}
|
||||
|
||||
|
||||
public static generateUUID() { // Public Domain/MIT
|
||||
let d = new Date().getTime();//Timestamp
|
||||
let d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now()*1000)) || 0;//Time in microseconds since page-load or 0 if unsupported
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
let r = Math.random() * 16;//random number between 0 and 16
|
||||
if(d > 0){//Use timestamp until depleted
|
||||
r = (d + r)%16 | 0;
|
||||
d = Math.floor(d/16);
|
||||
} else {//Use microseconds since page-load if supported
|
||||
r = (d2 + r)%16 | 0;
|
||||
d2 = Math.floor(d2/16);
|
||||
}
|
||||
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
public GetEditorComponent() {
|
||||
return this.$component[0].querySelector('.Editor');
|
||||
}
|
||||
|
||||
public GetCurrentHistoryState() {
|
||||
this.GetEditorComponent().dispatchEvent(new CustomEvent('getCurrentHistoryState'));
|
||||
}
|
||||
|
||||
public GetEditorState() {
|
||||
this.GetEditorComponent().dispatchEvent(new CustomEvent('getEditorState'));
|
||||
}
|
||||
|
||||
public SetEditorState(editorState: IEditorState) {
|
||||
this.GetEditorComponent().dispatchEvent(new CustomEvent('SetEditorState', { detail: editorState }));
|
||||
}
|
||||
|
||||
public AppendNewHistoryState(historyState: IHistoryState) {
|
||||
this.GetEditorComponent().dispatchEvent(new CustomEvent('appendNewState', { detail: historyState }));
|
||||
}
|
||||
|
||||
public OHistoryState: KnockoutObservable<any>;
|
||||
|
||||
private InitEventsListener() {
|
||||
this.$component[0].addEventListener('getCurrentHistoryState', (e: CustomEvent) => {
|
||||
this.OHistoryState(e.detail);
|
||||
console.log(this.OHistoryState());
|
||||
});
|
||||
this.$component[0].addEventListener('getEditorState', (e) => console.log((e as any).detail));
|
||||
}
|
||||
}
|
||||
|
||||
ko.components.register('svg-layout-designer', {
|
||||
viewModel: {
|
||||
createViewModel: function (params, componentInfo) {
|
||||
return new SmartBusiness.Web.Components.SVGLayoutDesigner(componentInfo, params);
|
||||
}
|
||||
},
|
||||
template: { element: 'svg-layout-designer' }
|
||||
});
|
||||
}
|
14
public/smartcomponent/svg-layout-designer.xcomponent
Normal file
14
public/smartcomponent/svg-layout-designer.xcomponent
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ComponentModel xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.Techform.com/SmartExpert/2009/05">
|
||||
<HasContent>false</HasContent>
|
||||
<Id>0A61000D-FC2D-4490-BB3E-0FAED2AF3FDC</Id>
|
||||
<ImageUrl />
|
||||
<ItemName>svg-layout-designer</ItemName>
|
||||
<Parameters>
|
||||
<ParameterModel>
|
||||
<ItemName>viewModel</ItemName>
|
||||
<Text>ViewModel</Text>
|
||||
</ParameterModel>
|
||||
</Parameters>
|
||||
<Text>svg-layout-designer</Text>
|
||||
</ComponentModel>
|
|
@ -11,6 +11,7 @@ import { DEFAULT_CONFIG, DEFAULT_MAINCONTAINER_PROPS } from '../../utils/default
|
|||
// App will never have props
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface IAppProps {
|
||||
root: Element | Document
|
||||
}
|
||||
|
||||
export const App: React.FunctionComponent<IAppProps> = (props) => {
|
||||
|
@ -59,6 +60,7 @@ export const App: React.FunctionComponent<IAppProps> = (props) => {
|
|||
return (
|
||||
<div>
|
||||
<Editor
|
||||
root={props.root}
|
||||
configuration={editorState.configuration}
|
||||
history={editorState.history}
|
||||
historyCurrentStep={editorState.historyCurrentStep}
|
||||
|
|
|
@ -10,7 +10,7 @@ interface IBarIconProps {
|
|||
export const BarIcon: React.FC<IBarIconProps> = (props) => {
|
||||
const isActiveClasses = props.isActive ? 'border-l-4 border-blue-500 bg-slate-200' : '';
|
||||
return (
|
||||
<button
|
||||
<button type="button"
|
||||
className={`bar-btn group ${isActiveClasses}`}
|
||||
title={props.title}
|
||||
onClick={() => props.onClick()}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { AddSymbol, OnPropertyChange as OnSymbolPropertyChange, DeleteSymbol, Se
|
|||
import { findContainerById } from '../../utils/itertools';
|
||||
|
||||
interface IEditorProps {
|
||||
root: Element | Document
|
||||
configuration: IConfiguration
|
||||
history: IHistoryState[]
|
||||
historyCurrentStep: number
|
||||
|
@ -57,6 +58,7 @@ function useShortcuts(
|
|||
}
|
||||
|
||||
function useWindowEvents(
|
||||
root: Element | Document,
|
||||
history: IHistoryState[],
|
||||
historyCurrentStep: number,
|
||||
configuration: IConfiguration,
|
||||
|
@ -75,6 +77,7 @@ function useWindowEvents(
|
|||
const funcs = new Map<string, () => void>();
|
||||
for (const event of events) {
|
||||
const func = (eventInitDict?: CustomEventInit): void => event.func(
|
||||
root,
|
||||
editorState,
|
||||
setHistory,
|
||||
setHistoryCurrentStep,
|
||||
|
@ -102,6 +105,7 @@ const Editor: React.FunctionComponent<IEditorProps> = (props) => {
|
|||
|
||||
useShortcuts(history, historyCurrentStep, setHistoryCurrentStep);
|
||||
useWindowEvents(
|
||||
props.root,
|
||||
history,
|
||||
historyCurrentStep,
|
||||
props.configuration,
|
||||
|
|
|
@ -81,7 +81,7 @@ export const ElementsSidebar: React.FC<IElementsSidebarProps> = (
|
|||
: 'bg-slate-300/60 hover:bg-slate-300';
|
||||
|
||||
return (
|
||||
<button
|
||||
<button type="button"
|
||||
className={`w-full border-blue-500 elements-sidebar-row whitespace-pre
|
||||
text-left text-sm font-medium transition-all ${selectedClass}`}
|
||||
id={key}
|
||||
|
|
|
@ -25,7 +25,7 @@ export const FloatingButton: React.FC<IFloatingButtonProps> = (props: IFloatingB
|
|||
<div className={`transition-all flex flex-col gap-2 items-center ${buttonListClasses}`}>
|
||||
{ props.children }
|
||||
</div>
|
||||
<button
|
||||
<button type="button"
|
||||
className={'transition-all w-14 h-14 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
|
||||
title='Open menu'
|
||||
onClick={() => toggleState(isHidden, setHidden)}
|
||||
|
|
|
@ -21,7 +21,7 @@ export const History: React.FC<IHistoryProps> = (props: IHistoryProps) => {
|
|||
: 'bg-slate-500 hover:bg-slate-700';
|
||||
|
||||
return (
|
||||
<button
|
||||
<button type="button"
|
||||
key={reversedIndex}
|
||||
style={style}
|
||||
onClick={() => props.jumpTo(reversedIndex)}
|
||||
|
|
|
@ -36,7 +36,7 @@ export const MainMenu: React.FC<IMainMenuProps> = (props) => {
|
|||
"/>
|
||||
</label>
|
||||
</form>
|
||||
<button
|
||||
<button type="button"
|
||||
onClick={() => setWindowState(WindowState.MAIN)}
|
||||
className='normal-btn block
|
||||
mt-8 '
|
||||
|
@ -49,8 +49,8 @@ export const MainMenu: React.FC<IMainMenuProps> = (props) => {
|
|||
default:
|
||||
return (
|
||||
<div className='absolute bg-blue-50 p-12 rounded-lg drop-shadow-lg grid grid-cols-1 md:grid-cols-2 gap-8 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'>
|
||||
<button className='mainmenu-btn' onClick={props.newEditor}>Start from scratch</button>
|
||||
<button className='mainmenu-btn' onClick={() => setWindowState(WindowState.LOAD)}>Load a configuration file</button>
|
||||
<button type="button" className='mainmenu-btn' onClick={props.newEditor}>Start from scratch</button>
|
||||
<button type="button" className='mainmenu-btn' onClick={() => setWindowState(WindowState.LOAD)}>Load a configuration file</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ interface IMenuItemProps {
|
|||
|
||||
export const MenuItem: React.FC<IMenuItemProps> = (props) => {
|
||||
return (
|
||||
<button
|
||||
<button type="button"
|
||||
className={props.className}
|
||||
onClick={() => props.onClick()}>{props.text}
|
||||
</button>
|
||||
|
|
|
@ -14,7 +14,7 @@ function handleDragStart(event: React.DragEvent<HTMLButtonElement>): void {
|
|||
|
||||
export const Sidebar: React.FC<ISidebarProps> = (props: ISidebarProps) => {
|
||||
const listElements = props.componentOptions.map(componentOption =>
|
||||
<button
|
||||
<button type="button"
|
||||
className='justify-center transition-all sidebar-component'
|
||||
key={componentOption.Type}
|
||||
id={componentOption.Type}
|
||||
|
|
|
@ -16,7 +16,7 @@ export const Symbols: React.FC<ISymbolsProps> = (props: ISymbolsProps) => {
|
|||
const listElements = props.componentOptions.map(componentOption => {
|
||||
if (componentOption.Image.Url !== undefined || componentOption.Image.Base64Image !== undefined) {
|
||||
const url = componentOption.Image.Base64Image ?? componentOption.Image.Url;
|
||||
return (<button
|
||||
return (<button type="button"
|
||||
className='justify-center sidebar-component-card hover:h-full'
|
||||
key={componentOption.Name}
|
||||
id={componentOption.Name}
|
||||
|
@ -37,7 +37,7 @@ export const Symbols: React.FC<ISymbolsProps> = (props: ISymbolsProps) => {
|
|||
</button>);
|
||||
}
|
||||
|
||||
return (<button
|
||||
return (<button type="button"
|
||||
className='group justify-center sidebar-component hover:h-full'
|
||||
key={componentOption.Name}
|
||||
id={componentOption.Name}
|
||||
|
|
|
@ -56,7 +56,7 @@ export const SymbolsSidebar: React.FC<ISymbolsSidebarProps> = (props: ISymbolsSi
|
|||
: 'bg-slate-300/60 hover:bg-slate-300';
|
||||
|
||||
return (
|
||||
<button
|
||||
<button type="button"
|
||||
className={
|
||||
`w-full border-blue-500 elements-sidebar-row whitespace-pre
|
||||
text-left text-sm font-medium transition-all ${selectedClass}`
|
||||
|
|
|
@ -109,14 +109,14 @@ export const UI: React.FunctionComponent<IUIProps> = (props: IUIProps) => {
|
|||
/>
|
||||
|
||||
<FloatingButton className={`fixed z-10 flex flex-col gap-2 items-center bottom-40 ${buttonRightOffsetClasses}`}>
|
||||
<button
|
||||
<button type="button"
|
||||
className={'transition-all w-10 h-10 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
|
||||
title='Export as JSON'
|
||||
onClick={props.SaveEditorAsJSON}
|
||||
>
|
||||
<UploadIcon className="heroicon text-white" />
|
||||
</button>
|
||||
<button
|
||||
<button type="button"
|
||||
className={'transition-all w-10 h-10 p-2 align-middle items-center justify-center rounded-full bg-blue-500 hover:bg-blue-800'}
|
||||
title='Export as SVG'
|
||||
onClick={props.SaveEditorAsSVG}
|
||||
|
|
|
@ -11,19 +11,26 @@ const initEditor = (configuration: IConfiguration): void => {
|
|||
|
||||
}
|
||||
|
||||
const getEditorState = (editorState: IEditorState): void => {
|
||||
const getEditorState = (
|
||||
root: Element | Document,
|
||||
editorState: IEditorState
|
||||
): void => {
|
||||
const customEvent = new CustomEvent<IEditorState>('getEditorState', { detail: editorState });
|
||||
document.dispatchEvent(customEvent);
|
||||
root.dispatchEvent(customEvent);
|
||||
};
|
||||
|
||||
const getCurrentHistoryState = (editorState: IEditorState): void => {
|
||||
const getCurrentHistoryState = (
|
||||
root: Element | Document,
|
||||
editorState: IEditorState
|
||||
): void => {
|
||||
const customEvent = new CustomEvent<IHistoryState>(
|
||||
'getCurrentHistoryState',
|
||||
{ detail: editorState.history[editorState.historyCurrentStep] });
|
||||
document.dispatchEvent(customEvent);
|
||||
root.dispatchEvent(customEvent);
|
||||
};
|
||||
|
||||
const appendNewState = (
|
||||
root: Element | Document,
|
||||
editorState: IEditorState,
|
||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>,
|
||||
|
@ -41,6 +48,7 @@ const appendNewState = (
|
|||
export interface IEditorEvent {
|
||||
name: string
|
||||
func: (
|
||||
root: Element | Document,
|
||||
editorState: IEditorState,
|
||||
setHistory: Dispatch<SetStateAction<IHistoryState[]>>,
|
||||
setHistoryCurrentStep: Dispatch<SetStateAction<number>>,
|
||||
|
|
15
src/main.tsx
15
src/main.tsx
|
@ -3,8 +3,19 @@ import ReactDOM from 'react-dom/client';
|
|||
import { App } from './Components/App/App';
|
||||
import './index.scss';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
function render(root: Element | Document): void {
|
||||
ReactDOM.createRoot(root.querySelector('#root') as HTMLDivElement).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<App root={root}/>
|
||||
</React.StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
namespace SVGLayoutDesigner {
|
||||
export const Render = render;
|
||||
}
|
||||
|
||||
|
||||
(window as any).SVGLayoutDesigner = SVGLayoutDesigner;
|
||||
|
||||
render(document);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue