feat: searchJump and calculator
Reviewed-on: daoyoucloud/tachybase#1246
This commit is contained in:
parent
c484545c39
commit
6af6e9a1f8
@ -19,6 +19,7 @@ import {
|
|||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
useACLRoleContext,
|
useACLRoleContext,
|
||||||
useAdminSchemaUid,
|
useAdminSchemaUid,
|
||||||
|
useApp,
|
||||||
useDocumentTitle,
|
useDocumentTitle,
|
||||||
useRequest,
|
useRequest,
|
||||||
useSystemSettings,
|
useSystemSettings,
|
||||||
@ -403,6 +404,7 @@ const SetThemeOfHeaderSubmenu = ({ children }) => {
|
|||||||
export const InternalAdminLayout = (props: any) => {
|
export const InternalAdminLayout = (props: any) => {
|
||||||
const sideMenuRef = useRef<HTMLDivElement>();
|
const sideMenuRef = useRef<HTMLDivElement>();
|
||||||
const result = useSystemSettings();
|
const result = useSystemSettings();
|
||||||
|
const app = useApp();
|
||||||
const params = useParams<any>();
|
const params = useParams<any>();
|
||||||
const { styles } = useStyles();
|
const { styles } = useStyles();
|
||||||
return (
|
return (
|
||||||
@ -411,7 +413,12 @@ export const InternalAdminLayout = (props: any) => {
|
|||||||
<Layout.Header className={styles.header}>
|
<Layout.Header className={styles.header}>
|
||||||
<div className={styles.headerA}>
|
<div className={styles.headerA}>
|
||||||
<div className={styles.headerB}>
|
<div className={styles.headerB}>
|
||||||
<div className={styles.titleContainer}>
|
<div
|
||||||
|
className={styles.titleContainer}
|
||||||
|
onClick={() => {
|
||||||
|
location.href = app.adminUrl;
|
||||||
|
}}
|
||||||
|
>
|
||||||
<img className={styles.logo} src={result?.data?.data?.logo?.url} />
|
<img className={styles.logo} src={result?.data?.data?.logo?.url} />
|
||||||
<h1 className={styles.title}>{result?.data?.data?.title}</h1>
|
<h1 className={styles.title}>{result?.data?.data?.title}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,6 +48,7 @@ export * from './modules/blocks/data-blocks/form';
|
|||||||
export * from './modules/blocks/data-blocks/table';
|
export * from './modules/blocks/data-blocks/table';
|
||||||
export * from './modules/blocks/data-blocks/table-selector';
|
export * from './modules/blocks/data-blocks/table-selector';
|
||||||
export * from 'react-i18next';
|
export * from 'react-i18next';
|
||||||
|
export { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
export * from './modules/blocks/useParentRecordCommon';
|
export * from './modules/blocks/useParentRecordCommon';
|
||||||
|
|
||||||
|
@ -1,60 +1,45 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { css, useDesignable } from '@tachybase/client';
|
import { useDesignable, useHotkeys } from '@tachybase/client';
|
||||||
|
|
||||||
import { CalculatorOutlined, CommentOutlined, HighlightOutlined, ToolOutlined } from '@ant-design/icons';
|
import {
|
||||||
|
CalculatorOutlined,
|
||||||
|
CommentOutlined,
|
||||||
|
HighlightOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
ToolOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
import { FloatButton } from 'antd';
|
import { FloatButton } from 'antd';
|
||||||
import { createPortal } from 'react-dom';
|
|
||||||
|
|
||||||
import { useContextMenu } from '../context-menu/useContextMenu';
|
import { useContextMenu } from '../context-menu/useContextMenu';
|
||||||
import { CalculatorWrapper } from './calculator/Calculator';
|
import { useCalculator } from './calculator/CalculatorProvider';
|
||||||
|
import { useSearchAndJump } from './search-and-jump';
|
||||||
|
|
||||||
export const AssistantProvider = ({ children }) => {
|
export const AssistantProvider = ({ children }) => {
|
||||||
const { designable, setDesignable } = useDesignable();
|
const { designable, setDesignable } = useDesignable();
|
||||||
|
const { visible, setVisible } = useCalculator();
|
||||||
|
const { setOpen } = useSearchAndJump();
|
||||||
|
|
||||||
|
// 快捷键切换编辑状态
|
||||||
|
useHotkeys('Ctrl+Shift+U', () => setDesignable(!designable), [designable]);
|
||||||
|
useHotkeys('Ctrl+K', () => setOpen((open) => !open), []);
|
||||||
|
useHotkeys('Cmd+K', () => setOpen((open) => !open), []);
|
||||||
|
|
||||||
const { contextMenuEnabled, setContextMenuEnable } = useContextMenu();
|
const { contextMenuEnabled, setContextMenuEnable } = useContextMenu();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{children}
|
{children}
|
||||||
<FloatButton.Group trigger="hover" type="default" style={{ right: 24, zIndex: 1250 }} icon={<ToolOutlined />}>
|
<FloatButton.Group trigger="hover" type="default" style={{ right: 24, zIndex: 1250 }} icon={<ToolOutlined />}>
|
||||||
|
<FloatButton icon={<SearchOutlined />} onClick={() => setOpen(true)} />
|
||||||
<FloatButton
|
<FloatButton
|
||||||
icon={<HighlightOutlined />}
|
icon={<HighlightOutlined />}
|
||||||
type={designable ? 'primary' : 'default'}
|
type={designable ? 'primary' : 'default'}
|
||||||
onClick={() => setDesignable(!designable)}
|
onClick={() => setDesignable(!designable)}
|
||||||
/>
|
/>
|
||||||
<FloatButton
|
<FloatButton
|
||||||
|
type={visible ? 'primary' : 'default'}
|
||||||
icon={<CalculatorOutlined />}
|
icon={<CalculatorOutlined />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
createPortal(
|
setVisible((visible) => !visible);
|
||||||
// <div
|
|
||||||
// className={css`
|
|
||||||
// position: fixed;
|
|
||||||
// top: 0;
|
|
||||||
// left: 0;
|
|
||||||
// width: 1000px;
|
|
||||||
// height: 1000px;
|
|
||||||
// z-index: 9999;
|
|
||||||
// background-color: red;
|
|
||||||
// `}
|
|
||||||
// >
|
|
||||||
// <div />
|
|
||||||
// {/* <CalculatorWrapper /> */}
|
|
||||||
// </div>,
|
|
||||||
<div
|
|
||||||
className={css`
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 9000;
|
|
||||||
display: 'flex';
|
|
||||||
flex-direction: column;
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
dsfasdfasfasdfadsf
|
|
||||||
</div>,
|
|
||||||
document.body,
|
|
||||||
);
|
|
||||||
alert('---');
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FloatButton icon={<CommentOutlined />} />
|
<FloatButton icon={<CommentOutlined />} />
|
||||||
|
@ -5,28 +5,36 @@ export const AutoScalingText = ({ children }) => {
|
|||||||
const [scale, setScale] = useState<number>(1);
|
const [scale, setScale] = useState<number>(1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ref.current) {
|
const observer = new ResizeObserver((entries) => {
|
||||||
return;
|
const node = entries[0].target as HTMLDivElement;
|
||||||
}
|
const parentNode = node.parentNode as HTMLDivElement;
|
||||||
|
|
||||||
|
const availableWidth = parentNode.offsetWidth;
|
||||||
|
const actualWidth = node.offsetWidth;
|
||||||
|
const actualScale = availableWidth / actualWidth;
|
||||||
|
|
||||||
|
setScale((scale) => {
|
||||||
|
if (actualScale < 1) {
|
||||||
|
return actualScale;
|
||||||
|
} else if (scale < 1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
const node = ref.current;
|
const node = ref.current;
|
||||||
const parentNode = node.parentNode;
|
observer.observe(node);
|
||||||
|
return () => observer.unobserve(node);
|
||||||
// @ts-ignore
|
}, []);
|
||||||
const availableWidth = parentNode.offsetWidth;
|
|
||||||
const actualWidth = node.offsetWidth;
|
|
||||||
const actualScale = availableWidth / actualWidth;
|
|
||||||
|
|
||||||
if (scale === actualScale) return;
|
|
||||||
|
|
||||||
if (actualScale < 1) {
|
|
||||||
setScale(actualScale);
|
|
||||||
} else if (scale < 1) {
|
|
||||||
setScale(1);
|
|
||||||
}
|
|
||||||
}, [scale]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="auto-scaling-text" style={{ transform: `scale(${scale},${scale})` }} ref={ref}>
|
<div
|
||||||
|
className="auto-scaling-text"
|
||||||
|
style={{
|
||||||
|
transition: 'transform 0.5s ease',
|
||||||
|
transform: `scale(${scale},${scale})`,
|
||||||
|
}}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { css } from '@tachybase/client';
|
||||||
|
|
||||||
import { CalculatorDisplay } from './CalculatorDisplay';
|
import { CalculatorDisplay } from './CalculatorDisplay';
|
||||||
import { CalculatorKey } from './CalculatorKey';
|
import { CalculatorKey } from './CalculatorKey';
|
||||||
@ -12,48 +13,33 @@ const CalculatorOperations = {
|
|||||||
'=': (prevValue, nextValue) => nextValue,
|
'=': (prevValue, nextValue) => nextValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Calculator extends React.Component {
|
const Calculator = React.forwardRef<{ className: string }, any>(({ className }, ref) => {
|
||||||
state = {
|
const [value, setValue] = useState(null);
|
||||||
value: null,
|
const [displayValue, setDisplayValue] = useState('0');
|
||||||
displayValue: '0',
|
const [operator, setOperator] = useState(null);
|
||||||
operator: null,
|
const [waitingForOperand, setWaitingForOperand] = useState(false);
|
||||||
waitingForOperand: false,
|
|
||||||
|
const clearAll = () => {
|
||||||
|
setValue(null);
|
||||||
|
setDisplayValue('0');
|
||||||
|
setOperator(null);
|
||||||
|
setWaitingForOperand(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
clearAll() {
|
const clearDisplay = () => {
|
||||||
this.setState({
|
setDisplayValue('0');
|
||||||
value: null,
|
};
|
||||||
displayValue: '0',
|
|
||||||
operator: null,
|
|
||||||
waitingForOperand: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
clearDisplay() {
|
const clearLastChar = () => {
|
||||||
this.setState({
|
setDisplayValue(displayValue.substring(0, displayValue.length - 1) || '0');
|
||||||
displayValue: '0',
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
clearLastChar() {
|
const toggleSign = () => {
|
||||||
const { displayValue } = this.state;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
displayValue: displayValue.substring(0, displayValue.length - 1) || '0',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSign() {
|
|
||||||
const { displayValue } = this.state;
|
|
||||||
const newValue = parseFloat(displayValue) * -1;
|
const newValue = parseFloat(displayValue) * -1;
|
||||||
|
setDisplayValue(String(newValue));
|
||||||
|
};
|
||||||
|
|
||||||
this.setState({
|
const inputPercent = () => {
|
||||||
displayValue: String(newValue),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
inputPercent() {
|
|
||||||
const { displayValue } = this.state;
|
|
||||||
const currentValue = parseFloat(displayValue);
|
const currentValue = parseFloat(displayValue);
|
||||||
|
|
||||||
if (currentValue === 0) return;
|
if (currentValue === 0) return;
|
||||||
@ -61,187 +47,155 @@ class Calculator extends React.Component {
|
|||||||
const fixedDigits = displayValue.replace(/^-?\d*\.?/, '');
|
const fixedDigits = displayValue.replace(/^-?\d*\.?/, '');
|
||||||
const newValue = parseFloat(displayValue) / 100;
|
const newValue = parseFloat(displayValue) / 100;
|
||||||
|
|
||||||
this.setState({
|
setDisplayValue(String(newValue.toFixed(fixedDigits.length + 2)));
|
||||||
displayValue: String(newValue.toFixed(fixedDigits.length + 2)),
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
inputDot() {
|
|
||||||
const { displayValue } = this.state;
|
|
||||||
|
|
||||||
|
const inputDot = () => {
|
||||||
if (!/\./.test(displayValue)) {
|
if (!/\./.test(displayValue)) {
|
||||||
this.setState({
|
setDisplayValue(displayValue + '.');
|
||||||
displayValue: displayValue + '.',
|
setWaitingForOperand(false);
|
||||||
waitingForOperand: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inputDigit(digit) {
|
|
||||||
const { displayValue, waitingForOperand } = this.state;
|
|
||||||
|
|
||||||
if (waitingForOperand) {
|
|
||||||
this.setState({
|
|
||||||
displayValue: String(digit),
|
|
||||||
waitingForOperand: false,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
displayValue: displayValue === '0' ? String(digit) : displayValue + digit,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
performOperation(nextOperator) {
|
|
||||||
const { value, displayValue, operator } = this.state;
|
|
||||||
const inputValue = parseFloat(displayValue);
|
|
||||||
|
|
||||||
if (value == null) {
|
|
||||||
this.setState({
|
|
||||||
value: inputValue,
|
|
||||||
});
|
|
||||||
} else if (operator) {
|
|
||||||
const currentValue = value || 0;
|
|
||||||
const newValue = CalculatorOperations[operator](currentValue, inputValue);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
value: newValue,
|
|
||||||
displayValue: String(newValue),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
waitingForOperand: true,
|
|
||||||
operator: nextOperator,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyDown = (event) => {
|
|
||||||
let { key } = event;
|
|
||||||
|
|
||||||
if (key === 'Enter') key = '=';
|
|
||||||
|
|
||||||
if (/\d/.test(key)) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.inputDigit(parseInt(key, 10));
|
|
||||||
} else if (key in CalculatorOperations) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.performOperation(key);
|
|
||||||
} else if (key === '.') {
|
|
||||||
event.preventDefault();
|
|
||||||
this.inputDot();
|
|
||||||
} else if (key === '%') {
|
|
||||||
event.preventDefault();
|
|
||||||
this.inputPercent();
|
|
||||||
} else if (key === 'Backspace') {
|
|
||||||
event.preventDefault();
|
|
||||||
this.clearLastChar();
|
|
||||||
} else if (key === 'Clear') {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (this.state.displayValue !== '0') {
|
|
||||||
this.clearDisplay();
|
|
||||||
} else {
|
|
||||||
this.clearAll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
const inputDigit = (digit) => {
|
||||||
document.addEventListener('keydown', this.handleKeyDown);
|
if (waitingForOperand) {
|
||||||
}
|
setDisplayValue(String(digit));
|
||||||
|
setWaitingForOperand(false);
|
||||||
|
} else {
|
||||||
|
setDisplayValue(displayValue === '0' ? String(digit) : displayValue + digit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
const performOperation = (nextOperator) => {
|
||||||
document.removeEventListener('keydown', this.handleKeyDown);
|
const inputValue = parseFloat(displayValue);
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
if (value == null) {
|
||||||
const { displayValue } = this.state;
|
setValue(inputValue);
|
||||||
|
} else if (operator) {
|
||||||
|
const currentValue = value || 0;
|
||||||
|
const newValue = CalculatorOperations[operator](currentValue, inputValue);
|
||||||
|
setValue(newValue);
|
||||||
|
setDisplayValue(String(newValue));
|
||||||
|
}
|
||||||
|
setWaitingForOperand(true);
|
||||||
|
setOperator(nextOperator);
|
||||||
|
};
|
||||||
|
|
||||||
const clearDisplay = displayValue !== '0';
|
useEffect(() => {
|
||||||
const clearText = clearDisplay ? 'C' : 'AC';
|
const handleKeyDown = (event) => {
|
||||||
|
let { key } = event;
|
||||||
|
|
||||||
return (
|
if (key === 'Enter') key = '=';
|
||||||
<div className={this.props.className}>
|
|
||||||
<div className="calculator">
|
if (/\d/.test(key)) {
|
||||||
<CalculatorDisplay value={displayValue} />
|
event.preventDefault();
|
||||||
<div className="calculator-keypad">
|
inputDigit(parseInt(key, 10));
|
||||||
<div className="input-keys">
|
} else if (key in CalculatorOperations) {
|
||||||
<div className="function-keys">
|
event.preventDefault();
|
||||||
<CalculatorKey
|
performOperation(key);
|
||||||
className="key-clear"
|
} else if (key === '.') {
|
||||||
onPress={() => (clearDisplay ? this.clearDisplay() : this.clearAll())}
|
event.preventDefault();
|
||||||
>
|
inputDot();
|
||||||
{clearText}
|
} else if (key === '%') {
|
||||||
</CalculatorKey>
|
event.preventDefault();
|
||||||
<CalculatorKey className="key-sign" onPress={() => this.toggleSign()}>
|
inputPercent();
|
||||||
±
|
} else if (key === 'Backspace') {
|
||||||
</CalculatorKey>
|
event.preventDefault();
|
||||||
<CalculatorKey className="key-percent" onPress={() => this.inputPercent()}>
|
clearLastChar();
|
||||||
%
|
} else if (key === 'Clear') {
|
||||||
</CalculatorKey>
|
event.preventDefault();
|
||||||
</div>
|
|
||||||
<div className="digit-keys">
|
if (displayValue !== '0') {
|
||||||
<CalculatorKey className="key-0" onPress={() => this.inputDigit(0)}>
|
clearDisplay();
|
||||||
0
|
} else {
|
||||||
</CalculatorKey>
|
clearAll();
|
||||||
<CalculatorKey className="key-dot" onPress={() => this.inputDot()}>
|
}
|
||||||
●
|
}
|
||||||
</CalculatorKey>
|
};
|
||||||
<CalculatorKey className="key-1" onPress={() => this.inputDigit(1)}>
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
1
|
return () => {
|
||||||
</CalculatorKey>
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
<CalculatorKey className="key-2" onPress={() => this.inputDigit(2)}>
|
};
|
||||||
2
|
});
|
||||||
</CalculatorKey>
|
|
||||||
<CalculatorKey className="key-3" onPress={() => this.inputDigit(3)}>
|
const clearKey = displayValue !== '0';
|
||||||
3
|
const clearText = clearKey ? 'C' : 'AC';
|
||||||
</CalculatorKey>
|
|
||||||
<CalculatorKey className="key-4" onPress={() => this.inputDigit(4)}>
|
return (
|
||||||
4
|
<div className="calculator" ref={ref as any}>
|
||||||
</CalculatorKey>
|
<CalculatorDisplay value={displayValue} />
|
||||||
<CalculatorKey className="key-5" onPress={() => this.inputDigit(5)}>
|
<div className="calculator-keypad">
|
||||||
5
|
<div className="input-keys">
|
||||||
</CalculatorKey>
|
<div className="function-keys">
|
||||||
<CalculatorKey className="key-6" onPress={() => this.inputDigit(6)}>
|
<CalculatorKey className="key-clear" onPress={() => (clearKey ? clearDisplay() : clearAll())}>
|
||||||
6
|
{clearText}
|
||||||
</CalculatorKey>
|
</CalculatorKey>
|
||||||
<CalculatorKey className="key-7" onPress={() => this.inputDigit(7)}>
|
<CalculatorKey className="key-sign" onPress={() => toggleSign()}>
|
||||||
7
|
±
|
||||||
</CalculatorKey>
|
</CalculatorKey>
|
||||||
<CalculatorKey className="key-8" onPress={() => this.inputDigit(8)}>
|
<CalculatorKey className="key-percent" onPress={() => inputPercent()}>
|
||||||
8
|
%
|
||||||
</CalculatorKey>
|
</CalculatorKey>
|
||||||
<CalculatorKey className="key-9" onPress={() => this.inputDigit(9)}>
|
</div>
|
||||||
9
|
<div className="digit-keys">
|
||||||
</CalculatorKey>
|
<CalculatorKey className="key-0" onPress={() => inputDigit(0)}>
|
||||||
</div>
|
0
|
||||||
</div>
|
</CalculatorKey>
|
||||||
<div className="operator-keys">
|
<CalculatorKey className="key-dot" onPress={() => inputDot()}>
|
||||||
<CalculatorKey className="key-divide" onPress={() => this.performOperation('/')}>
|
●
|
||||||
÷
|
</CalculatorKey>
|
||||||
</CalculatorKey>
|
<CalculatorKey className="key-1" onPress={() => inputDigit(1)}>
|
||||||
<CalculatorKey className="key-multiply" onPress={() => this.performOperation('*')}>
|
1
|
||||||
×
|
</CalculatorKey>
|
||||||
</CalculatorKey>
|
<CalculatorKey className="key-2" onPress={() => inputDigit(2)}>
|
||||||
<CalculatorKey className="key-subtract" onPress={() => this.performOperation('-')}>
|
2
|
||||||
−
|
</CalculatorKey>
|
||||||
</CalculatorKey>
|
<CalculatorKey className="key-3" onPress={() => inputDigit(3)}>
|
||||||
<CalculatorKey className="key-add" onPress={() => this.performOperation('+')}>
|
3
|
||||||
+
|
</CalculatorKey>
|
||||||
</CalculatorKey>
|
<CalculatorKey className="key-4" onPress={() => inputDigit(4)}>
|
||||||
<CalculatorKey className="key-equals" onPress={() => this.performOperation('=')}>
|
4
|
||||||
=
|
</CalculatorKey>
|
||||||
</CalculatorKey>
|
<CalculatorKey className="key-5" onPress={() => inputDigit(5)}>
|
||||||
</div>
|
5
|
||||||
|
</CalculatorKey>
|
||||||
|
<CalculatorKey className="key-6" onPress={() => inputDigit(6)}>
|
||||||
|
6
|
||||||
|
</CalculatorKey>
|
||||||
|
<CalculatorKey className="key-7" onPress={() => inputDigit(7)}>
|
||||||
|
7
|
||||||
|
</CalculatorKey>
|
||||||
|
<CalculatorKey className="key-8" onPress={() => inputDigit(8)}>
|
||||||
|
8
|
||||||
|
</CalculatorKey>
|
||||||
|
<CalculatorKey className="key-9" onPress={() => inputDigit(9)}>
|
||||||
|
9
|
||||||
|
</CalculatorKey>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="operator-keys">
|
||||||
|
<CalculatorKey className="key-divide" onPress={() => performOperation('/')}>
|
||||||
|
÷
|
||||||
|
</CalculatorKey>
|
||||||
|
<CalculatorKey className="key-multiply" onPress={() => performOperation('*')}>
|
||||||
|
×
|
||||||
|
</CalculatorKey>
|
||||||
|
<CalculatorKey className="key-subtract" onPress={() => performOperation('-')}>
|
||||||
|
−
|
||||||
|
</CalculatorKey>
|
||||||
|
<CalculatorKey className="key-add" onPress={() => performOperation('+')}>
|
||||||
|
+
|
||||||
|
</CalculatorKey>
|
||||||
|
<CalculatorKey className="key-equals" onPress={() => performOperation('=')}>
|
||||||
|
=
|
||||||
|
</CalculatorKey>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
}
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
export const CalculatorWrapper = () => {
|
export const CalculatorWrapper = React.forwardRef((props, ref) => {
|
||||||
const { styles } = useStyles();
|
const { styles } = useStyles();
|
||||||
return <Calculator className={styles.container} />;
|
return <Calculator ref={ref as any} className={styles.container} />;
|
||||||
};
|
});
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
import React, { createContext, useContext, useState } from 'react';
|
||||||
|
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
|
import { CalculatorWrapper } from './Calculator';
|
||||||
|
import { Draggable } from './Draggable';
|
||||||
|
import { useStyles } from './style';
|
||||||
|
|
||||||
|
export interface CalculatorProps {
|
||||||
|
visible: any;
|
||||||
|
setVisible: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CalculatorContext = createContext<Partial<CalculatorProps>>({});
|
||||||
|
export const useCalculator = () => {
|
||||||
|
return useContext(CalculatorContext);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CalculatorProvider = ({ children }) => {
|
||||||
|
const [visible, setVisible] = useState<boolean>(false);
|
||||||
|
const { styles } = useStyles();
|
||||||
|
return (
|
||||||
|
<CalculatorContext.Provider value={{ visible, setVisible }}>
|
||||||
|
{children}
|
||||||
|
|
||||||
|
{visible
|
||||||
|
? createPortal(
|
||||||
|
<div className={styles.container}>
|
||||||
|
<Draggable>
|
||||||
|
<CalculatorWrapper />
|
||||||
|
</Draggable>
|
||||||
|
</div>,
|
||||||
|
document.body,
|
||||||
|
)
|
||||||
|
: null}
|
||||||
|
</CalculatorContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,59 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export const Draggable = ({
|
||||||
|
children,
|
||||||
|
updateTransform = (transformStr, tx, ty, tdom) => {
|
||||||
|
tdom.style.transform = transformStr;
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
const ref = useRef<any>();
|
||||||
|
const positionRef = useRef({
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
dx: 0,
|
||||||
|
dy: 0,
|
||||||
|
tx: 0,
|
||||||
|
ty: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const node = ref.current;
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const start = (event) => {
|
||||||
|
if (event.button !== 0) {
|
||||||
|
//只允许左键,右键问题在于不选择conextmenu就不会触发mouseup事件
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.addEventListener('mousemove', docMove);
|
||||||
|
positionRef.current.startX = event.pageX - positionRef.current.dx;
|
||||||
|
positionRef.current.startY = event.pageY - positionRef.current.dy;
|
||||||
|
};
|
||||||
|
const docMove = (event) => {
|
||||||
|
const tx = event.pageX - positionRef.current.startX;
|
||||||
|
const ty = event.pageY - positionRef.current.startY;
|
||||||
|
const transformStr = `translate(${tx}px,${ty}px)`;
|
||||||
|
updateTransform(transformStr, tx, ty, node);
|
||||||
|
positionRef.current.dx = tx;
|
||||||
|
positionRef.current.dy = ty;
|
||||||
|
};
|
||||||
|
const docMouseUp = (event) => {
|
||||||
|
document.removeEventListener('mousemove', docMove);
|
||||||
|
};
|
||||||
|
|
||||||
|
node.addEventListener('mousedown', start);
|
||||||
|
//用document移除对mousemove事件的监听
|
||||||
|
document.addEventListener('mouseup', docMouseUp);
|
||||||
|
return () => {
|
||||||
|
node.removeEventListener('mousedown', start);
|
||||||
|
document.removeEventListener('mouseup', docMouseUp);
|
||||||
|
};
|
||||||
|
}, [updateTransform]);
|
||||||
|
|
||||||
|
const newStyle = { ...children.props.style, cursor: 'move', userSelect: 'none' };
|
||||||
|
return React.cloneElement(React.Children.only(children), {
|
||||||
|
ref,
|
||||||
|
style: newStyle,
|
||||||
|
});
|
||||||
|
};
|
@ -3,6 +3,14 @@ import { createStyles } from '@tachybase/client';
|
|||||||
export const useStyles = createStyles(({ css }) => {
|
export const useStyles = createStyles(({ css }) => {
|
||||||
return {
|
return {
|
||||||
container: css`
|
container: css`
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 320px;
|
||||||
|
height: 520px;
|
||||||
|
z-index: 1350;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
display: block;
|
display: block;
|
||||||
background: none;
|
background: none;
|
||||||
@ -20,20 +28,6 @@ export const useStyles = createStyles(({ css }) => {
|
|||||||
box-shadow: inset 0px 0px 80px 0px rgba(0, 0, 0, 0.25);
|
box-shadow: inset 0px 0px 80px 0px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrapper {
|
|
||||||
height: 100vh;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
width: 320px;
|
|
||||||
height: 520px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calculator {
|
.calculator {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
import { Plugin } from '@tachybase/client';
|
import { Plugin } from '@tachybase/client';
|
||||||
|
|
||||||
import { AssistantProvider } from './Assistant.provider';
|
import { AssistantProvider } from './Assistant.provider';
|
||||||
|
import { CalculatorProvider } from './calculator/CalculatorProvider';
|
||||||
|
import { SearchAndJumpProvider } from './search-and-jump';
|
||||||
|
|
||||||
export class PluginAssistant extends Plugin {
|
export class PluginAssistant extends Plugin {
|
||||||
async load() {
|
async load() {
|
||||||
|
this.app.use(SearchAndJumpProvider);
|
||||||
|
this.app.use(CalculatorProvider);
|
||||||
this.app.use(AssistantProvider);
|
this.app.use(AssistantProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { Input, InputRef, Modal } from 'antd';
|
||||||
|
|
||||||
|
export const SearchAndJumpContext = createContext({
|
||||||
|
open: false,
|
||||||
|
setOpen: (open: boolean | ((open: boolean) => boolean)) => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SearchAndJumpProvider = ({ children }) => {
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SearchAndJumpContext.Provider value={{ open, setOpen }}>
|
||||||
|
{children}
|
||||||
|
<SearchAndJump open={open} setOpen={setOpen} />
|
||||||
|
</SearchAndJumpContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSearchAndJump = () => {
|
||||||
|
return useContext(SearchAndJumpContext);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SearchAndJump = ({ open, setOpen }) => {
|
||||||
|
const inputRef = useRef<InputRef>();
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && inputRef.current) {
|
||||||
|
setTimeout(() => {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}, [open, inputRef]);
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={<Input style={{ width: '100%' }} autoFocus ref={inputRef} />}
|
||||||
|
closable={false}
|
||||||
|
open={open}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={null}
|
||||||
|
>
|
||||||
|
<p></p>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user