Skip to content

Commit

Permalink
Add a basic UI to monitor the metadata retrieval process
Browse files Browse the repository at this point in the history
  • Loading branch information
tnajdek committed Jan 8, 2025
1 parent ca53406 commit 5bb0260
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 45 deletions.
1 change: 0 additions & 1 deletion src/js/actions/current.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,6 @@ const currentRetrieveMetadata = () => {
const state = getState();
const { itemKeys: keys, libraryKey } = state.current;
const { itemKeys } = splitItemAndCollectionKeys(keys, libraryKey, state);

const promises = itemKeys.map(key => dispatch(retrieveMetadata(key, libraryKey)));
return await Promise.all(promises);
}
Expand Down
6 changes: 4 additions & 2 deletions src/js/component/modal-manager.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ExportModal from './modal/export';
import IdentifierPicker from './modal/identifier-picker';
import ItemsSortModal from './modal/items-sort';
import ManageTagsModal from './modal/manage-tags';
import metadataRetrieval from './modal/metadata-retrieval';
import MoveCollectionsModal from './modal/move-collections';
import NewCollectionModal from './modal/new-collection';
import NewItemModal from './modal/new-item';
Expand All @@ -18,8 +19,8 @@ import SettingsModal from './modal/settings';
import StyleInstallerModal from './modal/style-installer';

import { ADD_LINKED_URL_TOUCH, BIBLIOGRAPHY, COLLECTION_ADD, COLLECTION_RENAME, COLLECTION_SELECT,
EXPORT, IDENTIFIER_PICKER, MANAGE_TAGS, MOVE_COLLECTION, NEW_ITEM, SETTINGS, SORT_ITEMS,
STYLE_INSTALLER, ADD_BY_IDENTIFIER, } from '../constants/modals';
EXPORT, IDENTIFIER_PICKER, MANAGE_TAGS, METADATA_RETRIEVAL, MOVE_COLLECTION, NEW_ITEM, SETTINGS,
SORT_ITEMS, STYLE_INSTALLER, ADD_BY_IDENTIFIER, } from '../constants/modals';

