Skip to content

Commit

Permalink
Implement undo functionality for metadata retrieval
Browse files Browse the repository at this point in the history
  • Loading branch information
tnajdek committed Jan 13, 2025
1 parent 9502ba3 commit 57e679b
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 38 deletions.
16 changes: 14 additions & 2 deletions src/js/actions/current.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { omit } from 'web-common/utils';
import { getApiForItems, splitItemAndCollectionKeys } from '../common/actions';
import { exportItems, chunkedToggleTagsOnItems, chunkedAddToCollection, chunkedCopyToLibrary,
chunkedDeleteItems, chunkedMoveItemsToTrash, chunkedRecoverItemsFromTrash,
chunkedRemoveFromCollection, chunkedUpdateCollectionsTrash, chunkedDeleteCollections, createItem, createItemOfType, toggleModal, navigate, retrieveMetadata } from '.';
chunkedRemoveFromCollection, chunkedUpdateCollectionsTrash, chunkedDeleteCollections, createItem,
createItemOfType, toggleModal, navigate, retrieveMetadata, undoRetrieveMetadata } from '.';
import columnProperties from '../constants/column-properties';
import { BIBLIOGRAPHY, COLLECTION_SELECT, EXPORT, NEW_ITEM } from '../constants/modals';
import { TOGGLE_ADD, TOGGLE_REMOVE } from '../common/tags';
Expand Down Expand Up @@ -267,6 +268,16 @@ const currentRetrieveMetadata = () => {
}
}

const currentUndoRetrieveMetadata = () => {
return async (dispatch, getState) => {
const state = getState();
const { itemKeys: keys, libraryKey } = state.current;
const { itemKeys } = splitItemAndCollectionKeys(keys, libraryKey, state);
const promises = itemKeys.map(key => dispatch(undoRetrieveMetadata(key, libraryKey)));
return await Promise.all(promises);
}
}

export {
currentAddTags,
currentAddToCollection,
Expand All @@ -280,12 +291,13 @@ export {
currentExportItems,
currentExportItemsModal,
currentGoToSubscribeUrl,
currentMoveToTrash,
currentNewItemModal,
currentRecoverFromTrash,
currentRemoveColoredTags,
currentRemoveItemFromCollection,
currentRetrieveMetadata,
currentToggleTagByIndex,
currentMoveToTrash,
currentTrashOrDelete,
currentUndoRetrieveMetadata,
}
8 changes: 5 additions & 3 deletions src/js/actions/items-read.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,14 @@ const sortItems = (sortBy, sortDirection) => {
}
};

const getAttachmentUrl = itemKey => {
const getAttachmentUrl = (itemKey, forceFresh = false) => {
return async (dispatch, getState) => {
const state = getState();
const { libraryKey } = state.current;
const attachmentURLdata = state.libraries[state.current.libraryKey]?.attachmentsUrl[itemKey];

try {
const isURLFresh = (attachmentURLdata && Date.now() - attachmentURLdata.timestamp < 60000);
const isURLFresh = !forceFresh && (attachmentURLdata && Date.now() - attachmentURLdata.timestamp < 60000);
const isPromise = attachmentURLdata?.promise instanceof Promise;
const promise = (isURLFresh && isPromise) ? attachmentURLdata.promise : api(state.config.apiKey, state.config.apiConfig)
.library(libraryKey)
Expand All @@ -229,6 +229,7 @@ const getAttachmentUrl = itemKey => {
type: REQUEST_ATTACHMENT_URL,
libraryKey,
itemKey,
forceFresh,
promise
});

Expand All @@ -239,14 +240,15 @@ const getAttachmentUrl = itemKey => {
type: RECEIVE_ATTACHMENT_URL,
libraryKey,
itemKey,
forceFresh,
url
});

return url;
} catch(error) {
dispatch({
type: ERROR_ATTACHMENT_URL,
libraryKey, itemKey, error,
libraryKey, itemKey, forceFresh, error,
});

throw error;
Expand Down
27 changes: 15 additions & 12 deletions src/js/actions/items-write.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,21 +361,23 @@ const updateItem = (itemKey, patch, libraryKey) => {
}
const id = requestTracker.id++;

dispatch({
type: PRE_UPDATE_ITEM,
itemKey,
libraryKey,
patch,
id
});
return new Promise((resolve, reject) => {
dispatch({
type: PRE_UPDATE_ITEM,
itemKey,
libraryKey,
patch,
id
});

dispatch(
queueUpdateItem(itemKey, patch, libraryKey, id)
);
dispatch(
queueUpdateItem(itemKey, patch, libraryKey, { resolve, reject, id })
);
});
};
}

