parent
e10e627d1c
commit
c12b30ffef
@ -331,9 +331,10 @@ export class BuiltInPlugin extends Plugin {
|
||||
name: 'pinned-list',
|
||||
config: {
|
||||
items: {
|
||||
ui: { order: 100, component: 'DesignableSwitch', pin: true, snippet: 'ui.*' },
|
||||
pm: { order: 200, component: 'PluginManagerLink', pin: true, snippet: 'pm' },
|
||||
sc: { order: 300, component: 'SettingsCenterDropdown', pin: true, snippet: 'pm.*' },
|
||||
wf: { order: 100, component: 'WorkflowLink', pin: true, snippet: 'pm.*' },
|
||||
ds: { order: 200, component: 'DatasourceLink', pin: true, snippet: 'pm.*' },
|
||||
pm: { order: 300, component: 'PluginManagerLink', pin: true, snippet: 'pm' },
|
||||
sc: { order: 400, component: 'SettingsCenterDropdown', pin: true, snippet: 'pm.*' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -5,7 +5,7 @@ import { Plugin } from '../../application/Plugin';
|
||||
import { BlockTemplatesPane } from '../../schema-templates';
|
||||
import { SystemSettingsPane } from '../system-settings';
|
||||
import { PluginManager } from './PluginManager';
|
||||
import { PluginManagerLink, SettingsCenterDropdown } from './PluginManagerLink';
|
||||
import { DatasourceLink, PluginManagerLink, SettingsCenterDropdown, WorkflowLink } from './PluginManagerLink';
|
||||
import { AdminSettingsLayout } from './PluginSetting';
|
||||
|
||||
export * from './PluginManager';
|
||||
|
@ -1,23 +1,67 @@
|
||||
import React from 'react';
|
||||
import { useDesignable } from '@tachybase/client';
|
||||
import { css, useDesignable } from '@tachybase/client';
|
||||
|
||||
import { CalculatorOutlined, CommentOutlined, HighlightOutlined, ToolFilled, ToolOutlined } from '@ant-design/icons';
|
||||
import { CalculatorOutlined, CommentOutlined, HighlightOutlined, ToolOutlined } from '@ant-design/icons';
|
||||
import { FloatButton } from 'antd';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { useContextMenu } from '../context-menu/useContextMenu';
|
||||
import { CalculatorWrapper } from './calculator/Calculator';
|
||||
|
||||
export const AssistantProvider = ({ children }) => {
|
||||
const { designable, setDesignable } = useDesignable();
|
||||
const { contextMenuEnabled, setContextMenuEnable } = useContextMenu();
|
||||
const ContextMenuIcon = contextMenuEnabled ? ToolFilled : ToolOutlined;
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<FloatButton.Group trigger="hover" type="primary" style={{ right: 24, zIndex: 1250 }} icon={<ToolOutlined />}>
|
||||
<FloatButton icon={<HighlightOutlined />} onClick={() => setDesignable(!designable)} />
|
||||
<FloatButton icon={<CalculatorOutlined />} />
|
||||
<FloatButton.Group trigger="hover" type="default" style={{ right: 24, zIndex: 1250 }} icon={<ToolOutlined />}>
|
||||
<FloatButton
|
||||
icon={<HighlightOutlined />}
|
||||
type={designable ? 'primary' : 'default'}
|
||||
onClick={() => setDesignable(!designable)}
|
||||
/>
|
||||
<FloatButton
|
||||
icon={<CalculatorOutlined />}
|
||||
onClick={() => {
|
||||
createPortal(
|
||||
// <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={<ContextMenuIcon onClick={() => setContextMenuEnable(!contextMenuEnabled)} />} />
|
||||
<FloatButton
|
||||
type={contextMenuEnabled ? 'primary' : 'default'}
|
||||
icon={<ToolOutlined onClick={() => setContextMenuEnable(!contextMenuEnabled)} />}
|
||||
/>
|
||||
</FloatButton.Group>
|
||||
</>
|
||||
);
|
||||
|
@ -0,0 +1,33 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
export const AutoScalingText = ({ children }) => {
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
const [scale, setScale] = useState<number>(1);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
const node = ref.current;
|
||||
const parentNode = node.parentNode;
|
||||
|
||||
// @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 (
|
||||
<div className="auto-scaling-text" style={{ transform: `scale(${scale},${scale})` }} ref={ref}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,247 @@
|
||||
import React from 'react';
|
||||
|
||||
import { CalculatorDisplay } from './CalculatorDisplay';
|
||||
import { CalculatorKey } from './CalculatorKey';
|
||||
import { useStyles } from './style';
|
||||
|
||||
const CalculatorOperations = {
|
||||
'/': (prevValue, nextValue) => prevValue / nextValue,
|
||||
'*': (prevValue, nextValue) => prevValue * nextValue,
|
||||
'+': (prevValue, nextValue) => prevValue + nextValue,
|
||||
'-': (prevValue, nextValue) => prevValue - nextValue,
|
||||
'=': (prevValue, nextValue) => nextValue,
|
||||
};
|
||||
|
||||
class Calculator extends React.Component {
|
||||
state = {
|
||||
value: null,
|
||||
displayValue: '0',
|
||||
operator: null,
|
||||
waitingForOperand: false,
|
||||
};
|
||||
|
||||
clearAll() {
|
||||
this.setState({
|
||||
value: null,
|
||||
displayValue: '0',
|
||||
operator: null,
|
||||
waitingForOperand: false,
|
||||
});
|
||||
}
|
||||
|
||||
clearDisplay() {
|
||||
this.setState({
|
||||
displayValue: '0',
|
||||
});
|
||||
}
|
||||
|
||||
clearLastChar() {
|
||||
const { displayValue } = this.state;
|
||||
|
||||
this.setState({
|
||||
displayValue: displayValue.substring(0, displayValue.length - 1) || '0',
|
||||
});
|
||||
}
|
||||
|
||||
toggleSign() {
|
||||
const { displayValue } = this.state;
|
||||
const newValue = parseFloat(displayValue) * -1;
|
||||
|
||||
this.setState({
|
||||
displayValue: String(newValue),
|
||||
});
|
||||
}
|
||||
|
||||
inputPercent() {
|
||||
const { displayValue } = this.state;
|
||||
const currentValue = parseFloat(displayValue);
|
||||
|
||||
if (currentValue === 0) return;
|
||||
|
||||
const fixedDigits = displayValue.replace(/^-?\d*\.?/, '');
|
||||
const newValue = parseFloat(displayValue) / 100;
|
||||
|
||||
this.setState({
|
||||
displayValue: String(newValue.toFixed(fixedDigits.length + 2)),
|
||||
});
|
||||
}
|
||||
|
||||
inputDot() {
|
||||
const { displayValue } = this.state;
|
||||
|
||||
if (!/\./.test(displayValue)) {
|
||||
this.setState({
|
||||
displayValue: displayValue + '.',
|
||||
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() {
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { displayValue } = this.state;
|
||||
|
||||
const clearDisplay = displayValue !== '0';
|
||||
const clearText = clearDisplay ? 'C' : 'AC';
|
||||
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<div className="calculator">
|
||||
<CalculatorDisplay value={displayValue} />
|
||||
<div className="calculator-keypad">
|
||||
<div className="input-keys">
|
||||
<div className="function-keys">
|
||||
<CalculatorKey
|
||||
className="key-clear"
|
||||
onPress={() => (clearDisplay ? this.clearDisplay() : this.clearAll())}
|
||||
>
|
||||
{clearText}
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-sign" onPress={() => this.toggleSign()}>
|
||||
±
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-percent" onPress={() => this.inputPercent()}>
|
||||
%
|
||||
</CalculatorKey>
|
||||
</div>
|
||||
<div className="digit-keys">
|
||||
<CalculatorKey className="key-0" onPress={() => this.inputDigit(0)}>
|
||||
0
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-dot" onPress={() => this.inputDot()}>
|
||||
●
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-1" onPress={() => this.inputDigit(1)}>
|
||||
1
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-2" onPress={() => this.inputDigit(2)}>
|
||||
2
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-3" onPress={() => this.inputDigit(3)}>
|
||||
3
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-4" onPress={() => this.inputDigit(4)}>
|
||||
4
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-5" onPress={() => this.inputDigit(5)}>
|
||||
5
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-6" onPress={() => this.inputDigit(6)}>
|
||||
6
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-7" onPress={() => this.inputDigit(7)}>
|
||||
7
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-8" onPress={() => this.inputDigit(8)}>
|
||||
8
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-9" onPress={() => this.inputDigit(9)}>
|
||||
9
|
||||
</CalculatorKey>
|
||||
</div>
|
||||
</div>
|
||||
<div className="operator-keys">
|
||||
<CalculatorKey className="key-divide" onPress={() => this.performOperation('/')}>
|
||||
÷
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-multiply" onPress={() => this.performOperation('*')}>
|
||||
×
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-subtract" onPress={() => this.performOperation('-')}>
|
||||
−
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-add" onPress={() => this.performOperation('+')}>
|
||||
+
|
||||
</CalculatorKey>
|
||||
<CalculatorKey className="key-equals" onPress={() => this.performOperation('=')}>
|
||||
=
|
||||
</CalculatorKey>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const CalculatorWrapper = () => {
|
||||
const { styles } = useStyles();
|
||||
return <Calculator className={styles.container} />;
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import { AutoScalingText } from './AutoScalingText';
|
||||
|
||||
export const CalculatorDisplay = ({ value, ...props }) => {
|
||||
const language = navigator.language || 'en-US';
|
||||
let formattedValue = parseFloat(value).toLocaleString(language, {
|
||||
useGrouping: true,
|
||||
maximumFractionDigits: 6,
|
||||
});
|
||||
|
||||
// Add back missing .0 in e.g. 12.0
|
||||
const match = value.match(/\.\d*?(0*)$/);
|
||||
|
||||
if (match) formattedValue += /[1-9]/.test(match[0]) ? match[1] : match[0];
|
||||
|
||||
return (
|
||||
<div {...props} className="calculator-display">
|
||||
<AutoScalingText>{formattedValue}</AutoScalingText>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
export const CalculatorKey = ({ onPress, className, ...props }) => {
|
||||
return <button onClick={onPress} className={`calculator-key ${className}`} {...props} />;
|
||||
};
|
@ -0,0 +1,132 @@
|
||||
import { createStyles } from '@tachybase/client';
|
||||
|
||||
export const useStyles = createStyles(({ css }) => {
|
||||
return {
|
||||
container: css`
|
||||
button {
|
||||
display: block;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-family: inherit;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
button:active {
|
||||
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 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: black;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#wrapper .calculator {
|
||||
box-shadow: 0px 0px 20px 0px #aaa;
|
||||
}
|
||||
|
||||
.calculator-display {
|
||||
color: white;
|
||||
background: #1c191c;
|
||||
line-height: 130px;
|
||||
font-size: 6em;
|
||||
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.auto-scaling-text {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.calculator-display .auto-scaling-text {
|
||||
padding: 0 30px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transform-origin: right;
|
||||
}
|
||||
|
||||
.calculator-keypad {
|
||||
height: 400px;
|
||||
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.calculator .input-keys {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.calculator .function-keys {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.calculator .digit-keys {
|
||||
background: #e0e0e7;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap-reverse;
|
||||
}
|
||||
|
||||
.calculator-key {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-top: 1px solid #777;
|
||||
border-right: 1px solid #666;
|
||||
text-align: center;
|
||||
line-height: 80px;
|
||||
}
|
||||
.calculator .function-keys .calculator-key {
|
||||
font-size: 2em;
|
||||
}
|
||||
.calculator .function-keys .key-multiply {
|
||||
line-height: 50px;
|
||||
}
|
||||
.calculator .digit-keys .calculator-key {
|
||||
font-size: 2.25em;
|
||||
}
|
||||
.calculator .digit-keys .key-0 {
|
||||
width: 160px;
|
||||
text-align: left;
|
||||
padding-left: 32px;
|
||||
}
|
||||
.calculator .digit-keys .key-dot {
|
||||
padding-top: 1em;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
.calculator .operator-keys .calculator-key {
|
||||
color: white;
|
||||
border-right: 0;
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
.calculator .function-keys {
|
||||
background: linear-gradient(to bottom, rgba(202, 202, 204, 1) 0%, rgba(196, 194, 204, 1) 100%);
|
||||
}
|
||||
.calculator .operator-keys {
|
||||
background: linear-gradient(to bottom, rgba(252, 156, 23, 1) 0%, rgba(247, 126, 27, 1) 100%);
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { useToken } from '@tachybase/client';
|
||||
|
||||
import { DatabaseOutlined } from '@ant-design/icons';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useTranslation } from '../locale';
|
||||
|
||||
export const DatasourceLink = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { token } = useToken();
|
||||
return (
|
||||
<Tooltip title={t('Data sources')}>
|
||||
<Button
|
||||
icon={<DatabaseOutlined style={{ color: token.colorTextHeaderMenu }} />}
|
||||
title={t('Data sources')}
|
||||
onClick={() => {
|
||||
navigate('/admin/settings/data-source-manager/main/collections');
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
@ -4,6 +4,7 @@ import { Plugin } from '@tachybase/client';
|
||||
import { BreadcumbTitle } from './component/BreadcumbTitle';
|
||||
import { CollectionManagerPage } from './component/CollectionsManager';
|
||||
import { DatabaseConnectionManagerPane } from './component/DatabaseConnectionManager';
|
||||
import { DatasourceLink } from './component/DatasourceLink';
|
||||
import { MainDataSourceManager } from './component/MainDataSourceManager';
|
||||
import { DataSourcePermissionManager } from './component/PermissionManager';
|
||||
import { DatabaseConnectionProvider } from './DatabaseConnectionProvider';
|
||||
@ -16,6 +17,7 @@ export class PluginDataSourceManagerClient extends Plugin {
|
||||
// 注册组件
|
||||
this.app.addComponents({
|
||||
DataSourcePermissionManager,
|
||||
DatasourceLink,
|
||||
});
|
||||
this.app.use(DatabaseConnectionProvider);
|
||||
this.app.pluginSettingsManager.add(NAMESPACE, {
|
||||
|
@ -1,7 +1,11 @@
|
||||
import { i18n } from '@tachybase/client';
|
||||
import { i18n, useTranslation as useT } from '@tachybase/client';
|
||||
|
||||
export const NAMESPACE = 'data-source-manager';
|
||||
|
||||
export function lang(key: string, options = {}) {
|
||||
return i18n.t(key, { ...options, ns: NAMESPACE });
|
||||
}
|
||||
|
||||
export const useTranslation = (options?) => {
|
||||
return useT([NAMESPACE, 'client'], options);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { Plugin } from '@tachybase/client';
|
||||
import { Registry } from '@tachybase/utils/client';
|
||||
|
||||
import { WorkflowLink } from './components/WorkflowLink';
|
||||
import { ExecutionPage } from './ExecutionPage';
|
||||
import { PluginActionTrigger } from './features/action-trigger';
|
||||
import { PluginAggregate } from './features/aggregate';
|
||||
@ -127,6 +128,7 @@ export class PluginWorkflow extends Plugin {
|
||||
|
||||
addComponents() {
|
||||
this.app.addComponents({
|
||||
WorkflowLink,
|
||||
WorkflowPage,
|
||||
ExecutionPage,
|
||||
});
|
||||
|
@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { useToken } from '@tachybase/client';
|
||||
|
||||
import { PartitionOutlined } from '@ant-design/icons';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useWorkflowTranslation } from '../locale';
|
||||
|
||||
export const WorkflowLink = () => {
|
||||
const { t } = useWorkflowTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { token } = useToken();
|
||||
return (
|
||||
<Tooltip title={t('Workflow')}>
|
||||
<Button
|
||||
icon={<PartitionOutlined style={{ color: token.colorTextHeaderMenu }} />}
|
||||
title={t('Workflow')}
|
||||
onClick={() => {
|
||||
navigate('/admin/settings/workflow');
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user