const lookup = {
[ADD_BY_IDENTIFIER]: AddByIdentifierModal,
Expand All @@ -36,6 +37,7 @@ const lookup = {
[SETTINGS]: SettingsModal,
[SORT_ITEMS]: ItemsSortModal,
[STYLE_INSTALLER]: StyleInstallerModal,
[METADATA_RETRIEVAL]: metadataRetrieval,
};

const UNMOUNT_DELAY = 500; // to allow outro animatons (delay in ms)
Expand Down
104 changes: 104 additions & 0 deletions src/js/component/modal/metadata-retrieval.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import cx from 'classnames';
import { Fragment, useCallback, memo, useEffect } from 'react';
import { Button, Icon } from 'web-common/components';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { usePrevious } from 'web-common/hooks';

import Modal from '../ui/modal';
import { METADATA_RETRIEVAL } from '../../constants/modals';
import { currentRetrieveMetadata, toggleModal } from '../../actions';


const MetadataRetrievalModal = () => {
const dispatch = useDispatch();
const isTouchOrSmall = useSelector(state => state.device.isTouchOrSmall);
const isOpen = useSelector(state => state.modal.id === METADATA_RETRIEVAL);
const wasOpen = usePrevious(isOpen);
const recognizeProgress = useSelector(state => state.recognize.progress);
const recognizeEntries = useSelector(state => state.recognize.entries, shallowEqual);

const handleCancel = useCallback(() => {
dispatch(toggleModal());
}, [dispatch]);

useEffect(() => {
if(isOpen && !wasOpen) {
dispatch(currentRetrieveMetadata());
}
}, [dispatch, isOpen, wasOpen]);

return (
<Modal
className={ "recognize-modal" }
contentLabel="Metadata Retrieval"
isOpen={ isOpen }
onRequestClose={ handleCancel }
overlayClassName={ cx({ 'modal-centered modal-slide': isTouchOrSmall }) }
>
<div className="modal-header">
{
isTouchOrSmall ? (
<Fragment>
<div className="modal-header-center">
<h4 className="modal-title truncate">
Metadata Retrieval
</h4>
</div>
<div className="modal-header-right">
<Button
className="btn-link"
onClick={ handleCancel }
>
Close
</Button>
</div>
</Fragment>
) : (
<Fragment>
<h4 className="modal-title truncate">
Metadata Retrieval
</h4>
<Button
icon
className="close"
onClick={ handleCancel }
>
<Icon type={ '16/close' } width="16" height="16" />
</Button>
</Fragment>
)
}
</div>
<div
className="modal-body"
tabIndex={ !isTouchOrSmall ? 0 : null }
>
<div className="recognize-progress">
<progress value={ recognizeProgress } max="1" />
</div>
<div className="recognize-table">
{recognizeEntries.map(recognize => {
const { itemKey, libraryKey, error, completed, itemTitle, parentItemTitle } = recognize; //error, completed, parentItemKey
const key = `${itemKey}-${libraryKey}`;

return (
<div
key={ key }
className={ cx('recognize-row') }
>
<div className="recognize-row-left">
{itemTitle }
</div>
<div className="recognize-row-right">
{completed ? parentItemTitle : error ? `Error: ${error}` : "Processing" }
</div>
</div>
);
})}
</div>
</div>
</Modal>
);
}

export default memo(MetadataRetrievalModal);
1 change: 1 addition & 0 deletions src/js/constants/modals.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export const IDENTIFIER_PICKER = 'IDENTIFIER_PICKER';
export const MANAGE_TAGS = 'MANAGE_TAGS';
export const EMBEDDED_LIBRARIES_TREE = 'EMBEDDED_LIBRARIES_TREE';
export const SETTINGS = 'SETTINGS';
export const METADATA_RETRIEVAL = 'METADATA_RETRIEVAL';
7 changes: 3 additions & 4 deletions src/js/hooks/use-item-action-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import {
currentAddToCollectionModal, currentCiteModal, currentRemoveItemFromCollection,
currentMoveToTrash, currentDeletePermanently, currentRecoverFromTrash, triggerSelectMode,
currentBibliographyModal, currentDuplicateItem, currentExportItemsModal, currentCreateItemOfType,
currentRetrieveMetadata, navigate, triggerEditingItem, toggleModal, toggleItemsSortingDirection,
currentNewItemModal
navigate, triggerEditingItem, toggleModal, toggleItemsSortingDirection, currentNewItemModal
} from '../actions';
import { SORT_ITEMS, ADD_BY_IDENTIFIER } from '../constants/modals';
import { ADD_BY_IDENTIFIER, METADATA_RETRIEVAL, SORT_ITEMS } from '../constants/modals';

const useItemActionHandlers = () => {
const dispatch = useDispatch();
Expand Down Expand Up @@ -81,7 +80,7 @@ const useItemActionHandlers = () => {
}, [dispatch]);

const handleRetrieveMetadata = useCallback(() => {
dispatch(currentRetrieveMetadata());
dispatch(toggleModal(METADATA_RETRIEVAL, true));
}, [dispatch]);

return {
Expand Down
116 changes: 78 additions & 38 deletions src/js/reducers/recognize.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,95 @@
import { getBaseMappedValue } from '../common/item';
import { BEGIN_RECOGNIZE_DOCUMENT, CLEAR_RECOGNIZE_DOCUMENT, CLEAR_RECOGNIZE_DOCUMENTS, COMPLETE_RECOGNIZE_DOCUMENT,
ERROR_RECOGNIZE_DOCUMENT, UPDATE_RECOGNIZE_DOCUMENT } from '../constants/actions';

const recognize = (state = [], action) => {
const TOTALSTAGES = 4; // 3 progress update stages + 1 for completion

const updateProgressInState = (state) => {
const progress = state.entries.reduce(
(acc, { stage, completed, error }) => (completed || error) ? (acc + TOTALSTAGES) : (acc + stage), 0
);
const total = state.entries.length * TOTALSTAGES;

return {
...state,
progress: progress / total,
};
}


const recognize = (state = { progress: 0, entries: [] }, action, globalState) => {
switch (action.type) {
case BEGIN_RECOGNIZE_DOCUMENT:
return [
return updateProgressInState({
...state,
{
itemKey: action.itemKey,
libraryKey: action.libraryKey,
stage: 0,
error: null,
completed: false,
}
];
entries: [
...state.entries,
{
itemKey: action.itemKey,
itemTitle: getBaseMappedValue(
globalState?.meta?.mappings, globalState.libraries[globalState.current.libraryKey]?.items?.[action.itemKey], 'title'
),
libraryKey: action.libraryKey,
stage: 0,
error: null,
completed: false,
}
]
});
case UPDATE_RECOGNIZE_DOCUMENT:
return state.map(recognize => {
if (recognize.itemKey === action.itemKey && recognize.libraryKey === action.libraryKey) {
return {
...recognize,
stage: action.stage,
};
}
return recognize;
return updateProgressInState({
...state,
entries: state.entries.map(entry => {
if (entry.itemKey === action.itemKey && entry.libraryKey === action.libraryKey) {
return {
...entry,
stage: action.stage,
};
}
return entry;
})
});
case COMPLETE_RECOGNIZE_DOCUMENT:
return state.map(recognize => {
if (recognize.itemKey === action.itemKey && recognize.libraryKey === action.libraryKey) {
return {
...recognize,
parentItemKey: action.parentItemKey,
completed: true,
};
}
return recognize;
return updateProgressInState({
...state,
entries: state.entries.map(entry => {
if (entry.itemKey === action.itemKey && entry.libraryKey === action.libraryKey) {
return {
...entry,
parentItemKey: action.parentItemKey,
parentItemTitle: getBaseMappedValue(
globalState?.meta?.mappings, globalState.libraries[globalState.current.libraryKey]?.items?.[action.parentItemKey], 'title'
),
completed: true,
};
}
return entry;
})
});
case ERROR_RECOGNIZE_DOCUMENT:
return state.map(recognize => {
if (recognize.itemKey === action.itemKey && recognize.libraryKey === action.libraryKey) {
return {
...recognize,
stage: 3,
error: action.error,
};
}
return recognize;
return updateProgressInState({
...state,
entries: state.entries.map(entry => {
if (entry.itemKey === action.itemKey && entry.libraryKey === action.libraryKey) {
return {
...entry,
error: action.error,
};
}
return entry;
})
});
case CLEAR_RECOGNIZE_DOCUMENT:
return state.filter(recognize => recognize.itemKey !== action.itemKey);
return updateProgressInState({
...state,
entries: state.entries.filter(entry => entry.itemKey !== action.itemKey)
});
case CLEAR_RECOGNIZE_DOCUMENTS:
return [];
return {
...state,
progress: 0,
entries: []
}
default:
return state;
}
Expand Down
27 changes: 27 additions & 0 deletions src/scss/components/modal/_metadata-retrieval.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.recognize-progress {
progress {
width: 100%;
}
}

.recognize-table {
margin-top: 1em;

.recognize-row {
display: flex;
padding: 0.33em 0;
}

.recognize-row-left, .recognize-row-right {
@include text-truncate;
}

.recognize-row-left {
flex: 0 0 100px;
padding-right: 24px;
}

.recognize-row-right {
flex: 1 1 auto;
}
}
1 change: 1 addition & 0 deletions src/scss/zotero-web-library.scss
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ $scope: ".zotero-wl";
@import "components/modal/note";
@import "components/modal/identifier-picker";
@import "components/modal/manage-tags";
@import "components/modal/metadata-retrieval";
@import "components/drag-layer";
@import "components/dropzone";
@import "components/rich-editor";
Expand Down

0 comments on commit 5bb0260

Please sign in to comment.