feat: support context menu and draggable button (#844)

Co-authored-by: sealday <sealday@gmail.com>
Reviewed-on: daoyoucloud/tachybase#844
This commit is contained in:
sealday 2024-04-25 16:27:03 +08:00
parent 9f215c76e8
commit 1816660c14
19 changed files with 1013 additions and 39 deletions

View File

@ -26,6 +26,7 @@
"@vitejs/plugin-react": "^4.0.0",
"chalk": "2.4.2",
"esbuild-register": "^3.4.2",
"sass": "^1.75.0",
"execa": "^5.1.1",
"fast-glob": "^3.3.1",
"fs-extra": "^11.1.1",

View File

@ -27,6 +27,7 @@
"fs-extra": "^11.1.1",
"jsonwebtoken": "^8.5.1",
"lodash": "4.17.21",
"react-transition-group": "^4.4.5",
"path-to-regexp": "^6.1.0",
"qrcode": "^1.5.1",
"qrcode.react": "^3.1.0",

View File

@ -0,0 +1,14 @@
import React from 'react';
import CSSTransition from 'react-transition-group/CSSTransition';
const AnimateComponent = ({ children, isVisible, timeout, className }) => {
const nodeRef = React.useRef(null);
return (
<CSSTransition nodeRef={nodeRef} in={isVisible} timeout={timeout} classNames={className} unmountOnExit>
{children}
</CSSTransition>
);
};
export default AnimateComponent;

View File

@ -0,0 +1,166 @@
import React, { useRef, useEffect, useState, useCallback } from 'react';
import ReactDOM from 'react-dom';
import classnames from 'classnames';
import { registerEvent, callHideEvent } from './registerEvent';
import AnimateComponent from './animateComponent';
import { throttle } from './helper';
function ContextMenu({
children,
id,
appendTo,
hideOnLeave,
onMouseLeave,
onHide,
onShow,
preventHideOnScroll,
preventHideOnResize,
attributes,
className,
animation,
}) {
const contextMenuEl = useRef(null);
const [isVisible, setVisible] = useState(false);
const [clientPosition, setClientPosition] = useState(null);
const showMenu = (e) => {
const { position } = e;
setVisible(true);
setClientPosition(position);
};
const hideMenu = () => {
setVisible(false);
if (onHide) onHide();
};
const handleMouseLeave = useCallback((e) => {
e.preventDefault();
onMouseLeave(e);
if (hideOnLeave) callHideEvent(id);
});
const clickOutsideCallback = (event) => {
if (contextMenuEl.current && !contextMenuEl.current.contains(event.target)) {
callHideEvent(id);
}
};
const contextMenuCallback = (event) => {
let targetElement = event.target;
do {
if (targetElement.classList && targetElement.classList.contains('menu-trigger')) {
return;
}
targetElement = targetElement.parentNode;
} while (targetElement);
callHideEvent(id);
};
const onScrollHideCallback = throttle(() => {
callHideEvent(id);
}, 200);
const onResizeHideCallback = throttle(() => {
callHideEvent(id);
}, 200);
useEffect(() => {
registerEvent(id, showMenu, hideMenu);
// detect click outside
document.addEventListener('mousedown', clickOutsideCallback);
// detect right click outside
document.addEventListener('contextmenu', contextMenuCallback);
// on scroll hide handled
if (!preventHideOnScroll) {
window.addEventListener('scroll', onScrollHideCallback);
}
// on resize hide handled
if (!preventHideOnResize) {
window.addEventListener('resize', onResizeHideCallback);
}
return () => {
document.removeEventListener('mousedown', clickOutsideCallback);
document.removeEventListener('contextmenu', contextMenuCallback);
window.removeEventListener('scroll', onScrollHideCallback);
window.removeEventListener('resize', onResizeHideCallback);
};
}, []);
useEffect(() => {
if (isVisible) {
const { clientY, clientX } = clientPosition;
const { innerHeight: windowInnerHeight, innerWidth: windowInnerWidth } = window;
const { offsetHeight: elemHeight, offsetWidth: elemWidth } = contextMenuEl.current;
let newClientY = clientY;
let newClientX = clientX;
if (windowInnerHeight < clientY + elemHeight) newClientY = clientY - elemHeight;
if (windowInnerWidth < clientX + elemWidth) newClientX = clientX - elemWidth;
contextMenuEl.current.style.top = `${newClientY + 2}px`;
contextMenuEl.current.style.left = `${newClientX + 2}px`;
if (onShow) onShow();
}
}, [isVisible, clientPosition]);
const childrenWithProps = React.Children.map(children, (child) => React.cloneElement(child, { id }));
const ContextComponent = () => (
<div
className={classnames('contextmenu', ...className.split(' '))}
ref={contextMenuEl}
onMouseLeave={handleMouseLeave}
{...attributes}
>
{childrenWithProps}
</div>
);
const PortalContextComponent = () => ReactDOM.createPortal(<ContextComponent />, document.querySelector(appendTo));
if (document.readyState === 'complete' && appendTo) {
return animation ? (
<AnimateComponent isVisible={isVisible} timeout={200} className={animation}>
<PortalContextComponent />
</AnimateComponent>
) : (
<PortalContextComponent />
);
}
return animation ? (
<AnimateComponent isVisible={isVisible} timeout={200} className={animation}>
<ContextComponent />
</AnimateComponent>
) : (
<ContextComponent />
);
}
export default ContextMenu;
ContextMenu.defaultProps = {
appendTo: null,
hideOnLeave: false,
preventHideOnResize: false,
preventHideOnScroll: false,
attributes: {},
className: '',
animation: 'fade',
onMouseLeave: () => null,
onHide: () => null,
onShow: () => null,
};

View File

@ -0,0 +1,42 @@
import React, { useCallback, useRef } from 'react';
import classnames from 'classnames';
import { callHideEvent } from './registerEvent';
function ContextMenuItem({ children, onClick, disabled, preventClose, attributes, className }) {
const contextMenuItem = useRef(null);
const handleClickEvent = useCallback((e) => {
if (disabled) return;
onClick(e);
if (!preventClose) callHideEvent('ID_NOT_REQUIRED');
});
return (
<div
className={classnames(
'contextmenu__item',
{
'contextmenu__item--disabled': disabled,
},
...className.split(' '),
)}
onClick={handleClickEvent}
{...attributes}
ref={contextMenuItem}
>
{children}
</div>
);
}
export default ContextMenuItem;
ContextMenuItem.defaultProps = {
disabled: false,
preventClose: false,
attributes: {},
className: '',
onClick: () => null,
onItemHover: () => null,
};

View File

@ -0,0 +1,49 @@
import React, { useRef, useCallback } from 'react';
import classnames from 'classnames';
import { callShowEvent, callHideEvent } from './registerEvent';
function ContextMenuTrigger({ children, id, disableWhileShiftPressed, attributes, disable, className }) {
const menuTrigger = useRef(null);
const handleContextMenu = useCallback((e) => {
if (disable) return;
if (disableWhileShiftPressed && e.nativeEvent.shiftKey) {
callHideEvent(id);
return;
}
e.preventDefault();
e.stopPropagation();
const { clientX, clientY } = e.nativeEvent;
const opts = {
position: {
clientY,
clientX,
},
id,
};
callShowEvent(opts);
});
return (
<div
className={classnames('menu-trigger', ...className.split(' '))}
ref={menuTrigger}
{...attributes}
onContextMenu={(e) => handleContextMenu(e)}
>
{children}
</div>
);
}
export default ContextMenuTrigger;
ContextMenuTrigger.defaultProps = {
attributes: {},
disable: false,
renderTag: 'div',
disableWhileShiftPressed: false,
className: '',
};

View File

@ -0,0 +1,27 @@
/* global arguments */
export const uniqueId = () => `_${Math.random().toString(36).substr(2, 9)}`;
export const throttle = (func, limit) => {
let lastFunc;
let lastRan;
return () => {
const context = window;
const args = arguments;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(
() => {
if (Date.now() - lastRan >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
},
limit - (Date.now() - lastRan),
);
}
};
};

View File

@ -0,0 +1,46 @@
import * as React from 'react';
export interface ContextMenu {
id: string;
appendTo?: string;
animation?: string;
hideOnLeave?: boolean;
attributes?: object;
className?: string;
children?: ReactNode;
preventHideOnResize?: boolean;
preventHideOnScroll?: boolean;
onShow?: { (event: any): void };
onHide?: { (event: any): void };
onMouseLeave?: { (event: any): void };
}
export interface ContextMenuItem {
disabled?: boolean;
preventClose?: boolean;
disableWhileShiftPressed?: boolean;
attributes?: object;
className?: string;
children?: ReactNode;
onClick?: { (event: any): void };
}
export interface ContextMenuTrigger {
id: string;
attributes?: object;
disable?: boolean;
className?: string;
children?: ReactNode;
}
export interface Submenu {
title: string;
attributes?: object;
className?: boolean;
children?: ReactNode;
}
export const ContextMenu: React.FunctionComponent<ContextMenu>;
export const ContextMenuItem: React.FunctionComponent<ContextMenuItem>;
export const ContextMenuTrigger: React.FunctionComponent<ContextMenuTrigger>;
export const Submenu: React.FunctionComponent<Submenu>;

View File

@ -0,0 +1,6 @@
import './style.scss';
export { default as ContextMenu } from './contextMenu';
export { default as ContextMenuItem } from './contextMenuItem';
export { default as ContextMenuTrigger } from './contextMenuTrigger';
export { default as Submenu } from './submenu';

View File

@ -0,0 +1,36 @@
import { uniqueId } from './helper';
const events = {};
let activeEvent = {};
const registerEvent = (id, showMenu, hideMenu) => {
const _ = uniqueId();
events[_] = {
id,
showMenu,
hideMenu,
};
return id;
};
const callShowEvent = (opts) => {
if (activeEvent.hideMenu) activeEvent.hideMenu();
Object.keys(events).forEach((key) => {
if (events[key].id && events[key].id === opts.id) {
events[key].showMenu(opts);
activeEvent = events[key];
}
});
};
const callHideEvent = (menuId) => {
if (activeEvent.id === menuId || menuId === 'ID_NOT_REQUIRED') {
if (activeEvent.hideMenu) activeEvent.hideMenu();
activeEvent = {};
}
};
export { registerEvent, callShowEvent, callHideEvent };

View File

@ -0,0 +1,175 @@
.contextmenu {
$contextmenu: '.contextmenu';
position: fixed;
width: 200px;
left: 0;
top: calc(100% + 10px);
border-radius: 4px;
background-color: #fff;
padding: 10px 0;
z-index: 1001;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
&__item {
$item: &;
font-size: 14px;
display: block;
text-decoration: none;
padding: 10px 15px;
cursor: pointer;
user-select: none;
transition: 0.2s;
position: relative;
margin-bottom: 2px;
&:hover:not(#{$item}--disabled) {
background-color: #f1f1f1;
}
&--disabled {
opacity: 0.5;
cursor: no-drop;
}
}
.submenu {
$submenu: '.submenu';
position: relative;
&:hover {
& > #{$contextmenu}__item {
background-color: #f1f1f1;
}
}
&__item {
position: absolute;
top: 0;
left: 100%;
width: 100%;
border: solid 1px #ccc;
background-color: #fff;
border-radius: 4px;
padding: 5px 0;
opacity: 0;
visibility: hidden;
}
& > #{$contextmenu}__item {
&:after {
content: '';
border-style: solid;
border-width: 5px 8px;
border-color: transparent transparent transparent #000;
width: 0;
height: 0;
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 0px;
transition: 0.2s;
}
}
}
}
// fade in animation start
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 1;
transition: opacity 200ms;
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 200ms;
}
// fade in animation start
// zoom in animation start
.zoom-enter {
opacity: 0;
transform: scale(0.8);
transform-origin: top left;
}
.zoom-enter-active {
opacity: 1;
transform: scale(1);
transition: opacity 200ms, transform 200ms;
transform-origin: top left;
}
.zoom-exit {
opacity: 1;
transform: scale(1);
transform-origin: top left;
}
.zoom-exit-active {
opacity: 0;
transform: scale(0.8);
transition: opacity 200ms, transform 200ms;
transform-origin: top left;
}
// zoom in animation start
// to top left animation start
.toTopLeft-enter {
opacity: 0;
transform: translate(10px, 10px);
}
.toTopLeft-enter-active {
opacity: 1;
transform: translate(0px, 0px);
transition: opacity 200ms, transform 200ms;
}
.toTopLeft-exit {
opacity: 1;
transform: translate(0px, 0px);
}
.toTopLeft-exit-active {
opacity: 0;
transform: translate(10px, 10px);
transition: opacity 200ms, transform 200ms;
}
// to top left animation start
// right to left animation start
.rightToLeft-enter {
opacity: 0;
transform: translateX(10px);
}
.rightToLeft-enter-active {
opacity: 1;
transform: translateX(0px);
transition: opacity 200ms, transform 200ms;
}
.rightToLeft-exit {
opacity: 1;
transform: translateX(0px);
}
.rightToLeft-exit-active {
opacity: 0;
transform: translateX(10px);
transition: opacity 200ms, transform 200ms;
}
// right to left animation start
// right to left animation start
.pop-enter {
opacity: 0;
transform: scale(0.8);
}
.pop-enter-active {
opacity: 1;
transform: scale(1);
transition: opacity 200ms, transform 200ms;
}
.pop-exit {
opacity: 1;
transform: scale(1);
}
.pop-exit-active {
opacity: 0;
transform: scale(0.8);
transition: opacity 200ms, transform 200ms;
}
// right to left animation start

