@ant-design/icons#SaveOutlined TypeScript Examples
The following examples show how to use
@ant-design/icons#SaveOutlined.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Cohort.tsx From posthog-foss with MIT License | 6 votes |
export function CohortFooter(props: { cohort: CohortType }): JSX.Element {
const logic = cohortLogic(props)
const { cohort } = useValues(logic)
const { saveCohort } = useActions(logic)
return (
<Row style={{ display: 'flex' }}>
<div style={{ flexGrow: 1, textAlign: 'right' }}>
<Button
disabled={!cohort.name}
type="primary"
htmlType="submit"
data-attr="save-cohort"
onClick={() => saveCohort()}
style={{ float: 'right' }}
icon={<SaveOutlined />}
>
Save cohort
</Button>
</div>
</Row>
)
}
Example #2
Source File: config-toolbar.ts From XFlow with MIT License | 6 votes |
registerIcon = () => {
IconStore.set('SaveOutlined', SaveOutlined)
IconStore.set('UndoOutlined', UndoOutlined)
IconStore.set('RedoOutlined', RedoOutlined)
IconStore.set('VerticalAlignTopOutlined', VerticalAlignTopOutlined)
IconStore.set('VerticalAlignBottomOutlined', VerticalAlignBottomOutlined)
IconStore.set('GatewayOutlined', GatewayOutlined)
IconStore.set('GroupOutlined', GroupOutlined)
IconStore.set('UngroupOutlined', UngroupOutlined)
IconStore.set('CopyOutlined', CopyOutlined)
IconStore.set('SnippetsOutlined', SnippetsOutlined)
}
Example #3
Source File: Children.tsx From ui-visualization with MIT License | 5 votes |
Children = (props: Props) => {
const childLayout = [
{ i: 'd', x: 6, y: 0, w: 3, h: 2, minW: 2, maxW: 4 },
{ i: 'e', x: 8, y: 0, w: 1, h: 2 }
]
const [edit, setEdit] = useState<boolean>(false);
const [layout, setLayout] = useState<any[]>(childLayout);
return (
<Fragment>
<GridLayout
className="layout"
layout={layout}
cols={12}
rowHeight={30}
isDraggable={edit}
isResizable={edit}
width={1200}>
{
layout.map(i => <div key={i.i} style={{ backgroundColor: '#69E2D4' }}>{i.i}</div>)
}
</GridLayout>
<div className={styles.layoutOption}>
{edit ?
<div style={{ float: 'right' }}>
<Tooltip title="新增" style={{ float: 'right' }}>
<Button
type="danger"
shape="circle"
size="large"
onClick={() => {
layout.push({ i: Math.random(), x: 8, y: 7, w: 4, h: 4 })
setLayout([...layout]);
}}
>
<PlusOutlined />
</Button>
</Tooltip>
<div style={{ float: 'right', marginLeft: 10 }}>
<Tooltip title="保存" >
<Button
type="primary"
shape="circle"
size="large"
onClick={() => {
setEdit(false)
}}
>
<SaveOutlined />
</Button>
</Tooltip>
</div>
</div> :
<div style={{ float: 'right', textAlign: 'center' }}>
<Tooltip title="编辑" >
<Button
type="danger"
shape="circle"
size="large"
onClick={() => {
setEdit(true)
props.edit()
}}
>
<EditOutlined />
</Button>
</Tooltip>
</div>
}
</div>
</Fragment >
)
}
Example #4
Source File: index.tsx From jetlinks-ui-antd with MIT License | 5 votes |
OptionGroup: React.FC<Props> = props => {
const { edit } = props;
return (
<div className={styles.optionGroup}>
{edit ?
<div style={{ float: 'right' }}>
<Tooltip title="新增" style={{ float: 'right' }}>
<Button
type="danger"
shape="circle"
size="large"
onClick={() => {
}}
>
<PlusOutlined />
</Button>
</Tooltip>
<div style={{ float: 'right', marginLeft: 10 }}>
<Tooltip title="保存">
<Button
type="primary"
shape="circle"
size="large"
onClick={() => {
}}
>
<SaveOutlined />
</Button>
</Tooltip>
</div>
</div> :
<div style={{ textAlign: 'center' }}>
<Tooltip title="编辑">
<Button
type="danger"
shape="circle"
size="large"
onClick={() => {
}}
>
<EditOutlined />
</Button>
</Tooltip>
</div>
}
</div>
)
}
Example #5
Source File: EditorHeader.tsx From datart with Apache License 2.0 | 5 votes |
EditorHeader: FC = memo(({ children }) => {
const dispatch = useDispatch();
const history = useHistory();
const t = useI18NPrefix(`viz.action`);
const { updateBoard } = useContext(BoardActionContext);
const { onEditClearActiveWidgets } = useContext(WidgetActionContext);
const { name, status } = useContext(BoardContext);
const { saving } = useContext(BoardInfoContext);
const title = useStatusTitle(name, status);
const onCloseBoardEditor = () => {
const pathName = history.location.pathname;
const prePath = pathName.split('/boardEditor')[0];
history.push(`${prePath}`);
dispatch(clearEditBoardState());
};
const onUpdateBoard = () => {
onEditClearActiveWidgets();
setImmediate(() => {
updateBoard?.(onCloseBoardEditor);
});
};
return (
<Wrapper onClick={onEditClearActiveWidgets}>
<h1 className={classnames({ disabled: status < 2 })}>
<LeftOutlined onClick={onCloseBoardEditor} />
{title}
</h1>
<Space>
{children}
<>
<Button
key="cancel"
icon={<CloseOutlined />}
onClick={onCloseBoardEditor}
>
{t('common.cancel')}
</Button>
<Button
key="update"
type="primary"
loading={saving}
icon={<SaveOutlined />}
onClick={onUpdateBoard}
>
{t('common.save')}
</Button>
</>
</Space>
</Wrapper>
);
})
Example #6
Source File: NewDashboard.tsx From posthog-foss with MIT License | 5 votes |
export function NewDashboard(): JSX.Element {
const [form] = Form.useForm()
const { addDashboard } = useActions(dashboardsModel)
return (
<Form
layout="vertical"
form={form}
onFinish={(values) => {
addDashboard(values)
}}
>
<Form.Item
name="name"
label="Dashboard name"
rules={[{ required: true, message: 'Please give your dashboard a name.' }]}
>
<Input
autoFocus={true}
onChange={(e) => form.setFieldsValue({ key: slugify(e.target.value) })}
data-attr="dashboard-name-input"
className="ph-ignore-input"
/>
</Form.Item>
<Form.Item name="useTemplate" label="Start from">
<Select data-attr="copy-from-template" style={{ width: '100%' }}>
<Select.Option data-attr="dashboard-select-empty" value="">
Empty Dashboard
</Select.Option>
<Select.Option data-attr="dashboard-select-default-app" value="DEFAULT_APP">
Default Dashboard - Web App
</Select.Option>
</Select>
</Form.Item>
<br />
<Form.Item>
<Button icon={<SaveOutlined />} htmlType="submit" type="primary" data-attr="dashboard-submit">
Create
</Button>
</Form.Item>
</Form>
)
}
Example #7
Source File: save.tsx From imove with MIT License | 5 votes |
Save: React.FC<IProps> = makeBtnWidget({
tooltip: '保存',
handler: shortcuts.save.handler,
getIcon() {
return <SaveOutlined />;
},
})
Example #8
Source File: FormTableBlocks.tsx From condo with MIT License | 5 votes |
function RenderActionsColumn (text, item, index) {
const intl = useIntl()
const AreYouSureMessage = intl.formatMessage({ id: 'AreYouSure' })
const DeleteMessage = intl.formatMessage({ id: 'Delete' })
const EditMessage = intl.formatMessage({ id: 'Edit' })
const { user } = useAuth()
const { isUnsavedNew } = item
const { action, remove, form, setEditing, setLoading, editing, loading } = _useTableRowForm()
function validateFields () {
setLoading(true)
return form.validateFields()
.then((values) => action('CreateOrUpdate', { values, item, form }))
.then(() => (isUnsavedNew) ? remove({ id: item.id }) : null)
.then(() => (isUnsavedNew) ? null : setEditing(false))
.finally(() => setLoading(false))
}
function deleteRow () {
setLoading(false)
setEditing(false)
remove({ id: item.id })
}
return <Space>
{(isUnsavedNew || editing) ?
<Button size="small" type={'primary'} onClick={validateFields} loading={loading}>
<SaveOutlined/>
</Button>
: null}
{(isUnsavedNew) ?
<Button size="small" type={'primary'} onClick={deleteRow}>
<DeleteOutlined/>
</Button>
:
<ExtraDropdownActionsMenu actions={[
(item.user && item.user.id === user.id) ? null : {
confirm: {
title: AreYouSureMessage,
icon: <QuestionCircleOutlined style={{ color: 'red' }}/>,
},
label: DeleteMessage,
action: () => action('Delete', { values: { id: item.id }, item, form }),
},
{
label: EditMessage,
action: () => {
setEditing(true)
},
},
]}/>
}
</Space>
}
Example #9
Source File: MaterialEdit.tsx From mayoor with MIT License | 4 votes |
MaterialEdit: React.FC = () => {
const { t } = useTranslation();
const [currentlyLoading, setCurrentlyLoading] = useState<string | null>(null);
const { data } = useQuery<GetAllMaterials>(GET_ALL_MATERIALS);
const [updateMaterial] = useMutation<UpdateMaterial, UpdateMaterialVariables>(UPDATE_MATERIAL, {
onCompleted: () => {
message.success(t('Material updated'));
},
});
const [deleteMaterial] = useMutation<DeleteMaterial, DeleteMaterialVariables>(DELETE_MATERIAL, {
onCompleted: () => {
message.success(t('Material deleted'));
},
update: (cache, { data }) => {
const cached = cache.readQuery<GetAllMaterials>({ query: GET_ALL_MATERIALS });
if (cached === null) {
return;
}
const { getAllMaterials } = cached;
cache.writeQuery<GetAllMaterials>({
query: GET_ALL_MATERIALS,
data: {
getAllMaterials: getAllMaterials.filter(
({ id }) => id !== data?.deleteMaterial.id,
),
},
});
},
});
return (
<>
<PageTitle>{t('Material')}</PageTitle>
<StyledForm>
<MaterialEditWrapper>
<>
<Row gutter={24}>
<Col sm={14}>
<StyledLabel>{t('Material name')}</StyledLabel>
</Col>
<Col sm={6}>
<StyledLabel>{t('Price')}</StyledLabel>
</Col>
<Col sm={4}></Col>
</Row>
{data?.getAllMaterials.map((material) => (
<Formik<GetAllMaterials_getAllMaterials>
key={material.id}
initialValues={material}
onSubmit={async (values) => {
setCurrentlyLoading(material.id);
await updateMaterial({
variables: {
id: material.id,
name: values.name,
price: Number(values.price),
},
});
setCurrentlyLoading(null);
}}
validate={getFormikValidate(t)}
>
{({ handleSubmit }) => (
<Row gutter={18}>
<Col sm={14}>
<FormInput
label={t('Material name')}
name="name"
></FormInput>
</Col>
<Col sm={5}>
<FormInput label={t('Price')} name="price"></FormInput>
</Col>
<Col sm={3}>
<Button
onClick={() => handleSubmit()}
loading={currentlyLoading === material.id}
icon={<SaveOutlined />}
style={{ width: '100%' }}
>
{t('Save')}
</Button>
</Col>
<Col sm={1} style={{ textAlign: 'right' }}>
<Popconfirm
placement="topRight"
onConfirm={async () =>
await deleteMaterial({
variables: { id: material.id },
})
}
title={t(
'Do you really want to remove this material?',
)}
>
<Button
shape="circle"
icon={<DeleteOutlined />}
></Button>
</Popconfirm>
</Col>
</Row>
)}
</Formik>
))}
<MaterialCreate />
</>
</MaterialEditWrapper>
</StyledForm>
</>
);
}
Example #10
Source File: index.tsx From ui-visualization with MIT License | 4 votes |
Visualization = () => {
const data = [
{ i: 'a', x: 0, y: 0, w: 10, h: 8, },
{ i: 'b', x: 1, y: 0, w: 3, h: 2, minW: 2, maxW: 4 },
{ i: 'c', x: 4, y: 0, w: 1, h: 2 },
];
const [layout, setLayout] = useState(data);
const [flag, setFlag] = useState(false);
const [edit, setEdit] = useState<boolean>(false);
const changeStatus = (key: string) => {
setFlag(!flag);
}
return (
<>
<GridLayout
className="layout"
isDraggable={edit}
isResizable={edit}
layout={layout}
cols={12} rowHeight={30} width={1200}>
<div key="a" style={{ backgroundColor: '#1879FF' }}
>
<PlusCircleOutlined style={{ margin: '5px' }} />
<EditOutlined onClick={() => {
changeStatus("a")
}} />
<Children edit={() => changeStatus("a")} />
</div>
<div key="b" style={{ backgroundColor: '#1879FF' }}>b</div>
<div key="c" style={{ backgroundColor: '#1879FF' }}>c</div>
</GridLayout>
<div className={styles.optionGroup}>
{edit ?
<div style={{ float: 'right' }}>
<Tooltip title="新增" style={{ float: 'right' }}>
<Button
type="danger"
shape="circle"
size="large"
onClick={() => {
}}
>
<PlusOutlined />
</Button>
</Tooltip>
<div style={{ float: 'right', marginLeft: 10 }}>
<Tooltip title="保存" >
<Button
type="primary"
shape="circle"
size="large"
onClick={() => {
setEdit(false)
}}
>
<SaveOutlined />
</Button>
</Tooltip>
</div>
</div> :
<div style={{ textAlign: 'center' }}>
<Tooltip title="编辑" >
<Button
type="danger"
shape="circle"
size="large"
onClick={() => setEdit(true)}
>
<EditOutlined />
</Button>
</Tooltip>
</div>
}
</div>
</>
)
}
Example #11
Source File: EditInfo.tsx From foodie with MIT License | 4 votes |
EditInfo: React.FC<IProps> = ({ isOwnProfile, profile }) => {
const [isUpdating, setIsUpdating] = useState(false);
const [field, setField] = useState({
firstname: profile?.firstname || '',
lastname: profile?.lastname || '',
gender: profile?.info?.gender || '',
bio: profile?.info?.bio || '',
birthday: profile?.info?.birthday || ''
});
const [bioLength, setBioLength] = useState(200 - profile?.info?.bio?.length || 0);
const history = useHistory();
const dispatch = useDispatch();
const didMount = useDidMount();
useDocumentTitle(`Edit Info - ${profile.username} | Foodie`);
useEffect(() => {
setField({
firstname: profile.firstname,
lastname: profile.lastname,
gender: profile.info.gender,
bio: profile.info?.bio || '',
birthday: profile.info.birthday
});
}, [profile]);
const handleUpdateUser = async () => {
try {
setIsUpdating(true);
const user = await updateUser(profile.username, field);
dispatch(updateProfileInfo(user));
if (didMount) {
setIsUpdating(false);
history.push(`/user/${profile.username}/info`);
toast.dark('Profile updated successfully.')
}
} catch (e) {
if (didMount) {
setIsUpdating(false);
}
toast.dismiss();
toast.error(e.error?.message || 'Unable to process your request.');
}
};
const handleFirstnameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setField({ ...field, firstname: e.target.value });
}
const handleLastnameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setField({ ...field, lastname: e.target.value });
}
const handleGenderChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setField({ ...field, gender: e.target.value });
}
const handleBirthdayChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setField({ ...field, birthday: e.target.value });
}
const handleBioChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const val = e.target.value;
setField({ ...field, bio: val });
setBioLength(200 - val.length);
}
const handleTextOnlyInput = (e: React.KeyboardEvent<HTMLInputElement>) => {
const key = e.nativeEvent.key;
const regex = /[a-zA-Z\s]/ig;
if (!regex.test(key)) {
e.preventDefault();
}
}
const handleBack = () => {
history.push(`/user/${profile.username}/info`)
};
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
handleUpdateUser();
}
return (!isOwnProfile && profile.username) ? <Redirect to={`/user/${profile.username}`} /> : (
<div className="p-4 pb-8 bg-white dark:bg-indigo-1000 rounded-md min-h-10rem shadow-lg">
<h3 className="text-gray-500 dark:text-white">Edit Info</h3>
<form className="mt-8 space-y-4 divide-y divide-gray-100 dark:divide-gray-800" onSubmit={handleSubmit}>
{/* ---- FIRST NAME ------- */}
<div className="flex flex-col py-2">
<label htmlFor="firstname" className="ml-4 text-gray-400 mb-2">First Name</label>
<input
className="dark:bg-indigo-1100 dark:text-white dark:border-gray-800"
readOnly={isUpdating}
id="firstname"
type="text"
maxLength={50}
onChange={handleFirstnameChange}
onKeyDown={handleTextOnlyInput}
value={field.firstname}
/>
</div>
{/* ---- LAST NAME ------- */}
<div className="flex flex-col py-2">
<label htmlFor="lastname" className="ml-4 text-gray-400 mb-2">Last Name</label>
<input
className="dark:bg-indigo-1100 dark:text-white dark:border-gray-800"
readOnly={isUpdating}
id="lastname"
type="text"
maxLength={50}
onChange={handleLastnameChange}
onKeyDown={handleTextOnlyInput}
value={field.lastname}
/>
</div>
{/* ---- GENDER && BIRTHDAY ------- */}
<div className="grid laptop:grid-cols-2">
<div className="flex flex-col py-2">
<label htmlFor="gender" className="ml-4 text-gray-400 mb-2">Gender</label>
<select
className="dark:bg-indigo-1100 dark:text-white dark:border-gray-800"
id="gender"
onChange={handleGenderChange}
disabled={isUpdating}
value={field.gender === null ? '' : field.gender}
>
<option disabled value="">Select Gender</option>
<option value="male">Male</option>
<option value="female">Female</option>
<option value="unspecified">Better Not Say</option>
</select>
</div>
<div className="flex flex-col py-2">
<label htmlFor="birthday" className="ml-4 text-gray-400 mb-2">Birthday (mm/dd/yyyy)</label>
<input
className="dark:bg-indigo-1100 dark:text-white dark:border-gray-800"
readOnly={isUpdating}
type="date"
value={field.birthday ? new Date(field.birthday).toISOString().split('T')[0] : ''}
onChange={handleBirthdayChange}
/>
</div>
</div>
{/* ---- BIO ------- */}
<div className="flex flex-col py-2">
<label htmlFor="bio" className="ml-4 text-gray-400 mb-2">Bio</label>
<textarea
placeholder="Tell something about yourself"
id="bio"
cols={10}
rows={4}
className="dark:bg-indigo-1100 dark:text-white dark:border-gray-800"
readOnly={isUpdating}
onChange={handleBioChange}
maxLength={200}
value={field.bio}
/>
<span className="text-xs text-gray-400 block text-right">
{bioLength} {bioLength <= 1 ? 'character' : 'characters'} left
</span>
</div>
{/* ---- SUBMIT BUTTON ----- */}
<div className="flex justify-between pt-8">
<button
disabled={isUpdating}
type="button"
onClick={handleBack}
className="button--muted !rounded-full dark:bg-indigo-1100 dark:text-white dark:hover:bg-indigo-1100"
>
<ArrowLeftOutlined className="text-xl mr-4" />
Back to Info
</button>
<button
className="flex items-center"
type="submit"
disabled={isUpdating}
>
<SaveOutlined className="text-xl mr-4" />
{isUpdating ? 'Updating...' : 'Update'}
</button>
</div>
</form>
</div>
);
}
Example #12
Source File: DetailOrderProduction.tsx From mayoor with MIT License | 4 votes |
DetailOrderProduction: React.FC<Props> = ({
productionLogType,
productionButtonText,
}) => {
const routeParams = useParams<{ id: string }>();
const { t } = useTranslation();
const [noteValue, setNoteValue] = useState<string | undefined | null>();
const { data } = useQuery<GetOrder, GetOrderVariables>(GET_ORDER, {
variables: { number: Number(routeParams.id) },
onCompleted: (data) => {
setNoteValue(data.getOrderByNumber?.note);
},
});
const [updateOrderNote] = useMutation<UpdateOrderNote, UpdateOrderNoteVariables>(
UPDATE_ORDER_NOTE,
);
const [addProductionLog] = useMutation<AddProductionLog, AddProductionLogVariables>(
ADD_PRODUCTION_LOG_MUTATION,
);
const updateNoteHandler = async () => {
const id = data?.getOrderByNumber?.id;
if (!id) {
return;
}
try {
await updateOrderNote({
variables: {
id,
note: noteValue,
},
});
message.success(t('note_updated'));
} catch (err) {
console.error(err);
message.error(t('note_update_fail'));
}
};
const productionButtonHandler = (pieces: number, orderItemId: string) => {
addProductionLog({ variables: { orderItemId, pieces, action: productionLogType } });
};
if (!data || !data.getOrderByNumber) {
return (
<PageTitle>
<Skeleton active />
</PageTitle>
);
}
return (
<>
<PageTitle>
{t('Order #{{number}}', {
number: data?.getOrderByNumber?.number,
})}
</PageTitle>
<DetailDescription
createdAt={data?.getOrderByNumber?.createdAt}
createdByName={data?.getOrderByNumber?.createdBy.name}
updatedAt={data?.getOrderByNumber?.updatedAt}
></DetailDescription>
<OrderWrapper>
<Row gutter={12}>
<Col sm={4}>
<StyledLabel>{t('Material')}</StyledLabel>
</Col>
<Col sm={4}>
<StyledLabel>{t('Name')}</StyledLabel>
</Col>
<Col sm={2}>
<StyledLabel>{t('Width')}</StyledLabel>
</Col>
<Col sm={2}>
<StyledLabel>{t('Height')}</StyledLabel>
</Col>
<Col sm={2}>
<StyledLabel>{t('Pieces')}</StyledLabel>
</Col>
<Col sm={2}>
<StyledLabel>{t('Printed pcs')}</StyledLabel>
</Col>
<Col sm={2}>
<StyledLabel>{t('Production done pcs')}</StyledLabel>
</Col>
<Col sm={5}></Col>
</Row>
{data.getOrderByNumber.items.map((item) => (
<ProductionRow
key={item.id}
item={item}
onProductionClick={productionButtonHandler}
productionButtonText={productionButtonText}
/>
))}
<Row gutter={32}>
<Col sm={12}>
<StyledFormItem>
<StyledLabel>{t('Note')}</StyledLabel>
<Input.TextArea
rows={4}
name="note"
placeholder={t('note_placeholder')}
onChange={(e) => setNoteValue(e.target.value)}
value={noteValue || ''}
/>
<Button icon={<SaveOutlined />} onClick={updateNoteHandler}>
{t('Save note')}
</Button>
</StyledFormItem>
</Col>
<Col sm={12}>
<Row justify="end" style={{ marginTop: 20 }}>
<UpdateStatusButton
orderId={data.getOrderByNumber.id}
productionLogType={productionLogType}
orderStatus={data.getOrderByNumber.status}
/>
</Row>
</Col>
</Row>
</OrderWrapper>
</>
);
}
Example #13
Source File: YakExecutor.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
YakExecutor: React.FC<YakExecutorProp> = (props) => {
const [codePath, setCodePath] = useState<string>("")
const [loading, setLoading] = useState<boolean>(false)
const [fileList, setFileList] = useState<tabCodeProps[]>([])
const [tabList, setTabList] = useState<tabCodeProps[]>([])
const [activeTab, setActiveTab] = useState<string>("")
const [unTitleCount, setUnTitleCount] = useState(1)
const [hintShow, setHintShow] = useState<boolean>(false)
const [hintFile, setHintFile] = useState<string>("")
const [hintIndex, setHintIndex] = useState<number>(0)
const [renameHint, setRenameHint] = useState<boolean>(false)
const [renameIndex, setRenameIndex] = useState<number>(-1)
const [renameFlag, setRenameFlag] = useState<boolean>(false)
const [renameCache, setRenameCache] = useState<string>("")
const [fullScreen, setFullScreen] = useState<boolean>(false)
const [errors, setErrors] = useState<string[]>([])
const [executing, setExecuting] = useState(false)
const [outputEncoding, setOutputEncoding] = useState<"utf8" | "latin1">("utf8")
const xtermAsideRef = useRef(null)
const xtermRef = useRef(null)
const timer = useRef<any>(null)
const [extraParams, setExtraParams] = useState("")
// trigger for updating
const [triggerForUpdatingHistory, setTriggerForUpdatingHistory] = useState<any>(0)
const addFileTab = useMemoizedFn((res: any) => {
const {name, code} = res
const tab: tabCodeProps = {
tab: `${name}.yak`,
code: code,
suffix: "yak",
isFile: false
}
setActiveTab(`${tabList.length}`)
setTabList(tabList.concat([tab]))
setUnTitleCount(unTitleCount + 1)
})
useEffect(() => {
ipcRenderer.on("fetch-send-to-yak-running", (e, res: any) => addFileTab(res))
return () => ipcRenderer.removeAllListeners("fetch-send-to-yak-running")
}, [])
// 自动保存
const autoSave = useMemoizedFn(() => {
for (let tabInfo of tabList) {
if (tabInfo.isFile) {
ipcRenderer.invoke("write-file", {
route: tabInfo.route,
data: tabInfo.code
})
}
}
})
// 保存近期文件内的15个
const saveFiliList = useMemoizedFn(() => {
let files = cloneDeep(fileList).reverse()
files.splice(14)
files = files.reverse()
ipcRenderer.invoke("set-value", RecentFileList, files)
})
// 获取和保存近期打开文件信息,同时展示打开默认内容
useEffect(() => {
let time: any = null
let timer: any = null
setLoading(true)
ipcRenderer
.invoke("get-value", RecentFileList)
.then((value: any) => {
if ((value || []).length !== 0) {
setFileList(value)
} else {
const tab: tabCodeProps = {
tab: `Untitle-${unTitleCount}.yak`,
code: "# input your yak code\nprintln(`Hello Yak World!`)",
suffix: "yak",
isFile: false
}
setActiveTab(`${tabList.length}`)
setTabList([tab])
setUnTitleCount(unTitleCount + 1)
}
})
.catch(() => {})
.finally(() => {
setTimeout(() => setLoading(false), 300)
time = setInterval(() => {
autoSave()
}, 2000)
timer = setInterval(() => {
saveFiliList()
}, 5000)
})
return () => {
saveFiliList()
if (time) clearInterval(time)
if (timer) clearInterval(timer)
}
}, [])
// 全局监听重命名事件是否被打断
useEffect(() => {
document.onmousedown = (e) => {
// @ts-ignore
if (e.path[0].id !== "rename-input" && renameFlag) {
renameCode(renameIndex)
setRenameFlag(false)
}
}
}, [renameFlag])
// 打开文件
const addFile = useMemoizedFn((file: any) => {
const isExists = fileList.filter((item) => item.tab === file.name && item.route === file.path).length === 1
if (isExists) {
for (let index in tabList) {
const item = tabList[index]
if (item.tab === file.name && item.route === file.path) {
setActiveTab(`${index}`)
return false
}
}
}
ipcRenderer
.invoke("fetch-file-content", file.path)
.then((res) => {
const tab: tabCodeProps = {
tab: file.name,
code: res,
suffix: file.name.split(".").pop() === "yak" ? "yak" : "http",
isFile: true,
route: file.path,
extraParams: file.extraParams
}
setActiveTab(`${tabList.length}`)
if (!isExists) setFileList(fileList.concat([tab]))
setTabList(tabList.concat([tab]))
})
.catch(() => {
failed("无法获取该文件内容,请检查后后重试!")
const files = cloneDeep(fileList)
for (let i in files) if (files[i].route === file.path) files.splice(i, 1)
setFileList(files)
})
return false
})
// 新建文件
const newFile = useMemoizedFn(() => {
const tab: tabCodeProps = {
tab: `Untitle-${unTitleCount}.yak`,
code: "# input your yak code\nprintln(`Hello Yak World!`)",
suffix: "yak",
isFile: false
}
setActiveTab(`${tabList.length}`)
setTabList(tabList.concat([tab]))
setUnTitleCount(unTitleCount + 1)
})
//修改文件
const modifyCode = useMemoizedFn((value: string, index: number) => {
const tabs = cloneDeep(tabList)
tabs[index].code = value
setTabList(tabs)
})
// 保存文件
const saveCode = useMemoizedFn((info: tabCodeProps, index: number) => {
if (info.isFile) {
ipcRenderer.invoke("write-file", {
route: info.route,
data: info.code
})
} else {
ipcRenderer.invoke("show-save-dialog", `${codePath}${codePath ? '/' : ''}${info.tab}`).then((res) => {
if (res.canceled) return
const path = res.filePath
const name = res.name
ipcRenderer
.invoke("write-file", {
route: res.filePath,
data: info.code
})
.then(() => {
const suffix = name.split(".").pop()
var tabs = cloneDeep(tabList)
var active = null
tabs = tabs.filter((item) => item.route !== path)
tabs = tabs.map((item, index) => {
if (!item.route && item.tab === info.tab) {
active = index
item.tab = name
item.isFile = true
item.suffix = suffix === "yak" ? suffix : "http"
item.route = path
return item
}
return item
})
if (active !== null) setActiveTab(`${active}`)
setTabList(tabs)
const file: tabCodeProps = {
tab: name,
code: info.code,
isFile: true,
suffix: suffix === "yak" ? suffix : "http",
route: res.filePath,
extraParams: info.extraParams
}
for (let item of fileList) {
if (item.route === file.route) {
return
}
}
setFileList(fileList.concat([file]))
})
})
}
})
//关闭文件
const closeCode = useMemoizedFn((index, isFileList: boolean) => {
const tabInfo = isFileList ? fileList[+index] : tabList[+index]
if (isFileList) {
for (let i in tabList) {
if (tabList[i].tab === tabInfo.tab && tabList[i].route === tabInfo.route) {
const tabs = cloneDeep(tabList)
tabs.splice(i, 1)
setTabList(tabs)
setActiveTab(tabs.length >= 1 ? `0` : "")
}
}
const files = cloneDeep(fileList)
files.splice(+index, 1)
setFileList(files)
} else {
setActiveTab(index)
if (!tabInfo.isFile) {
setHintFile(tabInfo.tab)
setHintIndex(index)
setHintShow(true)
} else {
const tabs = cloneDeep(tabList)
tabs.splice(+index, 1)
setTabList(tabs)
setActiveTab(tabs.length >= 1 ? `0` : "")
}
}
})
// 关闭虚拟文件不保存
const ownCloseCode = useMemoizedFn(() => {
const tabs = cloneDeep(tabList)
tabs.splice(hintIndex, 1)
setTabList(tabs)
setHintShow(false)
setActiveTab(tabs.length >= 1 ? `0` : "")
})
// 删除文件
const delCode = useMemoizedFn((index) => {
const fileInfo = fileList[index]
ipcRenderer
.invoke("delelte-code-file", fileInfo.route)
.then(() => {
for (let i in tabList) {
if (tabList[i].tab === fileInfo.tab && tabList[i].route === fileInfo.route) {
const tabs = cloneDeep(tabList)
tabs.splice(i, 1)
setTabList(tabs)
setActiveTab(tabs.length >= 1 ? `0` : "")
}
}
const arr = cloneDeep(fileList)
arr.splice(index === undefined ? hintIndex : index, 1)
setFileList(arr)
})
.catch(() => {
failed("文件删除失败!")
})
})
//重命名操作
const renameCode = useMemoizedFn((index: number) => {
const tabInfo = fileList[index]
if (renameCache === tabInfo.tab) return
if (!renameCache) return
if (!tabInfo.route) return
const flagStr = tabInfo.route?.indexOf("/") > -1 ? "/" : "\\"
const routes = tabInfo.route?.split(flagStr)
routes?.pop()
ipcRenderer
.invoke("is-exists-file", routes?.concat([renameCache]).join(flagStr))
.then(() => {
const newRoute = routes?.concat([renameCache]).join(flagStr)
if (!tabInfo.route || !newRoute) return
renameFile(index, renameCache, tabInfo.route, newRoute)
})
.catch(() => {
setRenameHint(true)
})
})
// 重命名文件
const renameFile = useMemoizedFn(
(index: number, rename: string, oldRoute: string, newRoute: string, callback?: () => void) => {
ipcRenderer.invoke("rename-file", {old: oldRoute, new: newRoute}).then(() => {
const suffix = rename.split(".").pop()
var files = cloneDeep(fileList)
var tabs = cloneDeep(tabList)
var active = null
files = files.filter((item) => item.route !== newRoute)
tabs = tabs.filter((item) => item.route !== newRoute)
files = files.map((item) => {
if (item.route === oldRoute) {
item.tab = rename
item.suffix = suffix === "yak" ? suffix : "http"
item.route = newRoute
return item
}
return item
})
tabs = tabs.map((item, index) => {
if (item.route === oldRoute) {
active = index
item.tab = rename
item.suffix = suffix === "yak" ? suffix : "http"
item.route = newRoute
return item
}
return item
})
if (active !== null) setActiveTab(`${active}`)
setFileList(files)
setTabList(tabs)
if (callback) callback()
})
}
)
const fileFunction = (kind: string, index: string, isFileList: boolean) => {
const tabCodeInfo = isFileList ? fileList[index] : tabList[index]
switch (kind) {
case "own":
closeCode(index, isFileList)
return
case "other":
const tabInfo: tabCodeProps = cloneDeep(tabList[index])
for (let i in tabList) {
if (i !== index && !tabList[i].isFile) {
const arr: tabCodeProps[] =
+i > +index
? [tabInfo].concat(tabList.splice(+i, tabList.length))
: tabList.splice(+i, tabList.length)
const num = +i > +index ? 1 : 0
setActiveTab(`${num}`)
setTabList(arr)
setHintFile(arr[num].tab)
setHintIndex(num)
setHintShow(true)
return
}
}
const code = cloneDeep(tabList[index])
setTabList([code])
setActiveTab(`0`)
return
case "all":
for (let i in tabList) {
if (!tabList[i].isFile) {
const arr = tabList.splice(+i, tabList.length)
setActiveTab("0")
setTabList(arr)
setHintFile(arr[0].tab)
setHintIndex(0)
setHintShow(true)
return
}
}
setActiveTab("")
setTabList([])
return
case "remove":
closeCode(index, isFileList)
return
case "delete":
delCode(index)
return
case "rename":
setRenameIndex(+index)
setRenameFlag(true)
setRenameCache(tabCodeInfo.tab)
return
}
}
const openFileLayout = (file: any) => {
addFile(file)
}
useEffect(() => {
ipcRenderer.invoke("fetch-code-path")
.then((path: string) => {
ipcRenderer.invoke("is-exists-file", path)
.then(() => {
setCodePath("")
})
.catch(() => {
setCodePath(path)
})
})
}, [])
useEffect(() => {
if (tabList.length === 0) setFullScreen(false)
}, [tabList])
useEffect(() => {
if (!xtermRef) {
return
}
// let buffer = "";
ipcRenderer.on("client-yak-error", async (e: any, data) => {
failed(`FoundError: ${JSON.stringify(data)}`)
if (typeof data === "object") {
setErrors([...errors, `${JSON.stringify(data)}`])
} else if (typeof data === "string") {
setErrors([...errors, data])
} else {
setErrors([...errors, `${data}`])
}
})
ipcRenderer.on("client-yak-end", () => {
info("Yak 代码执行完毕")
setTriggerForUpdatingHistory(getRandomInt(100000))
setTimeout(() => {
setExecuting(false)
}, 300)
})
ipcRenderer.on("client-yak-data", async (e: any, data: ExecResult) => {
if (data.IsMessage) {
// alert(Buffer.from(data.Message).toString("utf8"))
}
if (data?.Raw) {
writeExecResultXTerm(xtermRef, data, outputEncoding)
// writeXTerm(xtermRef, Buffer.from(data.Raw).toString(outputEncoding).replaceAll("\n", "\r\n"))
// monacoEditorWrite(currentOutputEditor, )
}
})
return () => {
ipcRenderer.removeAllListeners("client-yak-data")
ipcRenderer.removeAllListeners("client-yak-end")
ipcRenderer.removeAllListeners("client-yak-error")
}
}, [xtermRef])
const bars = (props: any, TabBarDefault: any) => {
return (
<TabBarDefault
{...props}
children={(barNode: React.ReactElement) => {
return (
<Dropdown
overlay={CustomMenu(barNode.key, false, tabMenu, fileFunction)}
trigger={["contextMenu"]}
>
{barNode}
</Dropdown>
)
}}
/>
)
}
return (
<AutoCard
className={"yak-executor-body"}
// title={"Yak Runner"}
headStyle={{minHeight: 0}}
bodyStyle={{padding: 0, overflow: "hidden"}}
>
<div
style={{width: "100%", height: "100%", display: "flex", backgroundColor: "#E8E9E8"}}
tabIndex={0}
onKeyDown={(e) => {
if (e.keyCode === 78 && (e.ctrlKey || e.metaKey)) {
newFile()
}
if (e.keyCode === 83 && (e.ctrlKey || e.metaKey) && activeTab) {
saveCode(tabList[+activeTab], +activeTab)
}
}}
>
<div style={{width: `${fullScreen ? 0 : 15}%`}}>
<AutoSpin spinning={loading}>
<ExecutorFileList
lists={fileList}
activeFile={tabList[+activeTab]?.route || ""}
renameFlag={renameFlag}
renameIndex={renameIndex}
renameCache={renameCache}
setRenameCache={setRenameCache}
addFile={addFile}
newFile={newFile}
openFile={openFileLayout}
fileFunction={fileFunction}
/>
</AutoSpin>
</div>
<div style={{width: `${fullScreen ? 100 : 85}%`}} className='executor-right-body'>
{tabList.length > 0 && (
<ResizeBox
isVer
firstNode={
<Tabs
className={"right-editor"}
style={{height: "100%"}}
type='editable-card'
activeKey={activeTab}
hideAdd={true}
onChange={(activeTab) => setActiveTab(activeTab)}
onEdit={(key, event: "add" | "remove") => {
switch (event) {
case "remove":
closeCode(key, false)
return
case "add":
return
}
}}
renderTabBar={(props, TabBarDefault) => {
return bars(props, TabBarDefault)
}}
tabBarExtraContent={
tabList.length && (
<Space style={{marginRight: 5}} size={0}>
<Button
style={{height: 25}}
type={"link"}
size={"small"}
disabled={
tabList[+activeTab] && tabList[+activeTab].suffix !== "yak"
}
onClick={(e) => {
let m = showDrawer({
width: "60%",
placement: "left",
title: "选择你的 Yak 模块执行特定功能",
content: (
<>
<YakScriptManagerPage
type={"yak"}
onLoadYakScript={(s) => {
const tab: tabCodeProps = {
tab: `Untitle-${unTitleCount}.yak`,
code: s.Content,
suffix: "yak",
isFile: false
}
info(`加载 Yak 模块:${s.ScriptName}`)
xtermClear(xtermRef)
setActiveTab(`${tabList.length}`)
setTabList(tabList.concat([tab]))
setUnTitleCount(unTitleCount + 1)
m.destroy()
}}
/>
</>
)
})
}}
>
Yak脚本模板
</Button>
<Button
icon={
fullScreen ? (
<FullscreenExitOutlined style={{fontSize: 15}} />
) : (
<FullscreenOutlined style={{fontSize: 15}} />
)
}
type={"link"}
size={"small"}
style={{width: 30, height: 25}}
onClick={() => setFullScreen(!fullScreen)}
/>
<Popover
trigger={["click"]}
title={"设置命令行额外参数"}
placement="bottomRight"
content={
<Space style={{width: 400}}>
<div>yak {tabList[+activeTab]?.tab || "[file]"}</div>
<Divider type={"vertical"} />
<Paragraph
style={{width: 200, marginBottom: 0}}
editable={{
icon: <Space>
<EditOutlined />
<SaveOutlined onClick={(e) => {
e.stopPropagation()
tabList[+activeTab].extraParams = extraParams
setTabList(tabList)
if(tabList[+activeTab].isFile){
const files = fileList.map(item => {
if(item.route === tabList[+activeTab].route){
item.extraParams = extraParams
return item
}
return item
})
setFileList(files)
}
success("保存成功")
}}
/></Space>,
tooltip: '编辑/保存为该文件默认参数',
onChange: setExtraParams
}}
>
{extraParams}
</Paragraph>
</Space>
}
>
<Button type={"link"} icon={<EllipsisOutlined />} onClick={() => {
setExtraParams(tabList[+activeTab]?.extraParams || "")
}} />
</Popover>
{executing ? (
<Button
icon={<PoweroffOutlined style={{fontSize: 15}} />}
type={"link"}
danger={true}
size={"small"}
style={{width: 30, height: 25}}
onClick={() => ipcRenderer.invoke("cancel-yak")}
/>
) : (
<Button
icon={<CaretRightOutlined style={{fontSize: 15}} />}
type={"link"}
ghost={true}
size={"small"}
style={{width: 30, height: 25}}
disabled={
tabList[+activeTab] && tabList[+activeTab].suffix !== "yak"
}
onClick={() => {
setErrors([])
setExecuting(true)
ipcRenderer.invoke("exec-yak", {
Script: tabList[+activeTab].code,
Params: [],
RunnerParamRaw: extraParams
})
}}
/>
)}
</Space>
)
}
>
{tabList.map((item, index) => {
return (
<TabPane tab={item.tab} key={`${index}`}>
<div style={{height: "100%"}}>
<AutoSpin spinning={executing}>
<div style={{height: "100%"}}>
<YakEditor
type={item.suffix}
value={item.code}
setValue={(value) => {
modifyCode(value, index)
}}
/>
</div>
</AutoSpin>
</div>
</TabPane>
)
})}
</Tabs>
}
firstRatio='70%'
secondNode={
<div
ref={xtermAsideRef}
style={{
width: "100%",
height: "100%",
overflow: "hidden",
borderTop: "1px solid #dfdfdf"
}}
>
<Tabs
style={{height: "100%"}}
className={"right-xterm"}
size={"small"}
tabBarExtraContent={
<Space>
<SelectOne
formItemStyle={{marginBottom: 0}}
value={outputEncoding}
setValue={setOutputEncoding}
size={"small"}
data={[
{text: "GBxxx编码", value: "latin1"},
{text: "UTF-8编码", value: "utf8"}
]}
/>
<Button
size={"small"}
icon={<DeleteOutlined />}
type={"link"}
onClick={(e) => {
xtermClear(xtermRef)
}}
/>
</Space>
}
>
<TabPane
tab={<div style={{width: 50, textAlign: "center"}}>输出</div>}
key={"output"}
>
<div style={{width: "100%", height: "100%"}}>
<CVXterm
ref={xtermRef}
options={{
convertEol: true,
theme: {
foreground: "#536870",
background: "#E8E9E8",
cursor: "#536870",
black: "#002831",
brightBlack: "#001e27",
red: "#d11c24",
brightRed: "#bd3613",
green: "#738a05",
brightGreen: "#475b62",
yellow: "#a57706",
brightYellow: "#536870",
blue: "#2176c7",
brightBlue: "#708284",
magenta: "#c61c6f",
brightMagenta: "#5956ba",
cyan: "#259286",
brightCyan: "#819090",
white: "#eae3cb",
brightWhite: "#fcf4dc"
}
}}
/>
</div>
</TabPane>
<TabPane
tab={
<div style={{width: 50, textAlign: "center"}} key={"terminal"}>
终端(监修中)
</div>
}
disabled
>
<Terminal />
</TabPane>
</Tabs>
</div>
}
secondRatio='30%'
/>
)}
{tabList.length === 0 && (
<Empty className='right-empty' description={<p>请点击左侧打开或新建文件</p>}></Empty>
)}
</div>
<Modal
visible={hintShow}
onCancel={() => setHintShow(false)}
footer={[
<Button key='link' onClick={() => setHintShow(false)}>
取消
</Button>,
<Button key='submit' onClick={() => ownCloseCode()}>
不保存
</Button>,
<Button key='back' type='primary' onClick={() => saveCode(tabList[hintIndex], hintIndex)}>
保存
</Button>
]}
>
<div style={{height: 40}}>
<ExclamationCircleOutlined style={{fontSize: 22, color: "#faad14"}} />
<span style={{fontSize: 18, marginLeft: 15}}>文件未保存</span>
</div>
<p style={{fontSize: 15, marginLeft: 37}}>{`是否要保存${hintFile}里面的内容吗?`}</p>
</Modal>
<Modal
visible={renameHint}
onCancel={() => setHintShow(false)}
footer={[
<Button key='link' onClick={() => setRenameHint(false)}>
取消
</Button>,
<Button
key='back'
type='primary'
onClick={() => {
const oldRoute = tabList[renameIndex].route
if (!oldRoute) return
const flagStr = oldRoute?.indexOf("/") > -1 ? "/" : "\\"
const routes = oldRoute?.split(flagStr)
routes?.pop()
const newRoute = routes?.concat([renameCache]).join(flagStr)
if (!oldRoute || !newRoute) return
renameFile(renameIndex, renameCache, oldRoute, newRoute, () => {
setRenameHint(false)
})
}}
>
确定
</Button>
]}
>
<div style={{height: 40}}>
<ExclamationCircleOutlined style={{fontSize: 22, color: "#faad14"}} />
<span style={{fontSize: 18, marginLeft: 15}}>文件已存在</span>
</div>
<p style={{fontSize: 15, marginLeft: 37}}>{`是否要覆盖已存在的文件吗?`}</p>
</Modal>
</div>
</AutoCard>
)
}
Example #14
Source File: UserCreate.tsx From mayoor with MIT License | 4 votes |
UserCreate: React.FC = () => {
const { t } = useTranslation();
const [createUser] = useMutation<CreateUser, CreateUserVariables>(CREATE_USER, {
onCompleted: () => {
message.success(t('User created'));
},
update: (cache, { data }) => {
const cached = cache.readQuery<GetAllUsers>({ query: GET_ALL_USERS });
if (cached === null || !data) {
return;
}
const { getAllUsers } = cached;
cache.writeQuery<GetAllUsers>({
query: GET_ALL_USERS,
data: {
getAllUsers: [...getAllUsers, data.addUser],
},
});
},
});
return (
<>
<StyledDivider orientation="left">{t('Add new user')}</StyledDivider>
<Row gutter={18}>
<Col sm={4}>
<StyledLabel>{t('Login email')}</StyledLabel>
</Col>
<Col sm={4}>
<StyledLabel>{t('Password')}</StyledLabel>
</Col>
<Col sm={6}>
<StyledLabel>{t('User Name')}</StyledLabel>
</Col>
<Col sm={4}>
<StyledLabel>{t('Role')}</StyledLabel>
</Col>
<Col sm={4}></Col>
</Row>
<Formik<FormValue>
initialValues={{
name: '',
password: '',
email: '',
role: UserRole.FACTORY,
}}
onSubmit={(values) =>
createUser({
variables: {
input: {
email: values.email,
name: values.name,
password: values.password,
role: values.role,
},
},
})
}
validationSchema={getUserValidationSchema(t)}
>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Row gutter={18}>
<Col sm={4}>
<FormInput label={t('Login email')} name="email"></FormInput>
</Col>
<Col sm={4}>
<FormInput
label={t('Password')}
name="password"
type="password"
></FormInput>
</Col>
<Col sm={6}>
<FormInput label={t('User Name')} name="name"></FormInput>
</Col>
<Col sm={4}>
<UserRoleSelect />
</Col>
<Col sm={3}>
<Button
icon={<SaveOutlined />}
htmlType="submit"
style={{ width: '100%' }}
type="primary"
>
{t('Add')}
</Button>
</Col>
</Row>
</form>
)}
</Formik>
</>
);
}
Example #15
Source File: UserEdit.tsx From mayoor with MIT License | 4 votes |
UserEdit: React.FC = () => {
const { t } = useTranslation();
const [currentlyLoading, setCurrentlyLoading] = useState<string | null>(null);
const { data } = useQuery<GetAllUsers>(GET_ALL_USERS);
const [updateUser] = useMutation<UpdateUser, UpdateUserVariables>(UPDATE_USER, {
onCompleted: () => {
message.success(t('User updated'));
},
});
const [deleteUser] = useMutation<DeleteUser, DeleteUserVariables>(DELETE_USER, {
onCompleted: () => {
message.success(t('User deleted'));
},
update: (cache, { data }) => {
const cached = cache.readQuery<GetAllUsers>({ query: GET_ALL_USERS });
if (cached === null) {
return;
}
const { getAllUsers } = cached;
cache.writeQuery<GetAllUsers>({
query: GET_ALL_USERS,
data: {
getAllUsers: getAllUsers.filter(({ id }) => id !== data?.deleteUser.id),
},
});
},
});
return (
<>
<PageTitle>{t('Users')}</PageTitle>
<StyledForm>
<MaterialEditWrapper>
<>
<Row gutter={18}>
<Col sm={4}>
<StyledLabel>{t('Login email')}</StyledLabel>
</Col>
<Col sm={4}>
<StyledLabel>{t('Change password')}</StyledLabel>
</Col>
<Col sm={6}>
<StyledLabel>{t('User Name')}</StyledLabel>
</Col>
<Col sm={4}>
<StyledLabel>{t('Role')}</StyledLabel>
</Col>
<Col sm={4}></Col>
</Row>
{data?.getAllUsers.map((user) => (
<Formik<FormikValues>
key={user.id}
initialValues={{ ...user, password: undefined }}
onSubmit={async (values) => {
setCurrentlyLoading(user.id);
await updateUser({
variables: {
id: user.id,
input: {
email: values.email,
name: values.name,
role: values.role,
password: values.password || undefined,
},
},
});
setCurrentlyLoading(null);
}}
validationSchema={getUserValidationSchema(t)}
>
{({ handleSubmit }) => (
<Row gutter={18}>
<Col sm={4}>
<FormInput
label={t('Login email')}
name="email"
></FormInput>
</Col>
<Col sm={4}>
<FormInput
label={t('New Password')}
name="password"
type="password"
></FormInput>
</Col>
<Col sm={6}>
<FormInput
label={t('User Name')}
name="name"
></FormInput>
</Col>
<Col sm={4}>
<UserRoleSelect />
</Col>
<Col sm={3}>
<Button
onClick={() => handleSubmit()}
loading={currentlyLoading === user.id}
icon={<SaveOutlined />}
style={{ width: '100%' }}
>
{t('Save')}
</Button>
</Col>
<Col sm={1} style={{ textAlign: 'right' }}>
<Popconfirm
placement="topRight"
onConfirm={() =>
deleteUser({ variables: { id: user.id } })
}
title={t('Do you really want to remove this user?')}
>
<Button
shape="circle"
icon={<DeleteOutlined />}
></Button>
</Popconfirm>
</Col>
</Row>
)}
</Formik>
))}
<UserCreate />
</>
</MaterialEditWrapper>
</StyledForm>
</>
);
}
Example #16
Source File: index.tsx From memex with MIT License | 4 votes |
OperatorBar: React.FC<OperatorBarProps> = () => {
const dispatch = useDispatch();
const menus = [
{
id: 'inbox',
name: 'Inbox',
icon: <InboxOutlined />,
onClick: () => {
dispatch({
type: 'CHANGE_PAGE',
payload: {
page: 'inbox',
},
});
},
},
{
id: 'notes',
name: 'Notes & Highlights',
icon: <ReadOutlined />,
onClick: () => {
dispatch({
type: 'CHANGE_PAGE',
payload: {
page: 'notes',
},
});
},
},
{
id: 'search',
name: 'Search',
icon: <SearchOutlined />,
},
{
id: 'divider1',
isDivider: true,
},
{
id: 'tag_management',
name: 'Tag Management',
icon: <ApartmentOutlined />,
onClick: () => {
dispatch({
type: 'CHANGE_PAGE',
payload: {
page: 'tag_management',
},
});
},
},
{
id: 'diffuse',
name: 'Knowledge Map',
icon: <DeploymentUnitOutlined />,
onClick: () => {
dispatch({
type: 'CHANGE_PAGE',
payload: {
page: 'graph',
},
});
},
},
{
id: 'divider2',
isDivider: true,
},
{
id: 'save',
name: 'Save',
icon: <SaveOutlined />,
},
{
id: 'clear',
name: 'Delete',
icon: <DeleteOutlined />,
},
{
id: 'setting',
name: 'Settings',
icon: <SettingOutlined />,
},
];
return (
<div>
<ToolBar data={menus} className="operator-bar" />
</div>
);
}
Example #17
Source File: palette.tsx From jmix-frontend with Apache License 2.0 | 4 votes |
palette = () => (
<Palette>
<Category name="Text">
<Component name="Formatted Message">
<Variant>
<FormattedMessage />
</Variant>
</Component>
<Component name="Heading">
<Variant name="h1">
<Typography.Title></Typography.Title>
</Variant>
<Variant name="h2">
<Typography.Title level={2}></Typography.Title>
</Variant>
<Variant name="h3">
<Typography.Title level={3}></Typography.Title>
</Variant>
<Variant name="h4">
<Typography.Title level={4}></Typography.Title>
</Variant>
<Variant name="h5">
<Typography.Title level={5}></Typography.Title>
</Variant>
</Component>
<Component name="Text">
<Variant>
<Typography.Text></Typography.Text>
</Variant>
<Variant name="Secondary">
<Typography.Text type="secondary"></Typography.Text>
</Variant>
<Variant name="Success">
<Typography.Text type="success"></Typography.Text>
</Variant>
<Variant name="Warning">
<Typography.Text type="warning"></Typography.Text>
</Variant>
<Variant name="Danger">
<Typography.Text type="danger"></Typography.Text>
</Variant>
<Variant name="Disabled">
<Typography.Text disabled></Typography.Text>
</Variant>
</Component>
</Category>
<Category name="Layout">
<Component name="Divider">
<Variant>
<Divider />
</Variant>
</Component>
<Component name="Grid">
<Variant name="Simple Row">
<Row></Row>
</Variant>
<Variant name="Two columns">
<Row>
<Col span={12}></Col>
<Col span={12}></Col>
</Row>
</Variant>
<Variant name="Three columns">
<Row>
<Col span={8}></Col>
<Col span={8}></Col>
<Col span={8}></Col>
</Row>
</Variant>
</Component>
<Component name="Space">
<Variant>
<Space />
</Variant>
<Variant name="Small">
<Space size={"small"} />
</Variant>
<Variant name="Large">
<Space size={"large"} />
</Variant>
</Component>
</Category>
<Category name="Controls">
<Component name="Autocomplete">
<Variant>
<AutoComplete placeholder="input here" />
</Variant>
</Component>
<Component name="Button">
<Variant>
<Button></Button>
</Variant>
<Variant name="Primary">
<Button type="primary"></Button>
</Variant>
<Variant name="Link">
<Button type="link"></Button>
</Variant>
<Variant name="Dropdown">
<Dropdown
trigger={["click"]}
overlay={
<Menu>
<Menu.Item></Menu.Item>
<Menu.Item></Menu.Item>
<Menu.Item></Menu.Item>
</Menu>
}
>
<Button></Button>
</Dropdown>
</Variant>
</Component>
<Component name="Checkbox">
<Variant>
<Checkbox />
</Variant>
</Component>
<Component name="Switch">
<Variant>
<Switch />
</Variant>
</Component>
<Component name="Radio Group">
<Variant>
<Radio.Group>
<Radio value={1}>A</Radio>
<Radio value={2}>B</Radio>
<Radio value={3}>C</Radio>
<Radio value={4}>D</Radio>
</Radio.Group>
</Variant>
<Variant name="Button">
<Radio.Group>
<Radio.Button value={1}>A</Radio.Button>
<Radio.Button value={2}>B</Radio.Button>
<Radio.Button value={3}>C</Radio.Button>
<Radio.Button value={4}>D</Radio.Button>
</Radio.Group>
</Variant>
</Component>
<Component name="DatePicker">
<Variant>
<DatePicker />
</Variant>
<Variant name="Range">
<DatePicker.RangePicker />
</Variant>
</Component>
<Component name="TimePicker">
<Variant>
<TimePicker />
</Variant>
<Variant name="Range">
<TimePicker.RangePicker />
</Variant>
</Component>
<Component name="Input">
<Variant>
<Input />
</Variant>
<Variant name="Number">
<InputNumber />
</Variant>
</Component>
<Component name="Select">
<Variant>
<Select defaultValue="1">
<Select.Option value="1">1</Select.Option>
<Select.Option value="2">2</Select.Option>
</Select>
</Variant>
<Variant name="Multiple">
<Select defaultValue={["1"]} mode="multiple" allowClear>
<Select.Option value="1">1</Select.Option>
<Select.Option value="2">2</Select.Option>
</Select>
</Variant>
</Component>
<Component name="Link">
<Variant>
<Typography.Link href="" target="_blank"></Typography.Link>
</Variant>
</Component>
<Component name="Slider">
<Variant>
<Slider defaultValue={30} />
</Variant>
<Variant name="Range">
<Slider range defaultValue={[20, 50]} />
</Variant>
</Component>
</Category>
<Category name="Data Display">
<Component name="Field">
<Variant>
<Field
entityName={ENTITY_NAME}
disabled={readOnlyMode}
propertyName=""
formItemProps={{
style: { marginBottom: "12px" }
}}
/>
</Variant>
</Component>
<Component name="Card">
<Variant>
<Card />
</Variant>
<Variant name="With Title">
<Card>
<Card title="Card title">
<p>Card content</p>
</Card>
</Card>
</Variant>
<Variant name="My custom card">
<Card>
<Card title="Card title">
<p>Card content</p>
<Avatar />
</Card>
</Card>
</Variant>
</Component>
<Component name="Tabs">
<Variant>
<Tabs defaultActiveKey="1">
<Tabs.TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</Tabs.TabPane>
</Tabs>
</Variant>
<Variant name="Tab Pane">
<Tabs.TabPane></Tabs.TabPane>
</Variant>
</Component>
<Component name="Collapse">
<Variant>
<Collapse defaultActiveKey="1">
<Collapse.Panel
header="This is panel header 1"
key="1"
></Collapse.Panel>
<Collapse.Panel
header="This is panel header 2"
key="2"
></Collapse.Panel>
<Collapse.Panel
header="This is panel header 3"
key="3"
></Collapse.Panel>
</Collapse>
</Variant>
</Component>
<Component name="Image">
<Variant>
<Image width={200} src="" />
</Variant>
</Component>
<Component name="Avatar">
<Variant>
<Avatar icon={<UserOutlined />} />
</Variant>
<Variant name="Image">
<Avatar src="https://joeschmoe.io/api/v1/random" />
</Variant>
</Component>
<Component name="Badge">
<Variant>
<Badge count={1}></Badge>
</Variant>
</Component>
<Component name="Statistic">
<Variant>
<Statistic title="Title" value={112893} />
</Variant>
</Component>
<Component name="Alert">
<Variant name="Success">
<Alert message="Text" type="success" />
</Variant>
<Variant name="Info">
<Alert message="Text" type="info" />
</Variant>
<Variant name="Warning">
<Alert message="Text" type="warning" />
</Variant>
<Variant name="Error">
<Alert message="Text" type="error" />
</Variant>
</Component>
<Component name="List">
<Variant>
<List
bordered
dataSource={[]}
renderItem={item => <List.Item></List.Item>}
/>
</Variant>
</Component>
</Category>
<Category name="Icons">
<Component name="Arrow">
<Variant name="Up">
<ArrowUpOutlined />
</Variant>
<Variant name="Down">
<ArrowDownOutlined />
</Variant>
<Variant name="Left">
<ArrowLeftOutlined />
</Variant>
<Variant name="Right">
<ArrowRightOutlined />
</Variant>
</Component>
<Component name="Question">
<Variant>
<QuestionOutlined />
</Variant>
<Variant name="Circle">
<QuestionCircleOutlined />
</Variant>
</Component>
<Component name="Plus">
<Variant>
<PlusOutlined />
</Variant>
<Variant name="Circle">
<PlusCircleOutlined />
</Variant>
</Component>
<Component name="Info">
<Variant>
<InfoOutlined />
</Variant>
<Variant name="Circle">
<InfoCircleOutlined />
</Variant>
</Component>
<Component name="Exclamation">
<Variant>
<ExclamationOutlined />
</Variant>
<Variant name="Circle">
<ExclamationCircleOutlined />
</Variant>
</Component>
<Component name="Close">
<Variant>
<CloseOutlined />
</Variant>
<Variant name="Circle">
<CloseCircleOutlined />
</Variant>
</Component>
<Component name="Check">
<Variant>
<CheckOutlined />
</Variant>
<Variant name="Circle">
<CheckCircleOutlined />
</Variant>
</Component>
<Component name="Edit">
<Variant>
<EditOutlined />
</Variant>
</Component>
<Component name="Copy">
<Variant>
<CopyOutlined />
</Variant>
</Component>
<Component name="Delete">
<Variant>
<DeleteOutlined />
</Variant>
</Component>
<Component name="Bars">
<Variant>
<BarsOutlined />
</Variant>
</Component>
<Component name="Bell">
<Variant>
<BellOutlined />
</Variant>
</Component>
<Component name="Clear">
<Variant>
<ClearOutlined />
</Variant>
</Component>
<Component name="Download">
<Variant>
<DownloadOutlined />
</Variant>
</Component>
<Component name="Upload">
<Variant>
<UploadOutlined />
</Variant>
</Component>
<Component name="Sync">
<Variant>
<SyncOutlined />
</Variant>
</Component>
<Component name="Save">
<Variant>
<SaveOutlined />
</Variant>
</Component>
<Component name="Search">
<Variant>
<SearchOutlined />
</Variant>
</Component>
<Component name="Settings">
<Variant>
<SettingOutlined />
</Variant>
</Component>
<Component name="Paperclip">
<Variant>
<PaperClipOutlined />
</Variant>
</Component>
<Component name="Phone">
<Variant>
<PhoneOutlined />
</Variant>
</Component>
<Component name="Mail">
<Variant>
<MailOutlined />
</Variant>
</Component>
<Component name="Home">
<Variant>
<HomeOutlined />
</Variant>
</Component>
<Component name="Contacts">
<Variant>
<ContactsOutlined />
</Variant>
</Component>
<Component name="User">
<Variant>
<UserOutlined />
</Variant>
<Variant name="Add">
<UserAddOutlined />
</Variant>
<Variant name="Remove">
<UserDeleteOutlined />
</Variant>
</Component>
<Component name="Team">
<Variant>
<TeamOutlined />
</Variant>
</Component>
</Category>
<Category name="Screens">
<Component name="ExampleCustomScreen">
<Variant>
<ExampleCustomScreen />
</Variant>
</Component>
<Component name="CustomEntityFilterTest">
<Variant>
<CustomEntityFilterTest />
</Variant>
</Component>
<Component name="CustomFormControls">
<Variant>
<CustomFormControls />
</Variant>
</Component>
<Component name="CustomDataDisplayComponents">
<Variant>
<CustomDataDisplayComponents />
</Variant>
</Component>
<Component name="CustomAppLayouts">
<Variant>
<CustomAppLayouts />
</Variant>
</Component>
<Component name="CustomControls">
<Variant>
<CustomControls />
</Variant>
</Component>
<Component name="ErrorBoundaryTests">
<Variant>
<ErrorBoundaryTests />
</Variant>
</Component>
<Component name="TestBlankScreen">
<Variant>
<TestBlankScreen />
</Variant>
</Component>
<Component name="CarEditor">
<Variant>
<CarEditor />
</Variant>
</Component>
<Component name="CarBrowserCards">
<Variant>
<CarBrowserCards />
</Variant>
</Component>
<Component name="CarBrowserList">
<Variant>
<CarBrowserList />
</Variant>
</Component>
<Component name="CarBrowserTable">
<Variant>
<CarBrowserTable />
</Variant>
</Component>
<Component name="CarCardsGrid">
<Variant>
<CarCardsGrid />
</Variant>
</Component>
<Component name="FavoriteCars">
<Variant>
<FavoriteCars />
</Variant>
</Component>
<Component name="CarCardsWithDetails">
<Variant>
<CarCardsWithDetails />
</Variant>
</Component>
<Component name="CarTableWithFilters">
<Variant>
<CarTableWithFilters />
</Variant>
</Component>
<Component name="CarMasterDetail">
<Variant>
<CarMasterDetail />
</Variant>
</Component>
<Component name="FormWizardCompositionO2O">
<Variant>
<FormWizardCompositionO2O />
</Variant>
</Component>
<Component name="FormWizardEditor">
<Variant>
<FormWizardEditor />
</Variant>
</Component>
<Component name="FormWizardBrowserTable">
<Variant>
<FormWizardBrowserTable />
</Variant>
</Component>
<Component name="CarMultiSelectionTable">
<Variant>
<CarMultiSelectionTable />
</Variant>
</Component>
<Component name="DatatypesTestEditor">
<Variant>
<DatatypesTestEditor />
</Variant>
</Component>
<Component name="DatatypesTestBrowserCards">
<Variant>
<DatatypesTestBrowserCards />
</Variant>
</Component>
<Component name="DatatypesTestBrowserList">
<Variant>
<DatatypesTestBrowserList />
</Variant>
</Component>
<Component name="DatatypesTestBrowserTable">
<Variant>
<DatatypesTestBrowserTable />
</Variant>
</Component>
<Component name="DatatypesTestCards">
<Variant>
<DatatypesTestCards />
</Variant>
</Component>
<Component name="AssociationO2OEditor">
<Variant>
<AssociationO2OEditor />
</Variant>
</Component>
<Component name="AssociationO2OBrowserTable">
<Variant>
<AssociationO2OBrowserTable />
</Variant>
</Component>
<Component name="AssociationO2MEditor">
<Variant>
<AssociationO2MEditor />
</Variant>
</Component>
<Component name="AssociationO2MBrowserTable">
<Variant>
<AssociationO2MBrowserTable />
</Variant>
</Component>
<Component name="AssociationM2OEditor">
<Variant>
<AssociationM2OEditor />
</Variant>
</Component>
<Component name="AssociationM2OBrowserTable">
<Variant>
<AssociationM2OBrowserTable />
</Variant>
</Component>
<Component name="AssociationM2MEditor">
<Variant>
<AssociationM2MEditor />
</Variant>
</Component>
<Component name="AssociationM2MBrowserTable">
<Variant>
<AssociationM2MBrowserTable />
</Variant>
</Component>
<Component name="CompositionO2OEditor">
<Variant>
<CompositionO2OEditor />
</Variant>
</Component>
<Component name="CompositionO2OBrowserTable">
<Variant>
<CompositionO2OBrowserTable />
</Variant>
</Component>
<Component name="CompositionO2MEditor">
<Variant>
<CompositionO2MEditor />
</Variant>
</Component>
<Component name="CompositionO2MBrowserTable">
<Variant>
<CompositionO2MBrowserTable />
</Variant>
</Component>
<Component name="DeeplyNestedTestEntityEditor">
<Variant>
<DeeplyNestedTestEntityEditor />
</Variant>
</Component>
<Component name="DeeplyNestedO2MTestEntityTable">
<Variant>
<DeeplyNestedO2MTestEntityTable />
</Variant>
</Component>
<Component name="DeeplyNestedO2MTestEntityEditor">
<Variant>
<DeeplyNestedO2MTestEntityEditor />
</Variant>
</Component>
<Component name="IntIdEditor">
<Variant>
<IntIdEditor />
</Variant>
</Component>
<Component name="IntIdBrowserTable">
<Variant>
<IntIdBrowserTable />
</Variant>
</Component>
<Component name="IntIdBrowserCards">
<Variant>
<IntIdBrowserCards />
</Variant>
</Component>
<Component name="IntIdBrowserList">
<Variant>
<IntIdBrowserList />
</Variant>
</Component>
<Component name="IntIdentityIdCards">
<Variant>
<IntIdentityIdCards />
</Variant>
</Component>
<Component name="IntIdentityIdEditor">
<Variant>
<IntIdentityIdEditor />
</Variant>
</Component>
<Component name="IntIdentityIdBrowserTable">
<Variant>
<IntIdentityIdBrowserTable />
</Variant>
</Component>
<Component name="IntIdentityIdBrowserCards">
<Variant>
<IntIdentityIdBrowserCards />
</Variant>
</Component>
<Component name="IntIdentityIdBrowserList">
<Variant>
<IntIdentityIdBrowserList />
</Variant>
</Component>
<Component name="StringIdCards">
<Variant>
<StringIdCards />
</Variant>
</Component>
<Component name="StringIdMgtCardsEdit">
<Variant>
<StringIdMgtCardsEdit />
</Variant>
</Component>
<Component name="StringIdBrowserCards">
<Variant>
<StringIdBrowserCards />
</Variant>
</Component>
<Component name="StringIdBrowserList">
<Variant>
<StringIdBrowserList />
</Variant>
</Component>
<Component name="StringIdBrowserTable">
<Variant>
<StringIdBrowserTable />
</Variant>
</Component>
<Component name="WeirdStringIdEditor">
<Variant>
<WeirdStringIdEditor />
</Variant>
</Component>
<Component name="WeirdStringIdBrowserCards">
<Variant>
<WeirdStringIdBrowserCards />
</Variant>
</Component>
<Component name="WeirdStringIdBrowserList">
<Variant>
<WeirdStringIdBrowserList />
</Variant>
</Component>
<Component name="WeirdStringIdBrowserTable">
<Variant>
<WeirdStringIdBrowserTable />
</Variant>
</Component>
<Component name="BoringStringIdEditor">
<Variant>
<BoringStringIdEditor />
</Variant>
</Component>
<Component name="BoringStringIdBrowserTable">
<Variant>
<BoringStringIdBrowserTable />
</Variant>
</Component>
<Component name="TrickyIdEditor">
<Variant>
<TrickyIdEditor />
</Variant>
</Component>
<Component name="TrickyIdBrowserTable">
<Variant>
<TrickyIdBrowserTable />
</Variant>
</Component>
</Category>
</Palette>
)
Example #18
Source File: index.tsx From jetlinks-ui-antd with MIT License | 4 votes |
Visualization: React.FC<Props> = props => {
const [addItem, setAddItem] = useState(false);
const [edit, setEdit] = useState<boolean>(false);
const [layout, setLayout] = useState<any>([]);
const [current, setCurrent] = useState<any>();
const removeCard = (item: any) => {
const temp = layout.findIndex((it: any) => item.i === it.i);
layout.splice(temp, 1);
setLayout([...layout]);
}
const tempRef = useRef<any>();
useEffect(() => {
let subscription: any;
apis.visualization.getLayout({
target: props.target,
type: props.type
}).then(response => {
if (response.status === 200) {
const currentLayout = response.result.metadata === "" ? [] : JSON.parse(response.result.metadata);
subscription = rxjs.from(currentLayout).pipe(map((item: any) => {
const temp = item;
const tempProps = {
// item: item,
ready: (onReady: Function) => {
temp.doReady = onReady;
},
ref: tempRef,
onLoad: () => tempRef.current.onLoad(),
edit: (doEdit: Function) => {
temp.doEdit = doEdit;
},
complate: {},
loading: {},
hide: {},
onEvent: {},
id: item.i,
};
temp.props = tempProps;
return item;
}))
.pipe(toArray())
.subscribe(
item => {
// console.log(item, 'tiem')
setLayout(item);
},
() => { message.error('error') },
() => { }
)
}
}).catch(() => {
message.error('加载数据错误');
setLayout([]);
})
return () => subscription && subscription.unsubscribe();
}, []);
const saveLayout = () => {
apis.visualization.saveOrUpdate({
metadata: JSON.stringify(layout),
type: props.type,
target: props.target,
name: props.name,
id: `${props.type}:${props.target}`
} as VisualizationItem).then(response => {
if (response.status === 200) {
message.success('保存成功');
}
}).catch(() => {
message.error('保存失败!');
})
}
const layoutChange = (currnetLayout: any[]) => {
const newLayout = layout.map((item: any) => {
const temp = currnetLayout.find(i => i.i === item.i);
return { ...item, ...temp, };
});
setLayout(newLayout);
}
const saveLayoutItem = (item: any) => {
const id = randomString(8);
if (current) {
const index = layout.findIndex((i: any) => i.i === current.i);
current.config = item;
layout[index] = current;
setLayout(layout);
} else {
setLayout([{
i: id,
x: 0,
y: Infinity,
config: item,
h: 5,
w: 5,
}, ...layout]);
}
setAddItem(false);
}
const renderGridLayout = () =>
(
<>
<ReactGridLayout
onLayoutChange={(item: any) => {
layoutChange(item)
}}
// cols={{ md: 12 }}
isResizable={edit}
isDraggable={edit}
onDragStop={() => {
setLayout([...layout])
}}
onResizeStop={() => {
setLayout([...layout])
}}
className="layout"
layout={layout}
rowHeight={30}
>
{layout.map((item: any) => (
<Card
style={{ overflow: "hidden" }}
key={item.i}
id={item.i}
>
<div style={{ position: 'absolute', right: 15, top: 5, }}>
<div style={{ float: 'right' }}>
<Fragment>
{edit && (
<>
<Tooltip title="删除">
<CloseCircleOutlined onClick={() => removeCard(item)} />
</Tooltip>
<Divider type="vertical" />
<Tooltip title="编辑">
<EditOutlined onClick={() => {
setAddItem(true);
setCurrent(item)
}} />
</Tooltip>
</>)}
{item.doReady &&
<>
<Divider type="vertical" />
<Tooltip title="刷新">
<SyncOutlined onClick={() => { item.doReady() }} />
</Tooltip>
</>}
</Fragment>
</div>
</div>
<GridCard
{...item}
productId={props.productId}
deviceId={props.target} />
</Card>))}
</ReactGridLayout>
</>
)
return (
<>
{layout.length > 0 ? renderGridLayout() : (
<Button
style={{ width: '300px', height: '200px' }}
type="dashed"
onClick={() => {
setCurrent(undefined);
setEdit(true)
setAddItem(true);
}}
>
<Icon type="plus" />
新增
</Button>
)}
<div className={styles.optionGroup}>
{edit ?
<div style={{ float: 'right' }}>
<Tooltip title="新增" style={{ float: 'right' }}>
<Button
type="danger"
shape="circle"
size="large"
onClick={() => {
setCurrent(undefined);
setAddItem(true)
}}
>
<PlusOutlined />
</Button>
</Tooltip>
<div style={{ float: 'right', marginLeft: 10 }}>
<Tooltip title="保存" >
<Button
type="primary"
shape="circle"
size="large"
onClick={() => {
setEdit(false);
saveLayout();
}}
>
<SaveOutlined />
</Button>
</Tooltip>
</div>
</div> :
<div style={{ textAlign: 'center' }}>
<Tooltip title="编辑" >
<Button
type="danger"
shape="circle"
size="large"
onClick={() => setEdit(true)}
>
<EditOutlined />
</Button>
</Tooltip>
</div>
}
</div>
{addItem && (
<AddItem
close={() => { setAddItem(false) }}
metaData={props.metaData}
current={current}
save={(item: any) => {
saveLayoutItem(item);
}}
/>
)}
</ >
)
}
Example #19
Source File: MixedArguments.tsx From yugong with MIT License | 4 votes |
Mixedarguments: React.FC<Props> = ({ typeArguments, onChange, className }) => {
const [jsonData, setJsonData] = useState<AppDataLayoutItemTypes>();
const [jsonMode, setJsonMode] = useState<'view' | 'code'>('view');
useEffect(() => {
const result = cloneDeep(typeArguments);
setJsonData(result.data || {});
}, [typeArguments]);
const jsoneditor = useRef<JSONEditor>();
const container = useRef<any>();
const onsubmit = useCallback(() => {
try {
var json = jsoneditor.current?.get();
if (json && onChange instanceof Function) {
const result = cloneDeep(typeArguments);
result.data = json;
jsoneditor.current?.setMode('view');
setJsonMode('view');
onChange(result);
message.success(`${typeArguments.name}已更新!`);
}
} catch (e) {
message.error('保存失败!JSON数据格式不正确');
return;
}
}, [onChange, typeArguments]);
useEffect(() => {
if (container.current && jsonData) {
jsoneditor.current = new JSONEditor(container.current, {
mode: jsonMode,
mainMenuBar: false,
});
jsoneditor.current.set(jsonData);
}
return () => {
if (jsoneditor.current) {
jsoneditor.current.destroy();
}
};
}, [jsonData, jsonMode]);
const onChangeJsonMode = useCallback((e) => {
try {
var json = jsoneditor.current?.get();
if (json) {
jsoneditor.current?.setMode('code');
setJsonMode('code');
}
} catch (error) {
console.error(error);
}
}, []);
return (
<div className={classNames(className)}>
<div className={s.toolbar} >
<div>
<CopyToClipboard
text={JSON.stringify(jsonData)}
onCopy={() => message.info('已复制到剪切板')}
>
<Button size="small" icon={<CopyOutlined alt="复制到剪切板" />}>
复制
</Button>
</CopyToClipboard>
{jsonMode === 'view' ? (
<Button
size="small"
type="primary"
onClick={onChangeJsonMode}
icon={<FormOutlined alt="编辑JSON" />}
>
编辑
</Button>
) : null}
{jsonMode === 'code' ? (
<Button
size="small"
type="primary"
onClick={onsubmit}
icon={<SaveOutlined alt="保存JSON" />}
>
保存
</Button>
) : null}
</div>
</div>
<div className={s.wrap} ref={container} />
</div>
);
}
Example #20
Source File: FunctionDebuggerToolbar.tsx From next-basics with GNU General Public License v3.0 | 4 votes |
export function FunctionDebuggerToolbar({
type,
status,
saveDisabled,
onButtonClick,
}: FunctionDebuggerToolbarProps): React.ReactElement {
const refinedType = type ?? "input";
const isInput = refinedType === "input" || refinedType === "test-input";
const handleRunClick = useCallback(() => {
onButtonClick?.({ action: "run" });
}, [onButtonClick]);
const handleSaveClick = useCallback(() => {
if (!saveDisabled) {
onButtonClick?.({ action: "save" });
}
}, [onButtonClick, saveDisabled]);
const handleDeleteClick = useCallback(() => {
onButtonClick?.({ action: "delete" });
}, [onButtonClick]);
return (
<div
className={classNames(
styles.debuggerToolbar,
status && styles[status],
refinedType === "input" || refinedType === "output"
? styles.debug
: styles.test,
isInput ? styles.input : styles.output
)}
data-override-theme="dark"
>
<div className={styles.header}>
{refinedType === "input"
? "Input"
: refinedType === "test-input"
? "Test Input"
: refinedType === "test-output"
? "Expect Output"
: "Output"}
{isInput && (
<span className={styles.headerSuffix}>
(argument list in JSON format)
</span>
)}
</div>
{isInput ? (
<div className={styles.buttons}>
<Tooltip title="Run">
<div className={styles.debuggerButton} onClick={handleRunClick}>
<PlayCircleOutlined />
</div>
</Tooltip>
<Tooltip
title={refinedType === "input" ? "Add as a test case" : "Update"}
>
<div
className={classNames(styles.debuggerButton, {
[styles.disabled]: saveDisabled,
})}
onClick={handleSaveClick}
>
{refinedType === "input" ? (
<PlusCircleOutlined />
) : (
<SaveOutlined />
)}
</div>
</Tooltip>
{refinedType === "test-input" && (
<Tooltip title="Delete">
<div
className={styles.debuggerButton}
onClick={handleDeleteClick}
>
<DeleteOutlined />
</div>
</Tooltip>
)}
</div>
) : (
refinedType === "test-output" && (
<div className={styles.secondHeader}>
{status === "ok" ? (
<>
<span className={styles.secondHeaderIcon}>
<CheckOutlined />
</span>
<span>Test: passed</span>
</>
) : status === "failed" ? (
<>
<span className={styles.secondHeaderIcon}>
<CloseOutlined />
</span>
<span>Test: failed</span>
</>
) : (
<>
<span className={styles.secondHeaderIcon}>
<QuestionOutlined />
</span>
<span>Test: expired</span>
</>
)}
</div>
)
)}
</div>
);
}
Example #21
Source File: Icon.tsx From html2sketch with MIT License | 4 votes |
IconSymbol: FC = () => {
return (
<Row>
{/*<CaretUpOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/1.CaretUpOutlined'}*/}
{/*/>*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.MailOutlined'}*/}
{/*/>*/}
{/*<StepBackwardOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
{/*/>*/}
{/*<StepForwardOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
{/*/>*/}
<StepForwardOutlined />
<ShrinkOutlined />
<ArrowsAltOutlined />
<DownOutlined />
<UpOutlined />
<LeftOutlined />
<RightOutlined />
<CaretUpOutlined />
<CaretDownOutlined />
<CaretLeftOutlined />
<CaretRightOutlined />
<VerticalAlignTopOutlined />
<RollbackOutlined />
<FastBackwardOutlined />
<FastForwardOutlined />
<DoubleRightOutlined />
<DoubleLeftOutlined />
<VerticalLeftOutlined />
<VerticalRightOutlined />
<VerticalAlignMiddleOutlined />
<VerticalAlignBottomOutlined />
<ForwardOutlined />
<BackwardOutlined />
<EnterOutlined />
<RetweetOutlined />
<SwapOutlined />
<SwapLeftOutlined />
<SwapRightOutlined />
<ArrowUpOutlined />
<ArrowDownOutlined />
<ArrowLeftOutlined />
<ArrowRightOutlined />
<LoginOutlined />
<LogoutOutlined />
<MenuFoldOutlined />
<MenuUnfoldOutlined />
<BorderBottomOutlined />
<BorderHorizontalOutlined />
<BorderInnerOutlined />
<BorderOuterOutlined />
<BorderLeftOutlined />
<BorderRightOutlined />
<BorderTopOutlined />
<BorderVerticleOutlined />
<PicCenterOutlined />
<PicLeftOutlined />
<PicRightOutlined />
<RadiusBottomleftOutlined />
<RadiusBottomrightOutlined />
<RadiusUpleftOutlined />
<RadiusUprightOutlined />
<FullscreenOutlined />
<FullscreenExitOutlined />
<QuestionOutlined />
<PauseOutlined />
<MinusOutlined />
<PauseCircleOutlined />
<InfoOutlined />
<CloseOutlined />
<ExclamationOutlined />
<CheckOutlined />
<WarningOutlined />
<IssuesCloseOutlined />
<StopOutlined />
<EditOutlined />
<CopyOutlined />
<ScissorOutlined />
<DeleteOutlined />
<SnippetsOutlined />
<DiffOutlined />
<HighlightOutlined />
<AlignCenterOutlined />
<AlignLeftOutlined />
<AlignRightOutlined />
<BgColorsOutlined />
<BoldOutlined />
<ItalicOutlined />
<UnderlineOutlined />
<StrikethroughOutlined />
<RedoOutlined />
<UndoOutlined />
<ZoomInOutlined />
<ZoomOutOutlined />
<FontColorsOutlined />
<FontSizeOutlined />
<LineHeightOutlined />
<SortAscendingOutlined />
<SortDescendingOutlined />
<DragOutlined />
<OrderedListOutlined />
<UnorderedListOutlined />
<RadiusSettingOutlined />
<ColumnWidthOutlined />
<ColumnHeightOutlined />
<AreaChartOutlined />
<PieChartOutlined />
<BarChartOutlined />
<DotChartOutlined />
<LineChartOutlined />
<RadarChartOutlined />
<HeatMapOutlined />
<FallOutlined />
<RiseOutlined />
<StockOutlined />
<BoxPlotOutlined />
<FundOutlined />
<SlidersOutlined />
<AndroidOutlined />
<AppleOutlined />
<WindowsOutlined />
<IeOutlined />
<ChromeOutlined />
<GithubOutlined />
<AliwangwangOutlined />
<DingdingOutlined />
<WeiboSquareOutlined />
<WeiboCircleOutlined />
<TaobaoCircleOutlined />
<Html5Outlined />
<WeiboOutlined />
<TwitterOutlined />
<WechatOutlined />
<AlipayCircleOutlined />
<TaobaoOutlined />
<SkypeOutlined />
<FacebookOutlined />
<CodepenOutlined />
<CodeSandboxOutlined />
<AmazonOutlined />
<GoogleOutlined />
<AlipayOutlined />
<AntDesignOutlined />
<AntCloudOutlined />
<ZhihuOutlined />
<SlackOutlined />
<SlackSquareOutlined />
<BehanceSquareOutlined />
<DribbbleOutlined />
<DribbbleSquareOutlined />
<InstagramOutlined />
<YuqueOutlined />
<AlibabaOutlined />
<YahooOutlined />
<RedditOutlined />
<SketchOutlined />
<AccountBookOutlined />
<AlertOutlined />
<ApartmentOutlined />
<ApiOutlined />
<QqOutlined />
<MediumWorkmarkOutlined />
<GitlabOutlined />
<MediumOutlined />
<GooglePlusOutlined />
<AppstoreAddOutlined />
<AppstoreOutlined />
<AudioOutlined />
<AudioMutedOutlined />
<AuditOutlined />
<BankOutlined />
<BarcodeOutlined />
<BarsOutlined />
<BellOutlined />
<BlockOutlined />
<BookOutlined />
<BorderOutlined />
<BranchesOutlined />
<BuildOutlined />
<BulbOutlined />
<CalculatorOutlined />
<CalendarOutlined />
<CameraOutlined />
<CarOutlined />
<CarryOutOutlined />
<CiCircleOutlined />
<CiOutlined />
<CloudOutlined />
<ClearOutlined />
<ClusterOutlined />
<CodeOutlined />
<CoffeeOutlined />
<CompassOutlined />
<CompressOutlined />
<ContactsOutlined />
<ContainerOutlined />
<ControlOutlined />
<CopyrightCircleOutlined />
<CopyrightOutlined />
<CreditCardOutlined />
<CrownOutlined />
<CustomerServiceOutlined />
<DashboardOutlined />
<DatabaseOutlined />
<DeleteColumnOutlined />
<DeleteRowOutlined />
<DisconnectOutlined />
<DislikeOutlined />
<DollarCircleOutlined />
<DollarOutlined />
<DownloadOutlined />
<EllipsisOutlined />
<EnvironmentOutlined />
<EuroCircleOutlined />
<EuroOutlined />
<ExceptionOutlined />
<ExpandAltOutlined />
<ExpandOutlined />
<ExperimentOutlined />
<ExportOutlined />
<EyeOutlined />
<FieldBinaryOutlined />
<FieldNumberOutlined />
<FieldStringOutlined />
<DesktopOutlined />
<DingtalkOutlined />
<FileAddOutlined />
<FileDoneOutlined />
<FileExcelOutlined />
<FileExclamationOutlined />
<FileOutlined />
<FileImageOutlined />
<FileJpgOutlined />
<FileMarkdownOutlined />
<FilePdfOutlined />
<FilePptOutlined />
<FileProtectOutlined />
<FileSearchOutlined />
<FileSyncOutlined />
<FileTextOutlined />
<FileUnknownOutlined />
<FileWordOutlined />
<FilterOutlined />
<FireOutlined />
<FlagOutlined />
<FolderAddOutlined />
<FolderOutlined />
<FolderOpenOutlined />
<ForkOutlined />
<FormatPainterOutlined />
<FrownOutlined />
<FunctionOutlined />
<FunnelPlotOutlined />
<GatewayOutlined />
<GifOutlined />
<GiftOutlined />
<GlobalOutlined />
<GoldOutlined />
<GroupOutlined />
<HddOutlined />
<HeartOutlined />
<HistoryOutlined />
<HomeOutlined />
<HourglassOutlined />
<IdcardOutlined />
<ImportOutlined />
<InboxOutlined />
<InsertRowAboveOutlined />
<InsertRowBelowOutlined />
<InsertRowLeftOutlined />
<InsertRowRightOutlined />
<InsuranceOutlined />
<InteractionOutlined />
<KeyOutlined />
<LaptopOutlined />
<LayoutOutlined />
<LikeOutlined />
<LineOutlined />
<LinkOutlined />
<Loading3QuartersOutlined />
<LoadingOutlined />
<LockOutlined />
<MailOutlined />
<ManOutlined />
<MedicineBoxOutlined />
<MehOutlined />
<MenuOutlined />
<MergeCellsOutlined />
<MessageOutlined />
<MobileOutlined />
<MoneyCollectOutlined />
<MonitorOutlined />
<MoreOutlined />
<NodeCollapseOutlined />
<NodeExpandOutlined />
<NodeIndexOutlined />
<NotificationOutlined />
<NumberOutlined />
<PaperClipOutlined />
<PartitionOutlined />
<PayCircleOutlined />
<PercentageOutlined />
<PhoneOutlined />
<PictureOutlined />
<PoundCircleOutlined />
<PoundOutlined />
<PoweroffOutlined />
<PrinterOutlined />
<ProfileOutlined />
<ProjectOutlined />
<PropertySafetyOutlined />
<PullRequestOutlined />
<PushpinOutlined />
<QrcodeOutlined />
<ReadOutlined />
<ReconciliationOutlined />
<RedEnvelopeOutlined />
<ReloadOutlined />
<RestOutlined />
<RobotOutlined />
<RocketOutlined />
<SafetyCertificateOutlined />
<SafetyOutlined />
<ScanOutlined />
<ScheduleOutlined />
<SearchOutlined />
<SecurityScanOutlined />
<SelectOutlined />
<SendOutlined />
<SettingOutlined />
<ShakeOutlined />
<ShareAltOutlined />
<ShopOutlined />
<ShoppingCartOutlined />
<ShoppingOutlined />
<SisternodeOutlined />
<SkinOutlined />
<SmileOutlined />
<SolutionOutlined />
<SoundOutlined />
<SplitCellsOutlined />
<StarOutlined />
<SubnodeOutlined />
<SyncOutlined />
<TableOutlined />
<TabletOutlined />
<TagOutlined />
<TagsOutlined />
<TeamOutlined />
<ThunderboltOutlined />
<ToTopOutlined />
<ToolOutlined />
<TrademarkCircleOutlined />
<TrademarkOutlined />
<TransactionOutlined />
<TrophyOutlined />
<UngroupOutlined />
<UnlockOutlined />
<UploadOutlined />
<UsbOutlined />
<UserAddOutlined />
<UserDeleteOutlined />
<UserOutlined />
<UserSwitchOutlined />
<UsergroupAddOutlined />
<UsergroupDeleteOutlined />
<VideoCameraOutlined />
<WalletOutlined />
<WifiOutlined />
<BorderlessTableOutlined />
<WomanOutlined />
<BehanceOutlined />
<DropboxOutlined />
<DeploymentUnitOutlined />
<UpCircleOutlined />
<DownCircleOutlined />
<LeftCircleOutlined />
<RightCircleOutlined />
<UpSquareOutlined />
<DownSquareOutlined />
<LeftSquareOutlined />
<RightSquareOutlined />
<PlayCircleOutlined />
<QuestionCircleOutlined />
<PlusCircleOutlined />
<PlusSquareOutlined />
<MinusSquareOutlined />
<MinusCircleOutlined />
<InfoCircleOutlined />
<ExclamationCircleOutlined />
<CloseCircleOutlined />
<CloseSquareOutlined />
<CheckCircleOutlined />
<CheckSquareOutlined />
<ClockCircleOutlined />
<FormOutlined />
<DashOutlined />
<SmallDashOutlined />
<YoutubeOutlined />
<CodepenCircleOutlined />
<AliyunOutlined />
<PlusOutlined />
<LinkedinOutlined />
<AimOutlined />
<BugOutlined />
<CloudDownloadOutlined />
<CloudServerOutlined />
<CloudSyncOutlined />
<CloudUploadOutlined />
<CommentOutlined />
<ConsoleSqlOutlined />
<EyeInvisibleOutlined />
<FileGifOutlined />
<DeliveredProcedureOutlined />
<FieldTimeOutlined />
<FileZipOutlined />
<FolderViewOutlined />
<FundProjectionScreenOutlined />
<FundViewOutlined />
<MacCommandOutlined />
<PlaySquareOutlined />
<OneToOneOutlined />
<RotateLeftOutlined />
<RotateRightOutlined />
<SaveOutlined />
<SwitcherOutlined />
<TranslationOutlined />
<VerifiedOutlined />
<VideoCameraAddOutlined />
<WhatsAppOutlined />
{/*</Col>*/}
</Row>
);
}
Example #22
Source File: EnabledPluginsSection.tsx From posthog-foss with MIT License | 4 votes |
export function EnabledPluginSection(): JSX.Element {
const { user } = useValues(userLogic)
const {
rearrange,
setTemporaryOrder,
cancelRearranging,
savePluginOrders,
makePluginOrderSaveable,
toggleSectionOpen,
} = useActions(pluginsLogic)
const {
enabledPlugins,
filteredEnabledPlugins,
sortableEnabledPlugins,
unsortableEnabledPlugins,
rearranging,
loading,
temporaryOrder,
pluginOrderSaveable,
searchTerm,
sectionsOpen,
} = useValues(pluginsLogic)
const canRearrange: boolean = canConfigurePlugins(user?.organization) && sortableEnabledPlugins.length > 1
const rearrangingButtons = rearranging ? (
<>
<Button
type="primary"
icon={<SaveOutlined />}
loading={loading}
onClick={(e) => {
e.stopPropagation()
savePluginOrders(temporaryOrder)
}}
disabled={!pluginOrderSaveable}
>
Save order
</Button>
<Button
type="default"
icon={<CloseOutlined />}
onClick={(e) => {
cancelRearranging()
e.stopPropagation()
}}
>
Cancel
</Button>
</>
) : (
<Tooltip
title={
enabledPlugins.length <= 1 ? (
'At least two plugins need to be enabled for reordering.'
) : (
<>
{!!searchTerm ? (
'Editing the order of plugins is disabled when searching.'
) : (
<>
Order matters because event processing with plugins works like a pipe: the event is
processed by every enabled plugin <b>in sequence</b>.
</>
)}
</>
)
}
placement="top"
>
<Button
icon={<OrderedListOutlined />}
onClick={(e) => {
e.stopPropagation()
rearrange()
}}
disabled={!!searchTerm || sortableEnabledPlugins.length <= 1}
>
Edit order
</Button>
</Tooltip>
)
const onSortEnd = ({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }): void => {
if (oldIndex === newIndex) {
return
}
const move = (arr: PluginTypeWithConfig[], from: number, to: number): { id: number; order: number }[] => {
const clone = [...arr]
Array.prototype.splice.call(clone, to, 0, Array.prototype.splice.call(clone, from, 1)[0])
return clone.map(({ id }, order) => ({ id, order: order + 1 }))
}
const movedPluginId: number = enabledPlugins[oldIndex]?.id
const newTemporaryOrder: Record<number, number> = {}
for (const { id, order } of move(enabledPlugins, oldIndex, newIndex)) {
newTemporaryOrder[id] = order
}
if (!rearranging) {
rearrange()
}
setTemporaryOrder(newTemporaryOrder, movedPluginId)
}
const EnabledPluginsHeader = (): JSX.Element => (
<div className="plugins-installed-tab-section-header" onClick={() => toggleSectionOpen(PluginSection.Enabled)}>
<Subtitle
subtitle={
<>
{sectionsOpen.includes(PluginSection.Enabled) ? <CaretDownOutlined /> : <CaretRightOutlined />}
{` Enabled plugins (${filteredEnabledPlugins.length})`}
{rearranging && sectionsOpen.includes(PluginSection.Enabled) && (
<Tag color="red" style={{ fontWeight: 'normal', marginLeft: 10 }}>
Reordering in progress
</Tag>
)}
</>
}
buttons={<Space>{sectionsOpen.includes(PluginSection.Enabled) && rearrangingButtons}</Space>}
/>
</div>
)
if (enabledPlugins.length === 0) {
return (
<>
<EnabledPluginsHeader />
{sectionsOpen.includes(PluginSection.Enabled) && <p style={{ margin: 10 }}>No plugins enabled.</p>}
</>
)
}
return (
<>
<EnabledPluginsHeader />
{sectionsOpen.includes(PluginSection.Enabled) && (
<>
{sortableEnabledPlugins.length === 0 && unsortableEnabledPlugins.length === 0 && (
<p style={{ margin: 10 }}>No plugins match your search.</p>
)}
{canRearrange || rearranging ? (
<>
{sortableEnabledPlugins.length > 0 && (
<>
<SortablePlugins
useDragHandle
onSortEnd={onSortEnd}
onSortOver={makePluginOrderSaveable}
>
{sortableEnabledPlugins.map((plugin, index) => (
<SortablePlugin
key={plugin.id}
plugin={plugin}
index={index}
order={index + 1}
maxOrder={enabledPlugins.length}
rearranging={rearranging}
/>
))}
</SortablePlugins>
</>
)}
</>
) : (
<Row gutter={16} style={{ marginTop: 16 }}>
{sortableEnabledPlugins.length > 0 && (
<>
{sortableEnabledPlugins.map((plugin, index) => (
<InstalledPlugin
key={plugin.id}
plugin={plugin}
order={index + 1}
maxOrder={filteredEnabledPlugins.length}
/>
))}
</>
)}
</Row>
)}
{unsortableEnabledPlugins.map((plugin) => (
<InstalledPlugin
key={plugin.id}
plugin={plugin}
maxOrder={enabledPlugins.length}
rearranging={rearranging}
unorderedPlugin
/>
))}
</>
)}
</>
)
}
Example #23
Source File: NewPropertyComponent.tsx From posthog-foss with MIT License | 4 votes |
NewPropertyComponent = (): JSX.Element => {
const initialState = { creating: false, propertyType: 'string' } as NewPropertyInterface
const [state, setState] = useState(initialState)
const { editProperty } = useActions(personsLogic)
const saveProperty = (): void => {
if (state.key && state.value !== undefined) {
editProperty(state.key, state.value)
setState(initialState)
}
}
return (
<>
<div className="mb">
<div className="text-right">
<Button
data-attr="add-prop-button"
onClick={() => setState({ ...state, creating: true })}
type="primary"
icon={<PlusOutlined />}
>
New property
</Button>
</div>
</div>
<Modal
visible={state.creating}
destroyOnClose
onCancel={() => setState(initialState)}
title="Adding new property"
okText={
<>
<SaveOutlined style={{ marginRight: 4 }} />
Save Property
</>
}
okButtonProps={{ disabled: !state.key || state.value === undefined }}
onOk={saveProperty}
>
<div className="input-set">
<label htmlFor="propertyKey">Key</label>
<Input
id="propertyKey"
autoFocus
placeholder="try email, first_name, is_verified, membership_level, total_revenue"
onChange={(e) => setState({ ...state, key: e.target.value })}
/>
</div>
<div className="input-set">
<label htmlFor="propertyType">Type of Property</label>
<div>
<Radio.Group
value={state.propertyType}
onChange={(e) =>
setState({
...state,
propertyType: e.target.value,
value: e.target.value === 'string' ? undefined : 'true',
})
}
id="propertyType"
buttonStyle="solid"
>
<Radio.Button value="string">Text or Number</Radio.Button>
<Radio.Button value="boolean">Boolean or Null</Radio.Button>
</Radio.Group>
</div>
</div>
<div className="input-set">
<label htmlFor="propertyValue">Value</label>
{state.propertyType === 'boolean' ? (
<div>
<Radio.Group
value={state.value}
onChange={(e) =>
setState({
...state,
value: e.target.value,
})
}
id="propertyValue"
buttonStyle="solid"
>
<Radio.Button value="true" defaultChecked>
<CheckOutlined /> True
</Radio.Button>
<Radio.Button value="false">
<CloseOutlined /> False
</Radio.Button>
<Radio.Button value="null">
<StopOutlined /> Null
</Radio.Button>
</Radio.Group>
</div>
) : (
<Input
placeholder="try [email protected], gold, 1"
onChange={(e) => setState({ ...state, value: e.target.value })}
id="propertyValue"
onKeyDown={(e) => e.key === 'Enter' && saveProperty()}
/>
)}
</div>
</Modal>
</>
)
}
Example #24
Source File: FeatureFlag.tsx From posthog-foss with MIT License | 4 votes |
export function FeatureFlag(): JSX.Element {
const [form] = Form.useForm()
const {
featureFlag,
featureFlagId,
multivariateEnabled,
variants,
nonEmptyVariants,
areVariantRolloutsValid,
variantRolloutSum,
groupTypes,
aggregationTargetName,
taxonomicGroupTypes,
} = useValues(featureFlagLogic)
const {
addConditionSet,
updateConditionSet,
removeConditionSet,
duplicateConditionSet,
saveFeatureFlag,
deleteFeatureFlag,
setMultivariateEnabled,
addVariant,
updateVariant,
removeVariant,
distributeVariantsEqually,
setFeatureFlag,
setAggregationGroupTypeIndex,
} = useActions(featureFlagLogic)
const { showGroupsOptions, aggregationLabel } = useValues(groupsModel)
const { hasAvailableFeature, upgradeLink } = useValues(userLogic)
// whether the key for an existing flag is being changed
const [hasKeyChanged, setHasKeyChanged] = useState(false)
// whether to warn the user that their variants will be lost
const [showVariantDiscardWarning, setShowVariantDiscardWarning] = useState(false)
useEffect(() => {
form.setFieldsValue({ ...featureFlag })
}, [featureFlag])
// :KLUDGE: Match by select only allows Select.Option as children, so render groups option directly rather than as a child
const matchByGroupsIntroductionOption = GroupsIntroductionOption({ value: -2 })
return (
<div className="feature-flag">
{featureFlag ? (
<Form
layout="vertical"
form={form}
initialValues={{ name: featureFlag.name, key: featureFlag.key, active: featureFlag.active }}
onValuesChange={(newValues) => {
if (featureFlagId !== 'new' && newValues.key) {
setHasKeyChanged(newValues.key !== featureFlag.key)
}
setFeatureFlag({ ...featureFlag, ...newValues })
}}
onFinish={(values) =>
saveFeatureFlag({
...featureFlag,
...values,
filters: featureFlag.filters,
})
}
requiredMark={false}
scrollToFirstError
>
<PageHeader
title="Feature Flag"
buttons={
<div style={{ display: 'flex' }}>
<Form.Item className="enabled-switch">
<Form.Item
shouldUpdate={(prevValues, currentValues) =>
prevValues.active !== currentValues.active
}
style={{ marginBottom: 0, marginRight: 6 }}
>
{({ getFieldValue }) => {
return (
<span className="ant-form-item-label" style={{ lineHeight: '1.5rem' }}>
{getFieldValue('active') ? (
<span className="text-success">Enabled</span>
) : (
<span className="text-danger">Disabled</span>
)}
</span>
)
}}
</Form.Item>
<Form.Item name="active" noStyle valuePropName="checked">
<Switch />
</Form.Item>
</Form.Item>
{featureFlagId !== 'new' && (
<Button
data-attr="delete-flag"
danger
icon={<DeleteOutlined />}
onClick={() => {
deleteFeatureFlag(featureFlag)
}}
style={{ marginRight: 16 }}
>
Delete
</Button>
)}
<Button
icon={<SaveOutlined />}
type="primary"
data-attr="feature-flag-submit"
htmlType="submit"
>
Save changes
</Button>
</div>
}
/>
<h3 className="l3">General configuration</h3>
<div className="text-muted mb">
General settings for your feature flag and integration instructions.
</div>
<Row gutter={16} style={{ marginBottom: 32 }}>
<Col span={12}>
<Form.Item
name="key"
label="Key (must be unique)"
rules={[
{ required: true, message: 'You need to set a key.' },
{
pattern: /^([A-z]|[a-z]|[0-9]|-|_)+$/,
message: 'Only letters, numbers, hyphens (-) & underscores (_) are allowed.',
},
]}
validateStatus={hasKeyChanged ? 'warning' : undefined}
help={
hasKeyChanged ? (
<small>
<b>Warning! </b>Changing this key will
<a
href={`https://posthog.com/docs/features/feature-flags${UTM_TAGS}#feature-flag-persistence`}
target="_blank"
rel="noopener"
>
{' '}
affect the persistence of your flag <IconOpenInNew />
</a>
</small>
) : undefined
}
>
<Input
data-attr="feature-flag-key"
className="ph-ignore-input"
autoFocus
placeholder="examples: new-landing-page, betaFeature, ab_test_1"
autoComplete="off"
autoCapitalize="off"
autoCorrect="off"
spellCheck={false}
/>
</Form.Item>
<Form.Item name="name" label="Description">
<Input.TextArea
className="ph-ignore-input"
data-attr="feature-flag-description"
placeholder="Adding a helpful description can ensure others know what this feature is for."
/>
</Form.Item>
</Col>
<Col span={12} style={{ paddingTop: 31 }}>
<Collapse>
<Collapse.Panel
header={
<div style={{ display: 'flex', fontWeight: 'bold', alignItems: 'center' }}>
<IconJavascript style={{ marginRight: 6 }} /> Javascript integration
instructions
</div>
}
key="js"
>
<Form.Item
shouldUpdate={(prevValues, currentValues) =>
prevValues.key !== currentValues.key
}
>
{({ getFieldValue }) => <JSSnippet flagKey={getFieldValue('key')} />}
</Form.Item>
</Collapse.Panel>
<Collapse.Panel
header={
<div style={{ display: 'flex', fontWeight: 'bold', alignItems: 'center' }}>
<IconPython style={{ marginRight: 6 }} /> Python integration instructions
</div>
}
key="python"
>
<Form.Item
shouldUpdate={(prevValues, currentValues) =>
prevValues.key !== currentValues.key
}
>
{({ getFieldValue }) => <PythonSnippet flagKey={getFieldValue('key')} />}
</Form.Item>
</Collapse.Panel>
<Collapse.Panel
header={
<div style={{ display: 'flex', fontWeight: 'bold', alignItems: 'center' }}>
<ApiFilled style={{ marginRight: 6 }} /> API integration instructions
</div>
}
key="api"
>
<Form.Item
shouldUpdate={(prevValues, currentValues) =>
prevValues.key !== currentValues.key
}
>
<APISnippet />
</Form.Item>
</Collapse.Panel>
</Collapse>
</Col>
</Row>
<div className="mb-2">
<h3 className="l3">Served value</h3>
<div className="mb-05">
<Popconfirm
placement="top"
title="Change value type? The variants below will be lost."
visible={showVariantDiscardWarning}
onConfirm={() => {
setMultivariateEnabled(false)
setShowVariantDiscardWarning(false)
}}
onCancel={() => setShowVariantDiscardWarning(false)}
okText="OK"
cancelText="Cancel"
>
<Radio.Group
options={[
{
label: 'Boolean value (A/B test)',
value: false,
},
{
label: (
<Tooltip
title={
hasAvailableFeature(AvailableFeature.MULTIVARIATE_FLAGS)
? ''
: 'This feature is not available on your current plan.'
}
>
<div>
{!hasAvailableFeature(AvailableFeature.MULTIVARIATE_FLAGS) && (
<Link to={upgradeLink} target="_blank">
<LockOutlined
style={{ marginRight: 4, color: 'var(--warning)' }}
/>
</Link>
)}
String value (Multivariate test){' '}
<LemonTag type="warning">Beta</LemonTag>
</div>
</Tooltip>
),
value: true,
disabled: !hasAvailableFeature(AvailableFeature.MULTIVARIATE_FLAGS),
},
]}
onChange={(e) => {
const { value } = e.target
if (value === false && nonEmptyVariants.length) {
setShowVariantDiscardWarning(true)
} else {
setMultivariateEnabled(value)
focusVariantKeyField(0)
}
}}
value={multivariateEnabled}
optionType="button"
/>
</Popconfirm>
</div>
<div className="text-muted mb">
{capitalizeFirstLetter(aggregationTargetName)} will be served{' '}
{multivariateEnabled ? (
<>
<strong>a variant key</strong> according to the below distribution
</>
) : (
<strong>
<code>true</code>
</strong>
)}{' '}
if they match one or more release condition groups.
</div>
{multivariateEnabled && (
<div className="variant-form-list">
<Row gutter={8} className="label-row">
<Col span={7}>Variant key</Col>
<Col span={7}>Description</Col>
<Col span={9}>
<span>Rollout percentage</span>
<Button
type="link"
onClick={distributeVariantsEqually}
icon={<MergeCellsOutlined />}
style={{ padding: '0 0 0 0.5em' }}
title="Distribute variants equally"
>
Distribute
</Button>
</Col>
</Row>
{variants.map(({ rollout_percentage }, index) => (
<Form
key={index}
onValuesChange={(changedValues) => updateVariant(index, changedValues)}
initialValues={variants[index]}
validateTrigger={['onChange', 'onBlur']}
>
<Row gutter={8}>
<Col span={7}>
<Form.Item
name="key"
rules={[
{ required: true, message: 'Key should not be empty.' },
{
pattern: /^([A-z]|[a-z]|[0-9]|-|_)+$/,
message:
'Only letters, numbers, hyphens (-) & underscores (_) are allowed.',
},
]}
>
<Input
data-attr="feature-flag-variant-key"
data-key-index={index.toString()}
className="ph-ignore-input"
placeholder={`example-variant-${index + 1}`}
autoComplete="off"
autoCapitalize="off"
autoCorrect="off"
spellCheck={false}
/>
</Form.Item>
</Col>
<Col span={7}>
<Form.Item name="name">
<Input
data-attr="feature-flag-variant-name"
className="ph-ignore-input"
placeholder="Description"
/>
</Form.Item>
</Col>
<Col span={7}>
<Slider
tooltipPlacement="top"
value={rollout_percentage}
onChange={(value: number) =>
updateVariant(index, { rollout_percentage: value })
}
/>
</Col>
<Col span={2}>
<InputNumber
min={0}
max={100}
value={rollout_percentage}
onChange={(value) => {
if (value !== null && value !== undefined) {
const valueInt = parseInt(value.toString())
if (!isNaN(valueInt)) {
updateVariant(index, {
rollout_percentage: valueInt,
})
}
}
}}
style={{
width: '100%',
borderColor: areVariantRolloutsValid
? undefined
: 'var(--danger)',
}}
/>
</Col>
{variants.length > 1 && (
<Col span={1}>
<Tooltip title="Delete this variant" placement="bottomLeft">
<Button
type="link"
icon={<DeleteOutlined />}
onClick={() => removeVariant(index)}
style={{ color: 'var(--danger)' }}
/>
</Tooltip>
</Col>
)}
</Row>
</Form>
))}
{variants.length > 0 && !areVariantRolloutsValid && (
<p className="text-danger">
Percentage rollouts for variants must sum to 100 (currently {variantRolloutSum}
).
</p>
)}
<Button
type="dashed"
block
icon={<PlusOutlined />}
onClick={() => {
const newIndex = variants.length
addVariant()
focusVariantKeyField(newIndex)
}}
style={{ marginBottom: 16 }}
>
Add Variant
</Button>
</div>
)}
</div>
<div className="feature-flag-form-row">
<div>
<h3 className="l3">Release conditions</h3>
<div className="text-muted mb">
Specify the {aggregationTargetName} to which you want to release this flag. Note that
condition sets are rolled out independently of each other.
</div>
</div>
{showGroupsOptions && (
<div className="centered">
Match by
<Select
value={
featureFlag.filters.aggregation_group_type_index != null
? featureFlag.filters.aggregation_group_type_index
: -1
}
onChange={(value) => {
const groupTypeIndex = value !== -1 ? value : null
setAggregationGroupTypeIndex(groupTypeIndex)
}}
style={{ marginLeft: 8 }}
data-attr="feature-flag-aggregation-filter"
dropdownMatchSelectWidth={false}
dropdownAlign={{
// Align this dropdown by the right-hand-side of button
points: ['tr', 'br'],
}}
>
<Select.Option key={-1} value={-1}>
Users
</Select.Option>
{groupTypes.map((groupType) => (
<Select.Option
key={groupType.group_type_index}
value={groupType.group_type_index}
>
{capitalizeFirstLetter(aggregationLabel(groupType.group_type_index).plural)}
</Select.Option>
))}
{matchByGroupsIntroductionOption}
</Select>
</div>
)}
</div>
<Row gutter={16}>
{featureFlag.filters.groups.map((group, index) => (
<Col span={24} md={24} key={`${index}-${featureFlag.filters.groups.length}`}>
{index > 0 && (
<div style={{ display: 'flex', marginLeft: 16 }}>
<div className="stateful-badge or-light-grey mb">OR</div>
</div>
)}
<Card style={{ marginBottom: 16 }}>
<div className="feature-flag-form-row" style={{ height: 24 }}>
<div>
<span className="simple-tag tag-light-blue" style={{ marginRight: 8 }}>
Set {index + 1}
</span>
{group.properties?.length ? (
<>
Matching <b>{aggregationTargetName}</b> with filters
</>
) : (
<>
Condition set will match <b>all {aggregationTargetName}</b>
</>
)}
</div>
<div>
<Tooltip title="Duplicate this condition set" placement="bottomLeft">
<Button
type="link"
icon={<CopyOutlined />}
style={{ width: 24, height: 24 }}
onClick={() => duplicateConditionSet(index)}
/>
</Tooltip>
{featureFlag.filters.groups.length > 1 && (
<Tooltip title="Delete this condition set" placement="bottomLeft">
<Button
type="link"
icon={<DeleteOutlined />}
style={{ width: 24, height: 24 }}
onClick={() => removeConditionSet(index)}
/>
</Tooltip>
)}
</div>
</div>
<LemonSpacer large />
<PropertyFilters
style={{ marginLeft: 15 }}
pageKey={`feature-flag-${featureFlag.id}-${index}-${
featureFlag.filters.groups.length
}-${featureFlag.filters.aggregation_group_type_index ?? ''}`}
propertyFilters={group?.properties}
onChange={(properties) => updateConditionSet(index, undefined, properties)}
taxonomicGroupTypes={taxonomicGroupTypes}
showConditionBadge
greyBadges
/>
<LemonSpacer large />
<div className="feature-flag-form-row">
<div className="centered">
Roll out to{' '}
<InputNumber
style={{ width: 100, marginLeft: 8, marginRight: 8 }}
onChange={(value): void => {
updateConditionSet(index, value as number)
}}
value={
group.rollout_percentage != null ? group.rollout_percentage : 100
}
min={0}
max={100}
addonAfter="%"
/>{' '}
of <b>{aggregationTargetName}</b> in this set
</div>
</div>
</Card>
</Col>
))}
</Row>
<Card size="small" style={{ marginBottom: 16 }}>
<Button type="link" onClick={addConditionSet} style={{ marginLeft: 5 }}>
<PlusOutlined style={{ marginRight: 15 }} /> Add condition set
</Button>
</Card>
<Form.Item className="text-right">
<Button
icon={<SaveOutlined />}
htmlType="submit"
type="primary"
data-attr="feature-flag-submit-bottom"
>
Save changes
</Button>
</Form.Item>
</Form>
) : (
// TODO: This should be skeleton loaders
<SceneLoading />
)}
</div>
)
}
Example #25
Source File: DashboardItem.tsx From posthog-foss with MIT License | 4 votes |
export function DashboardItem({
item,
dashboardId,
receivedErrorFromAPI,
updateItemColor,
setDiveDashboard,
loadDashboardItems,
isDraggingRef,
isReloading,
reload,
dashboardMode,
isOnEditMode,
setEditMode,
index,
layout,
footer,
onClick,
moveDashboardItem,
saveDashboardItem,
duplicateDashboardItem,
isHighlighted = false,
doNotLoad = false,
}: DashboardItemProps): JSX.Element {
const [initialLoaded, setInitialLoaded] = useState(false)
const [showSaveModal, setShowSaveModal] = useState(false)
const { currentTeamId } = useValues(teamLogic)
const { nameSortedDashboards } = useValues(dashboardsModel)
const { renameInsight } = useActions(insightsModel)
const { featureFlags } = useValues(featureFlagLogic)
const _type = getDisplayedType(item.filters)
const insightTypeDisplayName =
item.filters.insight === InsightType.RETENTION
? 'Retention'
: item.filters.insight === InsightType.PATHS
? 'Paths'
: item.filters.insight === InsightType.FUNNELS
? 'Funnel'
: item.filters.insight === InsightType.STICKINESS
? 'Stickiness'
: 'Trends'
const className = displayMap[_type].className
const Element = displayMap[_type].element
const viewText = displayMap[_type].viewText
const link = combineUrl(urls.insightView(item.short_id, item.filters), undefined, {
fromDashboard: item.dashboard,
}).url
const color = item.color || 'white'
const otherDashboards: DashboardType[] = nameSortedDashboards.filter((d: DashboardType) => d.id !== dashboardId)
const getDashboard = (id: number): DashboardType | undefined => nameSortedDashboards.find((d) => d.id === id)
const longPressProps = useLongPress(setEditMode, {
ms: 500,
touch: true,
click: false,
exclude: 'table, table *',
})
const filters = { ...item.filters, from_dashboard: item.dashboard || undefined }
const logicProps: InsightLogicProps = {
dashboardItemId: item.short_id,
filters: filters,
cachedResults: (item as any).result,
doNotLoad,
}
const { insightProps, showTimeoutMessage, showErrorMessage, insight, insightLoading, isLoading } = useValues(
insightLogic(logicProps)
)
const { loadResults } = useActions(insightLogic(logicProps))
const { reportDashboardItemRefreshed } = useActions(eventUsageLogic)
const { areFiltersValid, isValidFunnel, areExclusionFiltersValid } = useValues(funnelLogic(insightProps))
const previousLoading = usePrevious(insightLoading)
const diveDashboard = item.dive_dashboard ? getDashboard(item.dive_dashboard) : null
// if a load is performed and returns that is not the initial load, we refresh dashboard item to update timestamp
useEffect(() => {
if (previousLoading && !insightLoading && !initialLoaded) {
setInitialLoaded(true)
}
}, [insightLoading])
// Empty states that completely replace the graph
const BlockingEmptyState = (() => {
// Insight specific empty states - note order is important here
if (item.filters.insight === InsightType.FUNNELS) {
if (!areFiltersValid) {
return <FunnelSingleStepState />
}
if (!areExclusionFiltersValid) {
return <FunnelInvalidExclusionState />
}
if (!isValidFunnel && !(insightLoading || isLoading)) {
return <InsightEmptyState />
}
}
// Insight agnostic empty states
if (showErrorMessage || receivedErrorFromAPI) {
return <InsightErrorState excludeDetail={true} />
}
if (showTimeoutMessage) {
return <InsightTimeoutState isLoading={isLoading} />
}
// Deprecated insights
if ((item.filters.insight as string) === 'SESSIONS') {
return <InsightDeprecatedState deleteCallback={loadDashboardItems} itemId={item.id} itemName={item.name} />
}
return null
})()
// Empty states that can coexist with the graph (e.g. Loading)
const CoexistingEmptyState = (() => {
if (isLoading || insightLoading || isReloading) {
return <Loading />
}
return null
})()
const response = (
<div
key={item.short_id}
className={`dashboard-item ${item.color || 'white'} di-width-${layout?.w || 0} di-height-${
layout?.h || 0
} ph-no-capture`}
{...longPressProps}
data-attr={'dashboard-item-' + index}
style={{ border: isHighlighted ? '1px solid var(--primary)' : undefined }}
>
{!BlockingEmptyState && CoexistingEmptyState}
<div className={`dashboard-item-container ${className}`}>
<div className="dashboard-item-header" style={{ cursor: isOnEditMode ? 'move' : 'inherit' }}>
<div className="dashboard-item-title" data-attr="dashboard-item-title">
{dashboardMode === DashboardMode.Public ? (
item.name
) : (
<Link
draggable={false}
to={link}
title={item.name}
preventClick
onClick={() => {
if (!isDraggingRef?.current) {
onClick ? onClick() : router.actions.push(link)
}
}}
style={{ fontSize: 16, fontWeight: 500 }}
>
{item.name || `Untitled ${insightTypeDisplayName} Query`}
</Link>
)}
</div>
{dashboardMode !== DashboardMode.Public && (
<div className="dashboard-item-settings">
{saveDashboardItem &&
dashboardMode !== DashboardMode.Internal &&
(!item.saved && item.dashboard ? (
<Link to={'/dashboard/' + item.dashboard}>
<small>View dashboard</small>
</Link>
) : (
<Tooltip title="Save insight">
<SaveOutlined
style={{
cursor: 'pointer',
marginTop: -3,
...(item.saved
? {
background: 'var(--primary)',
color: 'white',
}
: {}),
}}
onClick={() => {
if (item.saved) {
return saveDashboardItem({ ...item, saved: false })
}
if (item.name) {
// If item already has a name we don't have to ask for it again
return saveDashboardItem({ ...item, saved: true })
}
setShowSaveModal(true)
}}
/>
</Tooltip>
))}
{dashboardMode !== DashboardMode.Internal && (
<>
{featureFlags[FEATURE_FLAGS.DIVE_DASHBOARDS] &&
typeof item.dive_dashboard === 'number' && (
<Tooltip title={`Dive to ${diveDashboard?.name || 'connected dashboard'}`}>
<LinkButton
to={dashboardDiveLink(item.dive_dashboard, item.short_id)}
icon={
<span role="img" aria-label="dive" className="anticon">
<DiveIcon />
</span>
}
data-attr="dive-btn-dive"
className="dive-btn dive-btn-dive"
>
Dive
</LinkButton>
</Tooltip>
)}
<Dropdown
overlayStyle={{ minWidth: 240, border: '1px solid var(--primary)' }}
placement="bottomRight"
trigger={['click']}
overlay={
<Menu
data-attr={'dashboard-item-' + index + '-dropdown-menu'}
style={{ padding: '12px 4px' }}
>
<Menu.Item data-attr={'dashboard-item-' + index + '-dropdown-view'}>
<Link to={link}>{viewText}</Link>
</Menu.Item>
<Menu.Item
data-attr={'dashboard-item-' + index + '-dropdown-refresh'}
onClick={() => {
// On dashboards we use custom reloading logic, which updates a
// global "loading 1 out of n" label, and loads 4 items at a time
if (reload) {
reload()
} else {
loadResults(true)
}
reportDashboardItemRefreshed(item)
}}
>
<Tooltip
placement="left"
title={
<i>
Last updated:{' '}
{item.last_refresh
? dayjs(item.last_refresh).fromNow()
: 'recently'}
</i>
}
>
Refresh
</Tooltip>
</Menu.Item>
<Menu.Item
data-attr={'dashboard-item-' + index + '-dropdown-rename'}
onClick={() => renameInsight(item)}
>
Rename
</Menu.Item>
{updateItemColor && (
<Menu.SubMenu
data-attr={'dashboard-item-' + index + '-dropdown-color'}
key="colors"
title="Set color"
>
{Object.entries(dashboardColorNames).map(
([itemClassName, itemColor], colorIndex) => (
<Menu.Item
key={itemClassName}
onClick={() =>
updateItemColor(item.id, itemClassName)
}
data-attr={
'dashboard-item-' +
index +
'-dropdown-color-' +
colorIndex
}
>
<span
style={{
background: dashboardColors[itemClassName],
border: '1px solid #eee',
display: 'inline-block',
width: 13,
height: 13,
verticalAlign: 'middle',
marginRight: 5,
marginBottom: 1,
}}
/>
{itemColor}
</Menu.Item>
)
)}
</Menu.SubMenu>
)}
{featureFlags[FEATURE_FLAGS.DIVE_DASHBOARDS] && setDiveDashboard && (
<Menu.SubMenu
data-attr={'dashboard-item-' + index + '-dive-dashboard'}
key="dive"
title={`Set dive dashboard`}
>
{otherDashboards.map((dashboard, diveIndex) => (
<Menu.Item
data-attr={
'dashboard-item-' +
index +
'-dive-dashboard-' +
diveIndex
}
key={dashboard.id}
onClick={() => setDiveDashboard(item.id, dashboard.id)}
disabled={dashboard.id === item.dive_dashboard}
>
{dashboard.name}
</Menu.Item>
))}
<Menu.Item
data-attr={
'dashboard-item-' + index + '-dive-dashboard-remove'
}
key="remove"
onClick={() => setDiveDashboard(item.id, null)}
className="text-danger"
>
Remove
</Menu.Item>
</Menu.SubMenu>
)}
{duplicateDashboardItem && otherDashboards.length > 0 && (
<Menu.SubMenu
data-attr={'dashboard-item-' + index + '-dropdown-copy'}
key="copy"
title="Copy to"
>
{otherDashboards.map((dashboard, copyIndex) => (
<Menu.Item
data-attr={
'dashboard-item-' +
index +
'-dropdown-copy-' +
copyIndex
}
key={dashboard.id}
onClick={() =>
duplicateDashboardItem(item, dashboard.id)
}
>
<span
style={{
background: dashboardColors[className],
border: '1px solid #eee',
display: 'inline-block',
width: 13,
height: 13,
verticalAlign: 'middle',
marginRight: 5,
marginBottom: 1,
}}
/>
{dashboard.name}
</Menu.Item>
))}
</Menu.SubMenu>
)}
{moveDashboardItem &&
(otherDashboards.length > 0 ? (
<Menu.SubMenu
data-attr={'dashboard-item-' + index + '-dropdown-move'}
key="move"
title="Move to"
>
{otherDashboards.map((dashboard, moveIndex) => (
<Menu.Item
data-attr={
'dashboard-item-' +
index +
'-dropdown-move-' +
moveIndex
}
key={dashboard.id}
onClick={() =>
moveDashboardItem(item, dashboard.id)
}
>
{dashboard.name}
</Menu.Item>
))}
</Menu.SubMenu>
) : null)}
{duplicateDashboardItem && (
<Menu.Item
data-attr={'dashboard-item-' + index + '-dropdown-duplicate'}
onClick={() => duplicateDashboardItem(item)}
>
Duplicate
</Menu.Item>
)}
<Menu.Item
data-attr={'dashboard-item-' + index + '-dropdown-delete'}
onClick={() =>
deleteWithUndo({
object: {
id: item.id,
name: item.name,
},
endpoint: `projects/${currentTeamId}/insights`,
callback: loadDashboardItems,
})
}
className="text-danger"
>
Delete
</Menu.Item>
</Menu>
}
>
<span
data-attr={'dashboard-item-' + index + '-dropdown'}
style={{ cursor: 'pointer', marginTop: -3 }}
>
<EllipsisOutlined />
</span>
</Dropdown>
</>
)}
</div>
)}
</div>
{item.description && (
<div style={{ padding: '0 16px', marginBottom: 16, fontSize: 12 }}>{item.description}</div>
)}
<div className={`dashboard-item-content ${_type}`} onClickCapture={onClick}>
{!!BlockingEmptyState ? (
BlockingEmptyState
) : (
<Alert.ErrorBoundary message="Error rendering graph!">
{dashboardMode === DashboardMode.Public && !insight.result && !item.result ? (
<Skeleton />
) : (
<Element
dashboardItemId={item.short_id}
cachedResults={item.result}
filters={filters}
color={color}
theme={color === 'white' ? 'light' : 'dark'}
inSharedMode={dashboardMode === DashboardMode.Public}
/>
)}
</Alert.ErrorBoundary>
)}
</div>
{footer}
</div>
{showSaveModal && saveDashboardItem && (
<SaveModal
title="Save Chart"
prompt="Name of Chart"
textLabel="Name"
textPlaceholder="DAUs Last 14 days"
visible={true}
onCancel={() => {
setShowSaveModal(false)
}}
onSubmit={(text) => {
saveDashboardItem({ ...item, name: text, saved: true })
setShowSaveModal(false)
}}
/>
)}
</div>
)
return (
<BindLogic logic={insightLogic} props={insightProps}>
{response}
</BindLogic>
)
}
Example #26
Source File: ActionEdit.tsx From posthog-foss with MIT License | 4 votes |
export function ActionEdit({ action: loadedAction, id, onSave, temporaryToken }: ActionEditLogicProps): JSX.Element {
const relevantActionEditLogic = actionEditLogic({
id: id,
action: loadedAction,
onSave: (action) => onSave(action),
temporaryToken,
})
const { action, errorActionId, actionCount, actionCountLoading } = useValues(relevantActionEditLogic)
const { setAction, saveAction } = useActions(relevantActionEditLogic)
const { loadActions } = useActions(actionsModel)
const { currentTeam } = useValues(teamLogic)
const [edited, setEdited] = useState(false)
const slackEnabled = currentTeam?.slack_incoming_webhook
function addMatchGroup(): void {
setAction({ ...action, steps: [...(action.steps || []), { isNew: uuid() }] })
}
const addGroup = (
<Button onClick={addMatchGroup} size="small">
Add another match group
</Button>
)
const deleteAction = id ? (
<Button
data-attr="delete-action"
danger
icon={<DeleteOutlined />}
onClick={() => {
deleteWithUndo({
endpoint: api.actions.determineDeleteEndpoint(),
object: action,
callback: () => {
router.actions.push('/events/actions')
loadActions()
},
})
}}
>
Delete
</Button>
) : undefined
return (
<div className="action-edit-container">
<PageHeader title={id ? 'Editing action' : 'Creating action'} buttons={deleteAction} />
<form
onSubmit={(e) => {
e.preventDefault()
saveAction()
}}
>
<div className="input-set">
<label htmlFor="actionName">Action name</label>
<Input
required
placeholder="e.g. user account created, purchase completed, movie watched"
value={action.name}
style={{ maxWidth: 500, display: 'block' }}
onChange={(e) => {
setAction({ ...action, name: e.target.value })
setEdited(e.target.value ? true : false)
}}
data-attr="edit-action-input"
id="actionName"
/>
{id && (
<div>
<span className="text-muted mb-05">
{actionCountLoading && <LoadingOutlined />}
{actionCount !== null && actionCount > -1 && (
<>
This action matches <b>{compactNumber(actionCount)}</b> events in the last 3
months
</>
)}
</span>
</div>
)}
</div>
<div className="match-group-section" style={{ overflow: 'visible' }}>
<h2 className="subtitle">Match groups</h2>
<div>
Your action will be triggered whenever <b>any of your match groups</b> are received.{' '}
<a href="https://posthog.com/docs/features/actions" target="_blank">
<InfoCircleOutlined />
</a>
</div>
<div style={{ textAlign: 'right', marginBottom: 12 }}>{addGroup}</div>
<Row gutter={[24, 24]}>
{action.steps?.map((step, index) => (
<ActionStep
key={step.id || step.isNew}
identifier={String(step.id || step.isNew)}
index={index}
step={step}
actionId={action.id || 0}
isOnlyStep={!!action.steps && action.steps.length === 1}
onDelete={() => {
const identifier = step.id ? 'id' : 'isNew'
setAction({
...action,
steps: action.steps?.filter((s) => s[identifier] !== step[identifier]),
})
setEdited(true)
}}
onChange={(newStep) => {
setAction({
...action,
steps: action.steps?.map((s) =>
(step.id && s.id == step.id) || (step.isNew && s.isNew === step.isNew)
? {
id: step.id,
isNew: step.isNew,
...newStep,
}
: s
),
})
setEdited(true)
}}
/>
))}
<Col span={24} md={12}>
<div className="match-group-add-skeleton" onClick={addMatchGroup}>
<PlusOutlined style={{ fontSize: 28, color: '#666666' }} />
</div>
</Col>
</Row>
</div>
<div>
<div style={{ margin: '1rem 0' }}>
<p>
<input
id="webhook-checkbox"
type="checkbox"
onChange={(e) => {
setAction({ ...action, post_to_slack: e.target.checked })
setEdited(true)
}}
checked={!!action.post_to_slack}
disabled={!slackEnabled}
/>
<label
className={slackEnabled ? '' : 'disabled'}
style={{ marginLeft: '0.5rem', marginBottom: '0.5rem' }}
htmlFor="webhook-checkbox"
>
Post to webhook when this action is triggered.
</label>{' '}
<Link to="/project/settings#webhook">
{slackEnabled ? 'Configure' : 'Enable'} this integration in Setup.
</Link>
</p>
{action.post_to_slack && (
<>
<Input
addonBefore="Message format (optional)"
placeholder="Default: [action.name] triggered by [user.name]"
value={action.slack_message_format}
onChange={(e) => {
setAction({ ...action, slack_message_format: e.target.value })
setEdited(true)
}}
disabled={!slackEnabled || !action.post_to_slack}
data-attr="edit-slack-message-format"
/>
<small>
<a
href="https://posthog.com/docs/integrations/message-formatting/"
target="_blank"
rel="noopener noreferrer"
>
See documentation on how to format webhook messages.
</a>
</small>
</>
)}
</div>
</div>
{errorActionId && (
<p className="text-danger">
Action with this name already exists.{' '}
<a href={urls.action(errorActionId)}>Click here to edit.</a>
</p>
)}
<div className="float-right">
<span data-attr="delete-action-bottom">{deleteAction}</span>
<Button
disabled={!edited}
data-attr="save-action-button"
type="primary"
icon={<SaveOutlined />}
onClick={saveAction}
style={{ marginLeft: 16 }}
>
Save action
</Button>
</div>
</form>
</div>
)
}
Example #27
Source File: palette.tsx From jmix-frontend with Apache License 2.0 | 4 votes |
palette = () =>
<Palette>
<Category name="Text">
<Component name="Formatted Message">
<Variant>
<FormattedMessage />
</Variant>
</Component>
<Component name="Heading">
<Variant name='h1'>
<Typography.Title></Typography.Title>
</Variant>
<Variant name='h2'>
<Typography.Title level = {2}></Typography.Title>
</Variant>
<Variant name='h3'>
<Typography.Title level = {3}></Typography.Title>
</Variant>
<Variant name='h4'>
<Typography.Title level = {4}></Typography.Title>
</Variant>
<Variant name='h5'>
<Typography.Title level = {5}></Typography.Title>
</Variant>
</Component>
<Component name='Text'>
<Variant>
<Typography.Text></Typography.Text>
</Variant>
<Variant name = 'Secondary'>
<Typography.Text type="secondary"></Typography.Text>
</Variant>
<Variant name = 'Success'>
<Typography.Text type="success"></Typography.Text>
</Variant>
<Variant name = 'Warning'>
<Typography.Text type="warning"></Typography.Text>
</Variant>
<Variant name = 'Danger'>
<Typography.Text type="danger"></Typography.Text>
</Variant>
<Variant name = 'Disabled'>
<Typography.Text disabled></Typography.Text>
</Variant>
</Component>
</Category>
<Category name="Layout">
<Component name="Divider">
<Variant>
<Divider />
</Variant>
</Component>
<Component name="Grid">
<Variant name="Simple Row">
<Row></Row>
</Variant>
<Variant name="Two columns">
<Row>
<Col span={12}></Col>
<Col span={12}></Col>
</Row>
</Variant>
<Variant name="Three columns">
<Row>
<Col span={8}></Col>
<Col span={8}></Col>
<Col span={8}></Col>
</Row>
</Variant>
</Component>
<Component name="Space">
<Variant>
<Space />
</Variant>
<Variant name="Small">
<Space size={"small"} />
</Variant>
<Variant name="Large">
<Space size={"large"} />
</Variant>
</Component>
</Category>
<Category name="Controls">
<Component name="Autocomplete">
<Variant>
<AutoComplete placeholder="input here" />
</Variant>
</Component>
<Component name="Button">
<Variant>
<Button></Button>
</Variant>
<Variant name="Primary">
<Button type="primary" ></Button>
</Variant>
<Variant name="Link">
<Button type="link" ></Button>
</Variant>
<Variant name="Dropdown">
<Dropdown
trigger={['click']}
overlay={<Menu>
<Menu.Item>
</Menu.Item>
<Menu.Item>
</Menu.Item>
<Menu.Item>
</Menu.Item>
</Menu>}
>
<Button></Button>
</Dropdown>
</Variant>
</Component>
<Component name="Checkbox">
<Variant>
<Checkbox />
</Variant>
</Component>
<Component name='Switch'>
<Variant>
<Switch />
</Variant>
</Component>
<Component name='Radio Group'>
<Variant>
<Radio.Group>
<Radio value={1}>A</Radio>
<Radio value={2}>B</Radio>
<Radio value={3}>C</Radio>
<Radio value={4}>D</Radio>
</Radio.Group>
</Variant>
<Variant name = 'Button'>
<Radio.Group>
<Radio.Button value={1}>A</Radio.Button>
<Radio.Button value={2}>B</Radio.Button>
<Radio.Button value={3}>C</Radio.Button>
<Radio.Button value={4}>D</Radio.Button>
</Radio.Group>
</Variant>
</Component>
<Component name="DatePicker">
<Variant>
<DatePicker />
</Variant>
<Variant name="Range">
<DatePicker.RangePicker />
</Variant>
</Component>
<Component name="TimePicker">
<Variant>
<TimePicker />
</Variant>
<Variant name="Range">
<TimePicker.RangePicker />
</Variant>
</Component>
<Component name="Input">
<Variant>
<Input />
</Variant>
<Variant name='Number'>
<InputNumber />
</Variant>
</Component>
<Component name='Select'>
<Variant>
<Select defaultValue="1">
<Select.Option value="1">1</Select.Option>
<Select.Option value="2">2</Select.Option>
</Select>
</Variant>
<Variant name='Multiple'>
<Select
defaultValue={["1"]}
mode="multiple"
allowClear
>
<Select.Option value="1">1</Select.Option>
<Select.Option value="2">2</Select.Option>
</Select>
</Variant>
</Component>
<Component name="Link">
<Variant>
<Typography.Link href="" target="_blank">
</Typography.Link>
</Variant>
</Component>
<Component name='Slider'>
<Variant>
<Slider defaultValue={30} />
</Variant>
<Variant name = 'Range'>
<Slider range defaultValue={[20, 50]}/>
</Variant>
</Component>
</Category>
<Category name="Data Display">
<Component name="Field">
<Variant>
<Field
entityName={ENTITY_NAME}
disabled={readOnlyMode}
propertyName=''
formItemProps={{
style: { marginBottom: "12px" }
}}
/>
</Variant>
</Component>
<Component name="Card">
<Variant>
<Card />
</Variant>
<Variant name="With Title">
<Card>
<Card title="Card title">
<p>Card content</p>
</Card>
</Card>
</Variant>
<Variant name="My custom card">
<Card>
<Card title="Card title">
<p>Card content</p>
<Avatar />
</Card>
</Card>
</Variant>
</Component>
<Component name="Tabs">
<Variant>
<Tabs defaultActiveKey="1">
<Tabs.TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</Tabs.TabPane>
<Tabs.TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</Tabs.TabPane>
</Tabs>
</Variant>
<Variant name = "Tab Pane">
<Tabs.TabPane>
</Tabs.TabPane>
</Variant>
</Component>
<Component name="Collapse">
<Variant>
<Collapse defaultActiveKey='1'>
<Collapse.Panel header="This is panel header 1" key="1">
</Collapse.Panel>
<Collapse.Panel header="This is panel header 2" key="2">
</Collapse.Panel>
<Collapse.Panel header="This is panel header 3" key="3">
</Collapse.Panel>
</Collapse>
</Variant>
</Component>
<Component name="Image">
<Variant>
<Image
width={200}
src=""
/>
</Variant>
</Component>
<Component name="Avatar">
<Variant>
<Avatar icon={<UserOutlined />} />
</Variant>
<Variant name="Image">
<Avatar src="https://joeschmoe.io/api/v1/random" />
</Variant>
</Component>
<Component name="Badge">
<Variant>
<Badge count={1}>
</Badge>
</Variant>
</Component>
<Component name="Statistic">
<Variant>
<Statistic title="Title" value={112893} />
</Variant>
</Component>
<Component name="Alert">
<Variant name="Success">
<Alert message="Text" type="success" />
</Variant>
<Variant name="Info">
<Alert message="Text" type="info" />
</Variant>
<Variant name="Warning">
<Alert message="Text" type="warning" />
</Variant>
<Variant name="Error">
<Alert message="Text" type="error" />
</Variant>
</Component>
<Component name='List'>
<Variant>
<List
bordered
dataSource={[]}
renderItem={item => (
<List.Item>
</List.Item>
)}
/>
</Variant>
</Component>
</Category>
<Category name="Icons">
<Component name="Arrow">
<Variant name = 'Up'>
<ArrowUpOutlined />
</Variant>
<Variant name = 'Down'>
<ArrowDownOutlined />
</Variant>
<Variant name = 'Left'>
<ArrowLeftOutlined />
</Variant>
<Variant name = 'Right'>
<ArrowRightOutlined />
</Variant>
</Component>
<Component name = 'Question'>
<Variant>
<QuestionOutlined />
</Variant>
<Variant name = 'Circle'>
<QuestionCircleOutlined />
</Variant>
</Component>
<Component name = 'Plus'>
<Variant>
<PlusOutlined />
</Variant>
<Variant name = 'Circle'>
<PlusCircleOutlined />
</Variant>
</Component>
<Component name = 'Info'>
<Variant>
<InfoOutlined />
</Variant>
<Variant name = 'Circle'>
<InfoCircleOutlined />
</Variant>
</Component>
<Component name = 'Exclamation'>
<Variant>
<ExclamationOutlined />
</Variant>
<Variant name = 'Circle'>
<ExclamationCircleOutlined />
</Variant>
</Component>
<Component name = 'Close'>
<Variant>
<CloseOutlined />
</Variant>
<Variant name = 'Circle'>
<CloseCircleOutlined />
</Variant>
</Component>
<Component name = 'Check'>
<Variant>
<CheckOutlined />
</Variant>
<Variant name = 'Circle'>
<CheckCircleOutlined />
</Variant>
</Component>
<Component name = 'Edit'>
<Variant>
<EditOutlined />
</Variant>
</Component>
<Component name = 'Copy'>
<Variant>
<CopyOutlined />
</Variant>
</Component>
<Component name = 'Delete'>
<Variant>
<DeleteOutlined />
</Variant>
</Component>
<Component name = 'Bars'>
<Variant>
<BarsOutlined />
</Variant>
</Component>
<Component name = 'Bell'>
<Variant>
<BellOutlined />
</Variant>
</Component>
<Component name = 'Clear'>
<Variant>
<ClearOutlined />
</Variant>
</Component>
<Component name = 'Download'>
<Variant>
<DownloadOutlined />
</Variant>
</Component>
<Component name = 'Upload'>
<Variant>
<UploadOutlined />
</Variant>
</Component>
<Component name = 'Sync'>
<Variant>
<SyncOutlined />
</Variant>
</Component>
<Component name = 'Save'>
<Variant>
<SaveOutlined />
</Variant>
</Component>
<Component name = 'Search'>
<Variant>
<SearchOutlined />
</Variant>
</Component>
<Component name = 'Settings'>
<Variant>
<SettingOutlined />
</Variant>
</Component>
<Component name = 'Paperclip'>
<Variant>
<PaperClipOutlined />
</Variant>
</Component>
<Component name = 'Phone'>
<Variant>
<PhoneOutlined />
</Variant>
</Component>
<Component name = 'Mail'>
<Variant>
<MailOutlined />
</Variant>
</Component>
<Component name = 'Home'>
<Variant>
<HomeOutlined />
</Variant>
</Component>
<Component name = 'Contacts'>
<Variant>
<ContactsOutlined />
</Variant>
</Component>
<Component name = 'User'>
<Variant>
<UserOutlined />
</Variant>
<Variant name = 'Add'>
<UserAddOutlined />
</Variant>
<Variant name = 'Remove'>
<UserDeleteOutlined />
</Variant>
</Component>
<Component name = 'Team'>
<Variant>
<TeamOutlined />
</Variant>
</Component>
</Category>
</Palette>