mirror of https://github.com/usememos/memos
chore: remove shortcuts in frontend (#2071)
parent
11abc45440
commit
d1b0b0da10
@ -1,293 +0,0 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
|
||||||
import { toast } from "react-hot-toast";
|
|
||||||
import { getNormalizedTimeString } from "@/helpers/datetime";
|
|
||||||
import { filterConsts, getDefaultFilter, relationConsts } from "@/helpers/filter";
|
|
||||||
import useLoading from "@/hooks/useLoading";
|
|
||||||
import { useShortcutStore, useTagStore } from "@/store/module";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
|
||||||
import { generateDialog } from "./Dialog";
|
|
||||||
import Icon from "./Icon";
|
|
||||||
import Selector from "./kit/Selector";
|
|
||||||
import "@/less/create-shortcut-dialog.less";
|
|
||||||
|
|
||||||
interface Props extends DialogProps {
|
|
||||||
shortcutId?: ShortcutId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
|
|
||||||
const { destroy, shortcutId } = props;
|
|
||||||
const shortcutStore = useShortcutStore();
|
|
||||||
const [title, setTitle] = useState<string>("");
|
|
||||||
const [filters, setFilters] = useState<Filter[]>([]);
|
|
||||||
const requestState = useLoading(false);
|
|
||||||
const t = useTranslate();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (shortcutId) {
|
|
||||||
const shortcutTemp = shortcutStore.getShortcutById(shortcutId);
|
|
||||||
if (shortcutTemp) {
|
|
||||||
setTitle(shortcutTemp.title);
|
|
||||||
const temp = JSON.parse(shortcutTemp.payload);
|
|
||||||
if (Array.isArray(temp)) {
|
|
||||||
setFilters(temp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [shortcutId]);
|
|
||||||
|
|
||||||
const handleTitleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const text = e.target.value as string;
|
|
||||||
setTitle(text);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveBtnClick = async () => {
|
|
||||||
if (!title) {
|
|
||||||
toast.error(t("shortcut-list.title-required"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const filter of filters) {
|
|
||||||
if (!filter.value.value) {
|
|
||||||
toast.error(t("shortcut-list.value-required"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (shortcutId) {
|
|
||||||
await shortcutStore.patchShortcut({
|
|
||||||
id: shortcutId,
|
|
||||||
title,
|
|
||||||
payload: JSON.stringify(filters),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await shortcutStore.createShortcut({
|
|
||||||
title,
|
|
||||||
payload: JSON.stringify(filters),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
toast.error(error.response.data.message);
|
|
||||||
}
|
|
||||||
destroy();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddFilterBtnClick = () => {
|
|
||||||
if (filters.length > 0) {
|
|
||||||
const lastFilter = filters[filters.length - 1];
|
|
||||||
if (lastFilter.value.value === "") {
|
|
||||||
toast(t("shortcut-list.fill-previous"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setFilters([...filters, getDefaultFilter()]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFilterChange = useCallback((index: number, filter: Filter) => {
|
|
||||||
setFilters((filters) => {
|
|
||||||
const temp = [...filters];
|
|
||||||
temp[index] = filter;
|
|
||||||
return temp;
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleFilterRemove = useCallback((index: number) => {
|
|
||||||
setFilters((filters) => {
|
|
||||||
const temp = filters.filter((_, i) => i !== index);
|
|
||||||
return temp;
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="dialog-header-container">
|
|
||||||
<p className="title-text">{shortcutId ? t("shortcut-list.edit-shortcut") : t("shortcut-list.create-shortcut")}</p>
|
|
||||||
<button className="btn close-btn" onClick={destroy}>
|
|
||||||
<Icon.X />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="dialog-content-container">
|
|
||||||
<div className="form-item-container input-form-container">
|
|
||||||
<span className="normal-text">{t("common.title")}</span>
|
|
||||||
<input
|
|
||||||
className="title-input"
|
|
||||||
type="text"
|
|
||||||
placeholder={t("shortcut-list.shortcut-title")}
|
|
||||||
value={title}
|
|
||||||
onChange={handleTitleInputChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-item-container filter-form-container">
|
|
||||||
<span className="normal-text">{t("common.filter")}</span>
|
|
||||||
<div className="filters-wrapper">
|
|
||||||
{filters.map((f, index) => {
|
|
||||||
return (
|
|
||||||
<MemoFilterInputer
|
|
||||||
key={index}
|
|
||||||
index={index}
|
|
||||||
filter={f}
|
|
||||||
handleFilterChange={handleFilterChange}
|
|
||||||
handleFilterRemove={handleFilterRemove}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<div className="create-filter-btn" onClick={handleAddFilterBtnClick}>
|
|
||||||
{t("filter.new-filter")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="dialog-footer-container">
|
|
||||||
<div></div>
|
|
||||||
<div className="btns-container">
|
|
||||||
<button className={`btn-primary ${requestState.isLoading ? "requesting" : ""}`} onClick={handleSaveBtnClick}>
|
|
||||||
{t("common.save")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface MemoFilterInputerProps {
|
|
||||||
index: number;
|
|
||||||
filter: Filter;
|
|
||||||
handleFilterChange: (index: number, filter: Filter) => void;
|
|
||||||
handleFilterRemove: (index: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MemoFilterInputer: React.FC<MemoFilterInputerProps> = (props: MemoFilterInputerProps) => {
|
|
||||||
const { index, filter, handleFilterChange, handleFilterRemove } = props;
|
|
||||||
const t = useTranslate();
|
|
||||||
const tagStore = useTagStore();
|
|
||||||
const [value, setValue] = useState<string>(filter.value.value);
|
|
||||||
const tags = Array.from(tagStore.getState().tags);
|
|
||||||
const { type } = filter;
|
|
||||||
|
|
||||||
const typeDataSource = Object.values(filterConsts).map(({ text, value }) => ({ text: t(text), value }));
|
|
||||||
const operatorDataSource = Object.values(filterConsts[type as FilterType].operators).map(({ text, value }) => ({ text: t(text), value }));
|
|
||||||
const relationDataSource = Object.values(relationConsts).map(({ text, value }) => ({ text: t(text), value }));
|
|
||||||
|
|
||||||
const valueDataSource =
|
|
||||||
type === "TYPE"
|
|
||||||
? filterConsts["TYPE"].values.map(({ text, value }) => ({ text: t(text), value }))
|
|
||||||
: type === "VISIBILITY"
|
|
||||||
? filterConsts["VISIBILITY"].values.map(({ text, value }) => ({ text: t(text), value }))
|
|
||||||
: tags.sort().map((t) => {
|
|
||||||
return { text: t, value: t };
|
|
||||||
});
|
|
||||||
|
|
||||||
const maxDatetimeValue = getNormalizedTimeString("9999-12-31T23:59");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (type === "DISPLAY_TIME") {
|
|
||||||
const nowDatetimeValue = getNormalizedTimeString();
|
|
||||||
handleValueChange(nowDatetimeValue);
|
|
||||||
} else {
|
|
||||||
setValue(filter.value.value);
|
|
||||||
}
|
|
||||||
}, [type]);
|
|
||||||
|
|
||||||
const handleRelationChange = (value: string) => {
|
|
||||||
if (["AND", "OR"].includes(value)) {
|
|
||||||
handleFilterChange(index, {
|
|
||||||
...filter,
|
|
||||||
relation: value as MemoFilterRelation,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTypeChange = (value: string) => {
|
|
||||||
if (filter.type !== value) {
|
|
||||||
const ops = Object.values(filterConsts[value as FilterType].operators);
|
|
||||||
handleFilterChange(index, {
|
|
||||||
...filter,
|
|
||||||
type: value as FilterType,
|
|
||||||
value: {
|
|
||||||
operator: ops[0].value,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOperatorChange = (value: string) => {
|
|
||||||
handleFilterChange(index, {
|
|
||||||
...filter,
|
|
||||||
value: {
|
|
||||||
...filter.value,
|
|
||||||
operator: value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleValueChange = (value: string) => {
|
|
||||||
setValue(value);
|
|
||||||
handleFilterChange(index, {
|
|
||||||
...filter,
|
|
||||||
value: {
|
|
||||||
...filter.value,
|
|
||||||
value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveBtnClick = () => {
|
|
||||||
handleFilterRemove(index);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="memo-filter-input-wrapper">
|
|
||||||
{index > 0 ? (
|
|
||||||
<Selector
|
|
||||||
className="relation-selector"
|
|
||||||
dataSource={relationDataSource}
|
|
||||||
value={filter.relation}
|
|
||||||
handleValueChanged={handleRelationChange}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<Selector className="type-selector" dataSource={typeDataSource} value={filter.type} handleValueChanged={handleTypeChange} />
|
|
||||||
<Selector
|
|
||||||
className="operator-selector"
|
|
||||||
dataSource={operatorDataSource}
|
|
||||||
value={filter.value.operator}
|
|
||||||
handleValueChanged={handleOperatorChange}
|
|
||||||
/>
|
|
||||||
{type === "TEXT" ? (
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="value-inputer"
|
|
||||||
value={value}
|
|
||||||
onChange={(event) => {
|
|
||||||
handleValueChange(event.target.value);
|
|
||||||
}}
|
|
||||||
placeholder={t("filter.text-placeholder")}
|
|
||||||
/>
|
|
||||||
) : type === "DISPLAY_TIME" ? (
|
|
||||||
<input
|
|
||||||
className="datetime-selector"
|
|
||||||
type="datetime-local"
|
|
||||||
value={value}
|
|
||||||
max={maxDatetimeValue}
|
|
||||||
onChange={(event) => {
|
|
||||||
handleValueChange(event.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Selector className="value-selector" dataSource={valueDataSource} value={value} handleValueChanged={handleValueChange} />
|
|
||||||
)}
|
|
||||||
<Icon.X className="remove-btn" onClick={handleRemoveBtnClick} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function showCreateShortcutDialog(shortcutId?: ShortcutId): void {
|
|
||||||
generateDialog(
|
|
||||||
{
|
|
||||||
className: "create-shortcut-dialog",
|
|
||||||
dialogName: "create-shortcut-dialog",
|
|
||||||
},
|
|
||||||
CreateShortcutDialog,
|
|
||||||
{ shortcutId }
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,169 +0,0 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import { toast } from "react-hot-toast";
|
|
||||||
import { getTimeStampByDate } from "@/helpers/datetime";
|
|
||||||
import useLoading from "@/hooks/useLoading";
|
|
||||||
import useToggle from "@/hooks/useToggle";
|
|
||||||
import { useFilterStore, useShortcutStore } from "@/store/module";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
|
||||||
import showCreateShortcutDialog from "./CreateShortcutDialog";
|
|
||||||
import Icon from "./Icon";
|
|
||||||
|
|
||||||
const ShortcutList = () => {
|
|
||||||
const t = useTranslate();
|
|
||||||
const filterStore = useFilterStore();
|
|
||||||
const shortcutStore = useShortcutStore();
|
|
||||||
const filter = filterStore.state;
|
|
||||||
const shortcuts = shortcutStore.state.shortcuts;
|
|
||||||
const loadingState = useLoading();
|
|
||||||
|
|
||||||
const pinnedShortcuts = shortcuts
|
|
||||||
.filter((s) => s.rowStatus === "ARCHIVED")
|
|
||||||
.sort((a, b) => getTimeStampByDate(b.createdTs) - getTimeStampByDate(a.createdTs));
|
|
||||||
const unpinnedShortcuts = shortcuts
|
|
||||||
.filter((s) => s.rowStatus === "NORMAL")
|
|
||||||
.sort((a, b) => getTimeStampByDate(b.createdTs) - getTimeStampByDate(a.createdTs));
|
|
||||||
const sortedShortcuts = pinnedShortcuts.concat(unpinnedShortcuts);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
shortcutStore
|
|
||||||
.getMyAllShortcuts()
|
|
||||||
.catch(() => {
|
|
||||||
// do nth
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loadingState.setFinish();
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col justify-start items-start w-full mt-2 h-auto shrink-0 flex-nowrap hide-scrollbar">
|
|
||||||
<div className="flex flex-row justify-start items-center w-full px-4">
|
|
||||||
<span className="text-sm leading-6 font-mono text-gray-400">{t("common.shortcuts")}</span>
|
|
||||||
<button
|
|
||||||
className="flex flex-col justify-center items-center w-5 h-5 bg-gray-200 dark:bg-zinc-700 rounded ml-2 hover:shadow"
|
|
||||||
onClick={() => showCreateShortcutDialog()}
|
|
||||||
>
|
|
||||||
<Icon.Plus className="w-4 h-4 text-gray-400" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col justify-start items-start relative w-full h-auto flex-nowrap mb-2">
|
|
||||||
{sortedShortcuts.map((s) => {
|
|
||||||
return <ShortcutContainer key={s.id} shortcut={s} isActive={s.id === Number(filter?.shortcutId)} />;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ShortcutContainerProps {
|
|
||||||
shortcut: Shortcut;
|
|
||||||
isActive: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutContainerProps) => {
|
|
||||||
const { shortcut, isActive } = props;
|
|
||||||
const t = useTranslate();
|
|
||||||
const filterStore = useFilterStore();
|
|
||||||
const shortcutStore = useShortcutStore();
|
|
||||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
|
||||||
|
|
||||||
const handleShortcutClick = () => {
|
|
||||||
if (isActive) {
|
|
||||||
filterStore.setMemoShortcut(undefined);
|
|
||||||
} else {
|
|
||||||
filterStore.setMemoShortcut(shortcut.id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteMemoClick = async (event: React.MouseEvent) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
if (showConfirmDeleteBtn) {
|
|
||||||
try {
|
|
||||||
await shortcutStore.deleteShortcutById(shortcut.id);
|
|
||||||
if (filterStore.getState().shortcutId === shortcut.id) {
|
|
||||||
// need clear shortcut filter
|
|
||||||
filterStore.setMemoShortcut(undefined);
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(error);
|
|
||||||
toast.error(error.response.data.message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toggleConfirmDeleteBtn();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditShortcutBtnClick = (event: React.MouseEvent) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
showCreateShortcutDialog(shortcut.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePinShortcutBtnClick = async (event: React.MouseEvent) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const shortcutPatch: ShortcutPatch = {
|
|
||||||
id: shortcut.id,
|
|
||||||
rowStatus: shortcut.rowStatus === "ARCHIVED" ? "NORMAL" : "ARCHIVED",
|
|
||||||
};
|
|
||||||
await shortcutStore.patchShortcut(shortcutPatch);
|
|
||||||
} catch (error) {
|
|
||||||
// do nth
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteBtnMouseLeave = () => {
|
|
||||||
toggleConfirmDeleteBtn(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className="relative group flex flex-row justify-between items-center w-full h-10 py-0 px-4 mt-px first:mt-2 rounded-lg text-base cursor-pointer select-none shrink-0 hover:bg-white dark:hover:bg-zinc-700"
|
|
||||||
onClick={handleShortcutClick}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`flex flex-row justify-start items-center truncate shrink leading-5 mr-1 text-black dark:text-gray-200 ${
|
|
||||||
isActive && "text-green-600"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span className="truncate">{shortcut.title}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex-row justify-end items-center hidden group/btns group-hover:flex shrink-0">
|
|
||||||
<span className="flex flex-row justify-center items-center toggle-btn">
|
|
||||||
<Icon.MoreHorizontal className="w-4 h-auto" />
|
|
||||||
</span>
|
|
||||||
<div className="absolute top-4 right-0 flex-col justify-start items-start w-auto h-auto px-4 pt-3 hidden group-hover/btns:flex z-1">
|
|
||||||
<div className="flex flex-col justify-start items-start w-32 h-auto p-1 whitespace-nowrap rounded-md bg-white dark:bg-zinc-700 shadow">
|
|
||||||
<span
|
|
||||||
className="w-full text-sm leading-6 py-1 px-3 rounded text-left dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-zinc-800"
|
|
||||||
onClick={handlePinShortcutBtnClick}
|
|
||||||
>
|
|
||||||
{shortcut.rowStatus === "ARCHIVED" ? t("common.unpin") : t("common.pin")}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="w-full text-sm leading-6 py-1 px-3 rounded text-left dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-zinc-800"
|
|
||||||
onClick={handleEditShortcutBtnClick}
|
|
||||||
>
|
|
||||||
{t("common.edit")}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`w-full text-sm leading-6 py-1 px-3 rounded text-left dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-zinc-800 text-orange-600 ${
|
|
||||||
showConfirmDeleteBtn && "font-black"
|
|
||||||
}`}
|
|
||||||
onClick={handleDeleteMemoClick}
|
|
||||||
onMouseLeave={handleDeleteBtnMouseLeave}
|
|
||||||
>
|
|
||||||
{t("common.delete")}
|
|
||||||
{showConfirmDeleteBtn ? "!" : ""}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ShortcutList;
|
|
@ -1,101 +0,0 @@
|
|||||||
.create-shortcut-dialog {
|
|
||||||
@apply px-4;
|
|
||||||
|
|
||||||
> .dialog-container {
|
|
||||||
@apply w-180 max-w-full;
|
|
||||||
|
|
||||||
> .dialog-content-container {
|
|
||||||
@apply flex flex-col justify-start items-start;
|
|
||||||
|
|
||||||
> .form-item-container {
|
|
||||||
@apply w-full mt-2 py-1 flex sm:flex-row flex-col justify-start items-start;
|
|
||||||
|
|
||||||
> .normal-text {
|
|
||||||
@apply block shrink-0 w-12 mr-8 sm:text-right text-left text-sm leading-8;
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .title-input {
|
|
||||||
@apply w-full py-1 px-2 h-9 text-sm rounded border dark:border-zinc-700 dark:bg-zinc-800 shadow-inner;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .filters-wrapper {
|
|
||||||
@apply w-full flex flex-col justify-start items-start;
|
|
||||||
|
|
||||||
> .create-filter-btn {
|
|
||||||
@apply text-sm py-1 px-2 rounded shadow flex flex-row sm:justify-start justify-center items-center border dark:border-zinc-700 cursor-pointer text-blue-500 hover:opacity-80 sm:min-w-0 min-w-full sm:mb-0 mb-1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .dialog-footer-container {
|
|
||||||
@apply w-full mt-0 flex flex-row justify-between items-center;
|
|
||||||
|
|
||||||
> .btns-container {
|
|
||||||
@apply flex flex-row justify-start items-center;
|
|
||||||
|
|
||||||
> .tip-text {
|
|
||||||
@apply text-sm text-gray-400 mr-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .btn {
|
|
||||||
@apply text-base px-4 py-1 leading-7 rounded shadow hover:opacity-80;
|
|
||||||
|
|
||||||
&.save-btn {
|
|
||||||
@apply bg-green-600 text-white;
|
|
||||||
|
|
||||||
&.requesting {
|
|
||||||
@apply cursor-wait opacity-80;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.memo-filter-input-wrapper {
|
|
||||||
@apply w-full mb-3 shrink-0 flex flex-row sm:justify-start justify-center items-center sm:flex-nowrap flex-wrap sm:gap-0 gap-3;
|
|
||||||
|
|
||||||
> .selector-wrapper {
|
|
||||||
@apply mr-1 h-9 grow-0 shrink-0 sm:min-w-0 min-w-full;
|
|
||||||
|
|
||||||
&.relation-selector {
|
|
||||||
@apply w-16;
|
|
||||||
@media only screen and (min-width: 640px) {
|
|
||||||
margin-left: -68px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.type-selector {
|
|
||||||
@apply w-1/4;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.operator-selector {
|
|
||||||
@apply w-1/5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.value-selector {
|
|
||||||
@apply grow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> input.value-inputer {
|
|
||||||
@media only screen and (min-width: 640px) {
|
|
||||||
max-width: calc(100% - 152px);
|
|
||||||
}
|
|
||||||
@apply h-9 px-2 shrink-0 grow mr-1 text-sm rounded border bg-transparent hover:bg-gray-50 sm:min-w-0 min-w-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
> input.datetime-selector {
|
|
||||||
@media only screen and (min-width: 640px) {
|
|
||||||
max-width: calc(100% - 152px);
|
|
||||||
}
|
|
||||||
@apply h-9 px-2 shrink-0 grow mr-1 text-sm rounded border bg-transparent sm:min-w-0 min-w-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .remove-btn {
|
|
||||||
@apply w-4 h-auto ml-1 cursor-pointer opacity-60 hover:opacity-80;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
import * as api from "@/helpers/api";
|
|
||||||
import store, { useAppSelector } from "../";
|
|
||||||
import { createShortcut, deleteShortcut, patchShortcut, setShortcuts } from "../reducer/shortcut";
|
|
||||||
|
|
||||||
const convertResponseModelShortcut = (shortcut: Shortcut): Shortcut => {
|
|
||||||
return {
|
|
||||||
...shortcut,
|
|
||||||
createdTs: shortcut.createdTs * 1000,
|
|
||||||
updatedTs: shortcut.updatedTs * 1000,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useShortcutStore = () => {
|
|
||||||
const state = useAppSelector((state) => state.shortcut);
|
|
||||||
return {
|
|
||||||
state,
|
|
||||||
getState: () => {
|
|
||||||
return store.getState().shortcut;
|
|
||||||
},
|
|
||||||
getMyAllShortcuts: async () => {
|
|
||||||
const { data } = await api.getShortcutList();
|
|
||||||
const shortcuts = data.map((s) => convertResponseModelShortcut(s));
|
|
||||||
store.dispatch(setShortcuts(shortcuts));
|
|
||||||
},
|
|
||||||
getShortcutById: (id: ShortcutId) => {
|
|
||||||
for (const s of state.shortcuts) {
|
|
||||||
if (s.id === id) {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
createShortcut: async (shortcutCreate: ShortcutCreate) => {
|
|
||||||
const { data } = await api.createShortcut(shortcutCreate);
|
|
||||||
const shortcut = convertResponseModelShortcut(data);
|
|
||||||
store.dispatch(createShortcut(shortcut));
|
|
||||||
},
|
|
||||||
patchShortcut: async (shortcutPatch: ShortcutPatch) => {
|
|
||||||
const { data } = await api.patchShortcut(shortcutPatch);
|
|
||||||
const shortcut = convertResponseModelShortcut(data);
|
|
||||||
store.dispatch(patchShortcut(shortcut));
|
|
||||||
},
|
|
||||||
deleteShortcutById: async (shortcutId: ShortcutId) => {
|
|
||||||
await api.deleteShortcutById(shortcutId);
|
|
||||||
store.dispatch(deleteShortcut(shortcutId));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,51 +0,0 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
shortcuts: Shortcut[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const shortcutSlice = createSlice({
|
|
||||||
name: "memo",
|
|
||||||
initialState: {
|
|
||||||
shortcuts: [],
|
|
||||||
} as State,
|
|
||||||
reducers: {
|
|
||||||
setShortcuts: (state, action: PayloadAction<Shortcut[]>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
shortcuts: action.payload,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
createShortcut: (state, action: PayloadAction<Shortcut>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
shortcuts: state.shortcuts.concat(action.payload).sort((a, b) => b.createdTs - a.createdTs),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
patchShortcut: (state, action: PayloadAction<Partial<Shortcut>>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
shortcuts: state.shortcuts.map((s) => {
|
|
||||||
if (s.id === action.payload.id) {
|
|
||||||
return {
|
|
||||||
...s,
|
|
||||||
...action.payload,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
deleteShortcut: (state, action: PayloadAction<ShortcutId>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
shortcuts: [...state.shortcuts].filter((shortcut) => shortcut.id !== action.payload),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const { setShortcuts, createShortcut, patchShortcut, deleteShortcut } = shortcutSlice.actions;
|
|
||||||
|
|
||||||
export default shortcutSlice.reducer;
|
|
@ -1,28 +0,0 @@
|
|||||||
type ShortcutId = number;
|
|
||||||
|
|
||||||
interface Shortcut {
|
|
||||||
id: ShortcutId;
|
|
||||||
|
|
||||||
rowStatus: RowStatus;
|
|
||||||
createdTs: TimeStamp;
|
|
||||||
updatedTs: TimeStamp;
|
|
||||||
|
|
||||||
title: string;
|
|
||||||
payload: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ShortcutCreate {
|
|
||||||
title: string;
|
|
||||||
payload: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ShortcutPatch {
|
|
||||||
id: ShortcutId;
|
|
||||||
title?: string;
|
|
||||||
payload?: string;
|
|
||||||
rowStatus?: RowStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ShortcutFind {
|
|
||||||
creatorUsername?: string;
|
|
||||||
}
|
|
Loading…
Reference in New Issue