View File

@ -0,0 +1,74 @@
import React, { useCallback, useState, useRef } from 'react';
import classnames from 'classnames';
import ContextMenuItem from './contextMenuItem';
function Submenu({ children, title, attributes, className }) {
const [submenuStyle, setSubmenuStyle] = useState(null);
const submenuEl = useRef(null);
const submenuItem = useRef(null);
const calculateSubmenuPos = useCallback(() => {
const { innerHeight: windowInnerHeight, innerWidth: windowInnerWidth } = window;
const {
left: itemLeft,
top: itemTop,
width: itemWidth,
height: itemHeight,
} = submenuItem.current.getBoundingClientRect();
const { width: submenuWidth, height: submenuHeight } = submenuEl.current.getBoundingClientRect();
let style = {
opacity: 1,
visibility: 'visible',
};
if (itemTop + submenuHeight + itemHeight > windowInnerHeight) {
style = {
...style,
top: 'inherit',
bottom: '0',
};
}
if (itemLeft + submenuWidth + itemWidth > windowInnerWidth) {
style = {
...style,
left: 'inherit',
right: '100%',
};
}
setSubmenuStyle(style);
});
const hideSubmenu = useCallback(() => {
const style = {
opacity: 0,
visibility: 'hidden',
};
setSubmenuStyle(style);
});
return (
<div
className={classnames('submenu', ...className.split(' '))}
onMouseOver={() => calculateSubmenuPos()}
onMouseOut={() => hideSubmenu()}
onFocus={() => null}
onBlur={() => null}
ref={submenuItem}
{...attributes}
>
<ContextMenuItem>{title}</ContextMenuItem>
<div className="submenu__item" ref={submenuEl} style={submenuStyle}>
{children}
</div>
</div>
);
}
export default Submenu;
Submenu.defaultProps = {
title: 'Sub Menu',
className: '',
};

