ui: problem_main: support copy ids

This commit is contained in:
undefined committed 2025-06-22 04:19:31 +08:00
1 parent 77482145a9
commit 19471c3f70
2 files changed
+18 -21

No files matched your search

@@ -38,17 +38,13 @@ export interface AutoCompleteProps<Item> {
export interface AutoCompleteHandle<Item> { export interface AutoCompleteHandle<Item> {
getSelectedItems: () => Item[]; getSelectedItems: () => Item[];
getSelectedItemKeys: () => string[]; getSelectedItemKeys: () => string[];
getSelectedItemsAsString: () => string;
setSelectedItems: (items: Item[]) => void; setSelectedItems: (items: Item[]) => void;
getQuery: () => string; getQuery: () => string;
setQuery: (query: string) => void; setQuery: (query: string) => void;
setSelectedKeys: (keys: string[]) => void; setSelectedKeys: (keys: string[]) => void;
triggerQuery: () => any; triggerQuery: () => any;
closeList: () => void; closeList: () => void;
/** returns comma seperated value text */
getValue: () => string; getValue: () => string;
/** return value detail */
getValueArray: () => Item[];
clear: () => void; clear: () => void;
focus: () => void; focus: () => void;
} }
@@ -94,7 +90,6 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
const freeSoloConverter = freeSolo ? props.freeSoloConverter ?? ((i) => i) : ((i) => i); const freeSoloConverter = freeSolo ? props.freeSoloConverter ?? ((i) => i) : ((i) => i);
const [focused, setFocused] = useState(false); // is focused const [focused, setFocused] = useState(false); // is focused
const [selected, setSelected] = useState([]); // selected items
const [selectedKeys, setSelectedKeys] = useState(props.selectedKeys || []); // keys of selected items const [selectedKeys, setSelectedKeys] = useState(props.selectedKeys || []); // keys of selected items
const [itemList, setItemList] = useState([]); // items list const [itemList, setItemList] = useState([]); // items list
const [currentItem, setCurrentItem] = useState(null); // index of current item (in item list) const [currentItem, setCurrentItem] = useState(null); // index of current item (in item list)
@@ -152,24 +147,17 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
if (multi) { if (multi) {
const idx = selectedKeys.indexOf(key); const idx = selectedKeys.indexOf(key);
if (idx !== -1) { if (idx !== -1) {
setSelected((s) => {
const newSelected = [...s];
newSelected.splice(idx, 1);
return newSelected;
});
setSelectedKeys((s) => { setSelectedKeys((s) => {
const newSelectedKeys = [...s]; const newSelectedKeys = [...s];
newSelectedKeys.splice(idx, 1); newSelectedKeys.splice(idx, 1);
return newSelectedKeys; return newSelectedKeys;
}); });
} else { } else {
setSelected((s) => [...s, item]);
setSelectedKeys((s) => [...s, key]); setSelectedKeys((s) => [...s, key]);
} }
if (!preserve) inputRef.current.value = ''; if (!preserve) inputRef.current.value = '';
inputRef.current.focus(); inputRef.current.focus();
} else { } else {
setSelected([item]);
setSelectedKeys([key]); setSelectedKeys([key]);
inputRef.current.value = key; inputRef.current.value = key;
} }
@@ -201,7 +189,6 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
if (key === 'Backspace') { if (key === 'Backspace') {
if (target.value.length) return; if (target.value.length) return;
if (selectedKeys.length) { if (selectedKeys.length) {
setSelected((s) => s.slice(0, -1));
setSelectedKeys((s) => s.slice(0, -1)); setSelectedKeys((s) => s.slice(0, -1));
} }
return; return;
@@ -229,28 +216,24 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
}; };
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
getSelectedItems: () => selected, getSelectedItems: () => selectedKeys.map((key) => valueCache[key]),
getSelectedItemKeys: () => [...selectedKeys, inputRef.current?.value].filter((v) => v?.trim().length), getSelectedItemKeys: () => [...selectedKeys, inputRef.current?.value].filter((v) => v?.trim().length),
getSelectedItemsAsString: () => [...selectedKeys, inputRef.current?.value].filter((v) => v?.trim().length).join(','),
setSelectedItems: (items) => { setSelectedItems: (items) => {
setSelected(items);
setSelectedKeys(items.map((i) => itemKey(i))); setSelectedKeys(items.map((i) => itemKey(i)));
if (!multi && inputRef.current) inputRef.current.value = items.map((i) => itemKey(i)).join(','); if (!multi && inputRef.current) inputRef.current.value = items.map((i) => itemKey(i)).join(',');
}, },
setSelectedKeys,
getQuery: () => inputRef.current?.value, getQuery: () => inputRef.current?.value,
setQuery: (query) => { setQuery: (query) => {
if (inputRef.current) inputRef.current.value = query; if (inputRef.current) inputRef.current.value = query;
}, },
setSelectedKeys: (keys) => setSelectedKeys(keys),
triggerQuery: () => queryList(inputRef.current?.value), triggerQuery: () => queryList(inputRef.current?.value),
closeList: () => { closeList: () => {
setItemList([]); setItemList([]);
setCurrentItem(null); setCurrentItem(null);
}, },
getValue: () => (multi ? selectedKeys.join(',') : (inputRef.current.value ?? '')), getValue: () => (multi ? selectedKeys.join(',') : (inputRef.current.value ?? '')),
getValueArray: () => (multi ? selected : [inputRef.current?.value].filter((i) => !!i)),
clear: () => { clear: () => {
setSelected([]);
setSelectedKeys([]); setSelectedKeys([]);
if (inputRef.current) inputRef.current.value = ''; if (inputRef.current) inputRef.current.value = '';
}, },
@@ -258,7 +241,7 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
setFocused(true); setFocused(true);
inputRef.current?.focus(); inputRef.current?.focus();
}, },
}), [selected, selectedKeys, inputRef, multi]); }), [selectedKeys, inputRef, multi]);
const move = (dragId: string, hoverId: string) => { const move = (dragId: string, hoverId: string) => {
if (dragId === hoverId || !draggable) return; if (dragId === hoverId || !draggable) return;
@@ -309,7 +292,6 @@ const AutoComplete = forwardRef(function Impl<T>(props: AutoCompleteProps<T>, re
if (!ids.length) return; if (!ids.length) return;
const fetched = await props.fetchItems(ids); const fetched = await props.fetchItems(ids);
for (const item of fetched) valueCache[itemKey(item)] = item; for (const item of fetched) valueCache[itemKey(item)] = item;
setSelected([...selected, ...fetched]);
setSelectedKeys([...selectedKeys, ...fetched.map((val) => itemKey(val))]); setSelectedKeys([...selectedKeys, ...fetched.map((val) => itemKey(val))]);
}} }}
placeholder={props.placeholder} placeholder={props.placeholder}
@@ -320,6 +320,18 @@ function ProblemSelectionDisplay(props) { // eslint-disable-line
(window as any).__setPids = (newIds: string[]) => setPids(newIds); (window as any).__setPids = (newIds: string[]) => setPids(newIds);
}, [pids]); }, [pids]);
const problemSelectAutoCompleteRef = React.useRef(null);
const copyIds = React.useCallback(async () => {
await navigator.clipboard.writeText(pids.join(','));
Notification.success(i18n('Problem ids copied to clipboard!'));
problemSelectAutoCompleteRef.current.getSelectedItems();
}, [pids]);
const copyPids = React.useCallback(async () => {
const items = problemSelectAutoCompleteRef.current.getSelectedItems();
await navigator.clipboard.writeText(items.map((i) => i.pid || i.docId).join(','));
Notification.success(i18n('Problem ids copied to clipboard!'));
}, [pids]);
const updateCheckboxSelection = React.useCallback(() => { const updateCheckboxSelection = React.useCallback(() => {
for (const i of $('[data-checkbox-group="problem"]:checked')) { for (const i of $('[data-checkbox-group="problem"]:checked')) {
if (!pids.includes(i.closest('tr').dataset.pid)) { if (!pids.includes(i.closest('tr').dataset.pid)) {
@@ -357,6 +369,7 @@ function ProblemSelectionDisplay(props) { // eslint-disable-line
<div className="columns"> <div className="columns">
<ProblemSelectAutoComplete <ProblemSelectAutoComplete
multi multi
ref={problemSelectAutoCompleteRef}
onChange={(v) => setPids(v.split(',').filter((i) => i.trim()))} onChange={(v) => setPids(v.split(',').filter((i) => i.trim()))}
selectedKeys={pids} selectedKeys={pids}
/> />
@@ -367,6 +380,8 @@ function ProblemSelectionDisplay(props) { // eslint-disable-line
<div className="row"> <div className="row">
<div className="columns clearfix"> <div className="columns clearfix">
<div className="float-right dialog__action"> <div className="float-right dialog__action">
<button className="rounded button" onClick={copyIds}>{i18n('Copy IDs')}</button>{' '}
<button className="rounded button" onClick={copyPids}>{i18n('Copy pids')}</button>{' '}
<button className="rounded button" onClick={() => setPids([])}>{i18n('Clear')}</button>{' '} <button className="rounded button" onClick={() => setPids([])}>{i18n('Clear')}</button>{' '}
<button className="primary rounded button" onClick={() => setDialogOpen(false)}>{i18n('Ok')}</button> <button className="primary rounded button" onClick={() => setDialogOpen(false)}>{i18n('Ok')}</button>
</div> </div>