const queueUpdateItem = (itemKey, patch, libraryKey, id) => {
const queueUpdateItem = (itemKey, patch, libraryKey, { resolve, reject, id }) => {
return {
queue: libraryKey,
callback: async (next, dispatch, getState) => {
Expand Down Expand Up @@ -441,7 +443,7 @@ const queueUpdateItem = (itemKey, patch, libraryKey, id) => {
otherItems: state.libraries[libraryKey].items,
});

return updatedItem;
resolve(updatedItem);
} catch(error) {
dispatch({
type: ERROR_UPDATE_ITEM,
Expand All @@ -451,6 +453,7 @@ const queueUpdateItem = (itemKey, patch, libraryKey, id) => {
patch,
id
});
reject(error);
throw error;
} finally {
next();
Expand Down
60 changes: 53 additions & 7 deletions src/js/actions/recognize.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { createItem, getAttachmentUrl, updateItem } from '.';
import { BEGIN_RECOGNIZE_DOCUMENT, COMPLETE_RECOGNIZE_DOCUMENT, ERROR_RECOGNIZE_DOCUMENT, UPDATE_RECOGNIZE_DOCUMENT } from '../constants/actions';
import { createItem, deleteItem, getAttachmentUrl, updateItem } from '.';
import { BEGIN_RECOGNIZE_DOCUMENT, COMPLETE_RECOGNIZE_DOCUMENT, ERROR_RECOGNIZE_DOCUMENT,
UPDATE_RECOGNIZE_DOCUMENT, BEGIN_UNRECOGNIZE_DOCUMENT, COMPLETE_UNRECOGNIZE_DOCUMENT,
ERROR_UNRECOGNIZE_DOCUMENT, } from '../constants/actions';
import { PDFWorker } from '../common/pdf-worker.js';
import { pick } from 'web-common/utils';

Expand Down Expand Up @@ -65,7 +67,13 @@ const getRecognizerData = itemKey => {
}

const attachmentURL = await dispatch(getAttachmentUrl(itemKey));
const data = await (await fetch(attachmentURL)).arrayBuffer();
let response = await fetch(attachmentURL);
if (response.status === 410) {
const attachmentURL = await dispatch(getAttachmentUrl(itemKey, true));
response = await fetch(attachmentURL);
}

const data = await response.arrayBuffer();
const { pdfWorkerURL, pdfReaderCMapsURL, pdfReaderStandardFontsURL, recognizerUrl } = state.config;
const pdfWorker = new PDFWorker({ pdfWorkerURL, pdfReaderCMapsURL, pdfReaderStandardFontsURL });
const recognizerInputData = await pdfWorker.getRecognizerData(data); // TODO: add suport for password-protected PDFs
Expand All @@ -84,14 +92,14 @@ const getRecognizerData = itemKey => {
}

const url = `${recognizerUrl}/recognize`;
const response = await fetch(url, {
const recognizerResponse = await fetch(url, {
method: 'POST',
mode: 'cors',
headers: { 'content-type': 'application/json', },
body: JSON.stringify(recognizerInputData)
});
if (response.ok) {
return await response.json();
if (recognizerResponse.ok) {
return await recognizerResponse.json();
} else {
throw new Error('Failed to recognize document');
}
Expand Down Expand Up @@ -172,4 +180,42 @@ const recognizePDF = (recognizerData) => {
}
}

export { retrieveMetadata };
const undoRetrieveMetadata = (itemKey, libraryKey) => {
return async (dispatch, getState) => {
const state = getState();
dispatch({
type: BEGIN_UNRECOGNIZE_DOCUMENT,
itemKey,
libraryKey,
});
try {
const originalItemKey = state.recognize.lookup[`${libraryKey}-${itemKey}`];
if(!originalItemKey) {
throw new Error('Original item not found');
}
const item = state.libraries[libraryKey].items?.[itemKey];
if(!item) {
throw new Error('Item not found');
}

const collections = item.collections;
await dispatch(updateItem(originalItemKey, { parentItem: null, collections }, libraryKey));
await dispatch(deleteItem(item));
dispatch({
type: COMPLETE_UNRECOGNIZE_DOCUMENT,
itemKey,
libraryKey,
originalItemKey,
});
} catch (error) {
dispatch({
type: ERROR_UNRECOGNIZE_DOCUMENT,
itemKey,
libraryKey,
error: error?.message ?? 'Failed to undo retrieve metadata'
});
}
}
}

export { retrieveMetadata, undoRetrieveMetadata };
24 changes: 22 additions & 2 deletions src/js/component/item/actions/more-actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem, Icon } from 'web-
import { useDispatch, useSelector } from 'react-redux';

import { cleanDOI, cleanURL, get, getDOIURL } from '../../../utils';
import { currentGoToSubscribeUrl, pickBestItemAction } from '../../../actions';
import { currentGoToSubscribeUrl, currentUndoRetrieveMetadata, pickBestItemAction } from '../../../actions';
import { useItemActionHandlers } from '../../../hooks';
import { READER_CONTENT_TYPES, READER_CONTENT_TYPES_HUMAN_READABLE } from '../../../constants/reader';

Expand All @@ -13,6 +13,10 @@ const MoreActionsItems = ({ divider = false }) => {
const isReadOnly = useSelector(state => (state.config.libraries.find(l => l.key === state.current.libraryKey) || {}).isReadOnly);
const item = useSelector(state => get(state, ['libraries', state.current.libraryKey, 'items', state.current.itemKey]));
const itemsSource = useSelector(state => state.current.itemsSource);
const selectedItemsCanBeUnrecognized = useSelector(state =>
state.current.itemKeys.every(
key => !!state.recognize.lookup[`${state.current.libraryKey}-${key}`]
));
const { handleDuplicate } = useItemActionHandlers();

const attachment = get(item, [Symbol.for('links'), 'attachment'], null);
Expand All @@ -34,6 +38,10 @@ const MoreActionsItems = ({ divider = false }) => {
}
}, [doi, url]);

const handleUnrecognize = useCallback(() => {
dispatch(currentUndoRetrieveMetadata());
}, [dispatch]);

return (
<Fragment>
{ isViewFile && (
Expand All @@ -51,7 +59,15 @@ const MoreActionsItems = ({ divider = false }) => {
Duplicate Item
</DropdownItem>
) }
{ divider && (canDuplicate || isViewFile || isViewOnline) && <DropdownItem divider/> }
{ selectedItemsCanBeUnrecognized && (
<>
{(canDuplicate || isViewFile || isViewOnline) && <DropdownItem divider /> }
<DropdownItem onClick={ handleUnrecognize }>
Undo Retrieve Metadata
</DropdownItem>
</>
) }
{divider && (canDuplicate || isViewFile || isViewOnline || selectedItemsCanBeUnrecognized) && <DropdownItem divider/> }
</Fragment>
);
}
Expand Down Expand Up @@ -112,6 +128,10 @@ const MoreActionsDropdownDesktop = memo(props => {
);
});

MoreActionsItems.propTypes = {
divider: PropTypes.bool,
};

MoreActionsDropdownDesktop.propTypes = {
onFocusNext: PropTypes.func,
onFocusPrev: PropTypes.func,
Expand Down
13 changes: 8 additions & 5 deletions src/js/constants/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@ export const ABORT_REQUEST = 'ABORT_REQUEST';
export const BEGIN_FETCH_ALL_COLLECTIONS = 'BEGIN_FETCH_ALL_COLLECTIONS';
export const BEGIN_FETCH_COLLECTIONS_SINCE = 'BEGIN_FETCH_COLLECTIONS_SINCE';
export const BEGIN_ONGOING = 'BEGIN_ONGOING';
export const BEGIN_RECOGNIZE_DOCUMENT = 'BEGIN_RECOGNIZE_DOCUMENT';
export const BEGIN_SEARCH_MULTIPLE_IDENTIFIERS = 'BEGIN_SEARCH_MULTIPLE_IDENTIFIERS';
export const BEGIN_UNRECOGNIZE_DOCUMENT = 'BEGIN_UNRECOCNIZE_DOCUMENT';
export const CANCEL_UPDATE_LIBRARY_SETTINGS = 'CANCEL_UPDATE_LIBRARY_SETTINGS';
export const CLEAR_ONGOING = 'CLEAR_ONGOING';
export const CLEAR_RECOGNIZE_DOCUMENT = 'CLEAR_RECOGNIZE_DOCUMENT';
export const CLEAR_RECOGNIZE_DOCUMENTS = 'CLEAR_RECOGNIZE_DOCUMENTS';
export const COMPLETE_FETCH_ALL_COLLECTIONS = 'COMPLETE_FETCH_ALL_COLLECTIONS';
export const COMPLETE_FETCH_COLLECTIONS_SINCE = 'COMPLETE_FETCH_COLLECTIONS_SINCE';
export const COMPLETE_ONGOING = 'COMPLETE_ONGOING';
export const COMPLETE_RECOGNIZE_DOCUMENT = 'COMPLETE_RECOGNIZE_DOCUMENT';
export const COMPLETE_SEARCH_MULTIPLE_IDENTIFIERS = 'COMPLETE_SEARCH_MULTIPLE_IDENTIFIERS';
export const COMPLETE_UNRECOGNIZE_DOCUMENT = 'COMPLETE_UNRECOGNIZE_DOCUMENT';
export const CONFIGURE = 'CONFIGURE';
export const CONNECTION_ISSUES = 'CONNECTION_ISSUES';
export const DISMISS_ERROR = 'DISMISS_ERROR';
Expand Down Expand Up @@ -73,6 +79,7 @@ export const ERROR_MOVE_ITEMS_TRASH = 'ERROR_MOVE_ITEMS_TRASH';
export const ERROR_PATCH_ATTACHMENT = 'ERROR_PATCH_ATTACHMENT';
export const ERROR_PROCESSING_ANNOTATIONS = 'ERROR_PROCESSING_ANNOTATIONS';
export const ERROR_PUBLICATIONS_ITEMS = 'ERROR_PUBLICATIONS_ITEMS';
export const ERROR_RECOGNIZE_DOCUMENT = 'ERROR_RECOGNIZE_DOCUMENT';
export const ERROR_RECOVER_ITEMS_TRASH = 'ERROR_RECOVER_ITEMS_TRASH';
export const ERROR_REGISTER_FILE_ATTACHMENTS = 'ERROR_REGISTER_FILE_ATTACHMENTS';
export const ERROR_RELATED_ITEMS = 'ERROR_RELATED_ITEMS';
Expand All @@ -89,6 +96,7 @@ export const ERROR_TAGS_IN_TOP_ITEMS = 'ERROR_TAGS_IN_TOP_ITEMS';
export const ERROR_TAGS_IN_TRASH_ITEMS = 'ERROR_TAGS_IN_TRASH_ITEMS';
export const ERROR_TOP_ITEMS = 'ERROR_TOP_ITEMS';
export const ERROR_TRASH_ITEMS = 'ERROR_TRASH_ITEMS';
export const ERROR_UNRECOGNIZE_DOCUMENT = 'ERROR_UNRECOGNIZE_DOCUMENT';
export const ERROR_UPDATE_COLLECTION = 'ERROR_UPDATE_COLLECTION';
export const ERROR_UPDATE_COLLECTIONS_TRASH ='ERROR_UPDATE_COLLECTIONS_TRASH';
export const ERROR_UPDATE_ITEM = 'ERROR_UPDATE_ITEM';
Expand Down Expand Up @@ -263,9 +271,4 @@ export const TRIGGER_SEARCH_MODE = 'TRIGGER_SEARCH_MODE';
export const TRIGGER_SELECT_MODE = 'TRIGGER_SELECT_MODE';
export const TRIGGER_USER_TYPE_CHANGE = 'TRIGGER_USER_TYPE_CHANGE';
export const TRIGGER_VIEWPORT_CHANGE = 'TRIGGER_VIEWPORT_CHANGE';
export const BEGIN_RECOGNIZE_DOCUMENT = 'BEGIN_RECOGNIZE_DOCUMENT';
export const UPDATE_RECOGNIZE_DOCUMENT = 'UPDATE_RECOGNIZE_DOCUMENT';
export const COMPLETE_RECOGNIZE_DOCUMENT = 'COMPLETE_RECOGNIZE_DOCUMENT';
export const ERROR_RECOGNIZE_DOCUMENT = 'ERROR_RECOGNIZE_DOCUMENT';
export const CLEAR_RECOGNIZE_DOCUMENT = 'CLEAR_RECOGNIZE_DOCUMENT';
export const CLEAR_RECOGNIZE_DOCUMENTS = 'CLEAR_RECOGNIZE_DOCUMENTS';
Loading

0 comments on commit 57e679b

Please sign in to comment.