View File

@ -1,9 +1,12 @@
import { CalculatorOutlined, CommentOutlined, HighlightOutlined, ToolOutlined } from '@ant-design/icons';
import { CalculatorOutlined, CommentOutlined, HighlightOutlined, ToolOutlined, ToolFilled } from '@ant-design/icons';
import { useDesignable } from '@nocobase/client';
import { FloatButton } from 'antd';
import React from 'react';
import { useContextMenu } from '../context-menu/useContextMenu';
export const AssistantProvider = ({ children }) => {
const { designable, setDesignable } = useDesignable();
const { contextMenuEnabled, setContextMenuEnable } = useContextMenu();
const ContextMenuIcon = contextMenuEnabled ? ToolFilled : ToolOutlined;
return (
<>
{children}
@ -11,6 +14,7 @@ export const AssistantProvider = ({ children }) => {
<FloatButton icon={<HighlightOutlined />} onClick={() => setDesignable(!designable)} />
<FloatButton icon={<CalculatorOutlined />} />
<FloatButton icon={<CommentOutlined />} />
<FloatButton icon={<ContextMenuIcon onClick={() => setContextMenuEnable(!contextMenuEnabled)} />} />
</FloatButton.Group>
</>
);

View File

@ -0,0 +1,281 @@
import React, { useEffect, useRef, useState } from 'react';
import { css } from '@nocobase/client';
import { ContextMenuTrigger } from '../../components/context-menu';
import { ContextMenu } from '../../components/context-menu';
import { ContextMenuItem } from '../../components/context-menu';
import { ContextMenuContext } from './useContextMenu';
export const ContextMenuProvider = ({ children }) => {
const [enable, setEnable] = useState(true);
const [enableDragable, setEnableDragable] = useState(false);
const ref = useRef<HTMLDivElement>();
useEffect(() => {
if (!ref.current) {
return;
}
/**
*
*/
const config = {
/** 摇杆半径 */
radius: 100,
};
/**
*
*/
const ele = ref.current;
/** 是否正在按压 */
let pressing = false;
/** 初始X坐标 */
let prevX = 0;
/** 初始Y坐标 */
let prevY = 0;
/** 当前X坐标 */
let newX = 0;
/** 当前Y坐标 */
let newY = 0;
/** 相对X坐标 */
let relX = 0;
/** 相对Y坐标 */
let relY = 0;
/** 摇杆canvas移动后应在的X坐标 */
let moveX = 0;
/** 摇杆canvas移动后应在的Y坐标 */
let moveY = 0;
/** 根据半径限制相对坐标后的X坐标 */
let displayX = 0;
/** 根据半径限制相对坐标后的Y坐标 */
let displayY = 0;
ele.addEventListener('mousedown', down);
ele.addEventListener('mousemove', move);
ele.addEventListener('mouseup', up);
ele.addEventListener('touchstart', down);
ele.addEventListener('touchmove', move);
ele.addEventListener('touchend', up);
const stickEle = createStickCanvas();
const baseEle = createBaseCanvas();
// ele.style.position = 'fixed';
ele.appendChild(baseEle);
baseEle.style.position = 'absolute';
baseEle.style.visibility = 'hidden';
ele.appendChild(stickEle);
stickEle.style.position = 'absolute';
stickEle.style.visibility = 'hidden';
/**
*
* @param {} event TouchEvent | MouseEvent
*/
function down(event: MouseEvent) {
if (event.buttons === 1) {
console.log('🚀 ~ file: ContextMenu.provider.tsx:81 ~ down ~ event:', event);
pressing = true;
prevX = getClientPosition(event).x;
prevY = getClientPosition(event).y;
baseEle.style.visibility = 'visible';
stickEle.style.visibility = 'visible';
stickMove(stickEle.style, prevX - stickEle.width / 2, prevY - stickEle.height / 2);
stickMove(baseEle.style, prevX - baseEle.width / 2, prevY - baseEle.height / 2);
}
}
/**
* display坐标
* @param {} event TouchEvent | MouseEvent
*/
function up(event) {
pressing = false;
baseEle.style.visibility = 'hidden';
stickEle.style.visibility = 'hidden';
displayX = 0;
displayY = 0;
}
function moveTable(relX: number, relY: number) {
const tables = document.querySelectorAll('.ant-table-content');
const main = document.querySelector('main');
tables.forEach((table) => {
table.scrollBy(relX * 10, 0);
});
main.scrollBy(0, relY * 10 * -1);
}
let moveRelX = 0;
let moveRelY = 0;
const timerId = setInterval(() => {
if (pressing) {
moveTable(moveRelX, moveRelY);
}
}, 16);
/**
*
*
* @param {} event TouchEvent | MouseEvent
*/
function move(event) {
if (pressing) {
newX = getClientPosition(event).x;
newY = getClientPosition(event).y;
relX = newX - prevX;
relY = prevY - newY;
const distance = Math.sqrt(Math.pow(relX, 2) + Math.pow(relY, 2));
const stickNormalizedX = relX / distance;
const stickNormalizedY = relY / distance;
moveRelX = stickNormalizedX;
moveRelY = stickNormalizedY;
// moveTable(stickNormalizedX, stickNormalizedY);
if (distance <= config.radius) {
moveX = newX - stickEle.width / 2;
moveY = newY - stickEle.height / 2;
stickMove(stickEle.style, moveX, moveY);
} else {
moveX = stickNormalizedX * config.radius + prevX - stickEle.width / 2;
moveY = -stickNormalizedY * config.radius + prevY - stickEle.height / 2;
stickMove(stickEle.style, moveX, moveY);
}
displayX = Math.round(stickNormalizedX * config.radius);
displayY = Math.round(stickNormalizedY * config.radius);
}
}
/**
* canvas
*/
function createStickCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 86;
canvas.height = 86;
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.lineWidth = 6;
ctx.arc(canvas.width / 2, canvas.width / 2, 40, 0, Math.PI * 2, true);
ctx.stroke();
return canvas;
}
/**
* canvas
*/
function createBaseCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 126;
canvas.height = 126;
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.lineWidth = 6;
ctx.arc(canvas.width / 2, canvas.width / 2, 40, 0, Math.PI * 2, true);
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.arc(canvas.width / 2, canvas.width / 2, 60, 0, Math.PI * 2, true);
ctx.stroke();
return canvas;
}
/**
*
* @param {} style /style属性
* @param {} x x轴移动距离
* @param {} y y轴移动距离
* @example stickMove(stickEle.style, (prevX - stickEle.width/2), (prevY - stickEle.height/2));
*/
function stickMove(style, x, y) {
const transform = supportTransform();
if (transform) {
style[transform] = 'translate(' + x + 'px,' + y + 'px)';
} else {
style.left = x + 'px';
style.top = y + 'px';
}
}
/**
* translate
*/
function supportTransform() {
const styles = ['webkitTransform', 'MozTransform', 'msTransform', 'OTransform', 'transform'];
const el = document.createElement('p');
let style;
for (let i = 0; i < styles.length; i++) {
style = styles[i];
if (null != el.style[style]) {
return style;
}
}
}
/**
* client坐标
* @param {} event TouchEvent | MouseEvent
*/
function getClientPosition(event) {
if (event instanceof TouchEvent) {
return {
x: event.touches[0].clientX,
y: event.touches[0].clientY,
};
} else {
return {
x: event.clientX,
y: event.clientY,
};
}
}
return () => {
clearInterval(timerId);
};
});
return (
<ContextMenuContext.Provider value={{ contextMenuEnabled: enable, setContextMenuEnable: setEnable }}>
{enable ? (
<>
<ContextMenu id="my-context-menu-1">
<ContextMenuItem
onClick={() => {
setEnableDragable((enable) => !enable);
}}
>
</ContextMenuItem>
</ContextMenu>
<ContextMenuTrigger id="my-context-menu-1">
{enableDragable && (
<div
ref={ref}
className={css`
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 1000;
background-color: rgba(141, 141, 113, 0.274);
`}
></div>
)}
{children}
</ContextMenuTrigger>
</>
) : (
<>{children}</>
)}
</ContextMenuContext.Provider>
);
};

View File

@ -0,0 +1,8 @@
import { Plugin } from '@nocobase/client';
import { ContextMenuProvider } from './ContextMenu.provider';
export class PluginContextMenu extends Plugin {
async load() {
this.app.use(ContextMenuProvider);
}
}

View File

@ -0,0 +1,12 @@
import React, { createContext, useContext } from 'react';
interface ContextMenuContext {
contextMenuEnabled: boolean;
setContextMenuEnable: (enabled: boolean) => void;
}
export const ContextMenuContext = createContext<Partial<ContextMenuContext>>({});
export const useContextMenu = () => {
return useContext(ContextMenuContext);
};

View File

@ -80,6 +80,7 @@ import { PluginPageStyle } from './features/page-style';
import { PluginHeraVersion } from './features/hera-version';
import { PluginAssistant } from './features/assistant';
import { TstzrangeFieldInterface } from './interfaces/TstzrangeFieldInterface';
import { PluginContextMenu } from './features/context-menu';
export { usePDFViewerRef } from './schema-initializer';
export * from './components/custom-components/custom-components';
@ -92,6 +93,7 @@ export class PluginCoreClient extends Plugin {
await this.app.pm.add(DepartmentsPlugin);
await this.app.pm.add(PluginPageStyle);
await this.app.pm.add(PluginHeraVersion);
await this.app.pm.add(PluginContextMenu);
await this.app.pm.add(PluginAssistant);
}

View File

@ -257,6 +257,9 @@ importers:
gulp-typescript:
specifier: 6.0.0-alpha.1
version: 6.0.0-alpha.1(typescript@5.4.4)
sass:
specifier: ^1.75.0
version: 1.75.0
tar:
specifier: ^6.2.0
version: 6.2.0
@ -271,7 +274,7 @@ importers:
version: 3.0.0
vite:
specifier: ^5.0.0
version: 5.1.5(@types/node@20.12.7)
version: 5.1.5(@types/node@20.12.7)(sass@1.75.0)
vite-plugin-css-injected-by-js:
specifier: ^3.2.1
version: 3.3.0(vite@5.1.5)
@ -1234,7 +1237,7 @@ importers:
version: 6.3.3
vite:
specifier: ^5.0.0
version: 5.1.5(@types/node@20.12.7)
version: 5.1.5(@types/node@20.12.7)(sass@1.75.0)
vitest:
specifier: ^1.4.0
version: 1.4.0(@types/node@20.12.7)(jsdom@16.7.0)
@ -1531,6 +1534,9 @@ importers:
react-router-dom:
specifier: ^6.11.2
version: 6.21.0(react-dom@18.2.0)(react@18.2.0)
react-transition-group:
specifier: ^4.4.5
version: 4.4.5(react-dom@18.2.0)(react@18.2.0)
redis:
specifier: ^4.6.11
version: 4.6.13
@ -9652,7 +9658,7 @@ packages:
peerDependencies:
react: '>=16.3.0'
dependencies:
'@babel/runtime': 7.23.6
'@babel/runtime': 7.24.4
hoist-non-react-statics: 3.3.2
react: 18.1.0
react-is: 16.13.1
@ -9663,7 +9669,7 @@ packages:
peerDependencies:
react: '>=16.3.0'
dependencies:
'@babel/runtime': 7.23.6
'@babel/runtime': 7.24.4
hoist-non-react-statics: 3.3.2
react: 18.2.0
react-is: 16.13.1
@ -10511,13 +10517,13 @@ packages:
/@react-pdf/fns@2.2.1:
resolution: {integrity: sha512-s78aDg0vDYaijU5lLOCsUD+qinQbfOvcNeaoX9AiE7+kZzzCo6B/nX+l48cmt9OosJmvZvE9DWR9cLhrhOi2pA==}
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
dev: true
/@react-pdf/font@2.4.4:
resolution: {integrity: sha512-yjK5eSY+LcbxS0m+sOYln8GdgIbUgti4xjwf14kx8OSsOMJQJyHFALHMh2cLcKJR9yZeqVDo1FwCsY6gw1yCkg==}
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
'@react-pdf/types': 2.4.1
cross-fetch: 3.1.8
fontkit: 2.0.2
@ -10529,7 +10535,7 @@ packages:
/@react-pdf/image@2.3.4:
resolution: {integrity: sha512-IE34l7gfTdaxXe3XR9240xMZsFdxF1myIwmEWK28XoeTaucUPAUyOiNcFSGRT59vNuZVBuakYz3BlGGrkvAPVQ==}
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
'@react-pdf/png-js': 2.3.1
cross-fetch: 3.1.8
jay-peg: 1.0.1
@ -10540,7 +10546,7 @@ packages:
/@react-pdf/layout@3.11.2:
resolution: {integrity: sha512-5EiHJ+Eb0odqnkWll9pWbTp+dwH1QRm7mOXDMiklqIWK98eI7e3cEae5Dgr0TtdnB7KgPW9Tvul2CwRJTwq54A==}
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
'@react-pdf/fns': 2.2.1
'@react-pdf/image': 2.3.4
'@react-pdf/pdfkit': 3.1.6
@ -10559,7 +10565,7 @@ packages:
/@react-pdf/pdfkit@3.1.6:
resolution: {integrity: sha512-U96VVhphniDBsLbmeJHgEml15nng8cr90mmEfPATh98gsqg6wev0avBr4k9XPjLdaN1f2xTXD4VdlaMYJZ+n7Q==}
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
'@react-pdf/png-js': 2.3.1
browserify-zlib: 0.2.0
crypto-js: 4.2.0
@ -10581,7 +10587,7 @@ packages:
/@react-pdf/render@3.4.3:
resolution: {integrity: sha512-9LL059vfwrK1gA0uIA4utpQ/pUH9EW/yia4bb7pCoARs8IlupY5UP265jgax15ua0p+MdUwShZzQ9rilu7kGsw==}
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
'@react-pdf/fns': 2.2.1
'@react-pdf/primitives': 3.1.1
'@react-pdf/textkit': 4.4.1
@ -10618,7 +10624,7 @@ packages:
/@react-pdf/stylesheet@4.2.4:
resolution: {integrity: sha512-CgRfDzeMtnV0GL7zSn381NubmgwqKhFKcK1YrWX3azl/KWVh52jjFd3HWi6dvcETNT862mjWz5MnExe4WOBJXA==}
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
'@react-pdf/fns': 2.2.1
'@react-pdf/types': 2.4.1
color-string: 1.9.1
@ -10630,7 +10636,7 @@ packages:
/@react-pdf/textkit@4.4.1:
resolution: {integrity: sha512-Jl9wdTqIvJ5pX+vAGz0EOhP7ut5Two9H6CzTKo/YYPeD79cM2yTXF3JzTERBC28y7LR0Waq9D2LHQjI+b/EYUQ==}
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
'@react-pdf/fns': 2.2.1
bidi-js: 1.0.3
hyphen: 1.10.4
@ -13141,7 +13147,7 @@ packages:
/@umijs/history@5.3.1:
resolution: {integrity: sha512-/e0cEGrR2bIWQD7pRl3dl9dcyRGeC9hoW0OCvUTT/hjY0EfUrkd6G8ZanVghPMpDuY5usxq9GVcvrT8KNXLWvA==}
dependencies:
'@babel/runtime': 7.23.6
'@babel/runtime': 7.24.4
query-string: 6.14.1
/@umijs/lint@4.1.10(eslint@8.57.0)(stylelint@14.16.1)(typescript@5.4.5):
@ -13578,7 +13584,7 @@ packages:
'@babel/plugin-transform-react-jsx-self': 7.24.1(@babel/core@7.24.4)
'@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.4)
react-refresh: 0.14.0
vite: 4.5.2(@types/node@20.12.2)(less@4.1.3)
vite: 4.5.2(@types/node@20.12.7)(less@4.1.3)
transitivePeerDependencies:
- supports-color
@ -13593,7 +13599,7 @@ packages:
'@babel/plugin-transform-react-jsx-source': 7.23.3(@babel/core@7.23.6)
'@types/babel__core': 7.20.5
react-refresh: 0.14.0
vite: 5.1.5(@types/node@20.12.7)
vite: 5.1.5(@types/node@20.12.7)(sass@1.75.0)
transitivePeerDependencies:
- supports-color
dev: false
@ -17557,7 +17563,7 @@ packages:
/dom-helpers@5.2.1:
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
csstype: 3.1.3
dev: true
@ -20257,7 +20263,7 @@ packages:
/history@5.3.0:
resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==}
dependencies:
'@babel/runtime': 7.23.6
'@babel/runtime': 7.24.4
/hmac-drbg@1.0.1:
resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==}
@ -20590,6 +20596,10 @@ packages:
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
dev: true
/immutable@4.3.5:
resolution: {integrity: sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==}
dev: false
/import-fresh@3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
@ -25061,7 +25071,7 @@ packages:
dependencies:
nanoid: 3.3.7
picocolors: 1.0.0
source-map-js: 1.0.2
source-map-js: 1.2.0
dev: false
/postcss@8.4.38:
@ -25567,7 +25577,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
classnames: 2.5.1
dom-align: 1.12.4
rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0)
@ -25895,7 +25905,7 @@ packages:
react: '*'
react-dom: '*'
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
classnames: 2.5.1
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
rc-overflow: 1.3.2(react-dom@18.2.0)(react@18.2.0)
@ -26089,7 +26099,7 @@ packages:
react: '*'
react-dom: '*'
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
classnames: 2.5.1
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
rc-util: 5.39.1(react-dom@18.2.0)(react@18.2.0)
@ -26136,7 +26146,7 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
classnames: 2.5.1
rc-align: 4.0.15(react-dom@18.2.0)(react@18.2.0)
rc-motion: 2.9.0(react-dom@18.2.0)(react@18.2.0)
@ -26303,7 +26313,7 @@ packages:
peerDependencies:
react: '>=16.13.1'
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
react: 18.2.0
dev: false
@ -26327,7 +26337,7 @@ packages:
react: ^16.6.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0
dependencies:
'@babel/runtime': 7.23.6
'@babel/runtime': 7.24.4
invariant: 2.2.4
prop-types: 15.8.1
react: 18.1.0
@ -26341,7 +26351,7 @@ packages:
react: ^16.6.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0
dependencies:
'@babel/runtime': 7.23.6
'@babel/runtime': 7.24.4
invariant: 2.2.4
prop-types: 15.8.1
react: 18.2.0
@ -26459,7 +26469,7 @@ packages:
react: '>=16.3.0'
react-dom: '>=16.3.0'
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
'@popperjs/core': 2.11.8
'@restart/hooks': 0.4.15(react@18.2.0)
'@types/warning': 3.0.3
@ -26523,7 +26533,7 @@ packages:
react-native:
optional: true
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
'@types/react-redux': 7.1.33
hoist-non-react-statics: 3.3.2
loose-envify: 1.4.0
@ -26653,6 +26663,20 @@ packages:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
/react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
peerDependencies:
react: '>=16.6.0'
react-dom: '>=16.6.0'
dependencies:
'@babel/runtime': 7.24.4
dom-helpers: 5.2.1
loose-envify: 1.4.0
prop-types: 15.8.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: true
/react@18.1.0:
resolution: {integrity: sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==}
engines: {node: '>=0.10.0'}
@ -26833,7 +26857,7 @@ packages:
/redux@4.2.1:
resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==}
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
/reflect-metadata@0.1.14:
resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==}
@ -27464,6 +27488,16 @@ packages:
postcss: 8.4.35
dev: false
/sass@1.75.0:
resolution: {integrity: sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==}
engines: {node: '>=14.0.0'}
hasBin: true
dependencies:
chokidar: 3.6.0
immutable: 4.3.5
source-map-js: 1.2.0
dev: false
/sax@1.3.0:
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
@ -27998,11 +28032,6 @@ packages:
is-plain-obj: 4.1.0
sort-object-keys: 1.1.3
/source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
dev: false
/source-map-js@1.2.0:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'}
@ -29831,7 +29860,7 @@ packages:
peerDependencies:
react: '>=15.0.0'
dependencies:
'@babel/runtime': 7.24.0
'@babel/runtime': 7.24.4
'@types/react': 18.2.79
invariant: 2.2.4
react: 18.2.0
@ -30378,7 +30407,7 @@ packages:
peerDependencies:
vite: '>2.0.0-0'
dependencies:
vite: 5.1.5(@types/node@20.12.7)
vite: 5.1.5(@types/node@20.12.7)(sass@1.75.0)
dev: false
/vite-plugin-lib-inject-css@1.2.0(vite@5.1.5):
@ -30388,7 +30417,7 @@ packages:
dependencies:
magic-string: 0.30.8
picocolors: 1.0.0
vite: 5.1.5(@types/node@20.12.7)
vite: 5.1.5(@types/node@20.12.7)(sass@1.75.0)
dev: false
/vite@4.5.2(@types/node@20.12.2)(less@4.1.3):
@ -30426,6 +30455,7 @@ packages:
rollup: 3.29.4
optionalDependencies:
fsevents: 2.3.3
dev: false
/vite@4.5.2(@types/node@20.12.7)(less@4.1.3):
resolution: {integrity: sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==}
@ -30462,9 +30492,8 @@ packages:
rollup: 3.29.4
optionalDependencies:
fsevents: 2.3.3
dev: true
/vite@5.1.5(@types/node@20.12.7):
/vite@5.1.5(@types/node@20.12.7)(sass@1.75.0):
resolution: {integrity: sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
@ -30496,6 +30525,7 @@ packages:
esbuild: 0.19.9
postcss: 8.4.35
rollup: 4.9.0
sass: 1.75.0
optionalDependencies:
fsevents: 2.3.3
dev: false