Skip to content

Commit

Permalink
Add basic import from file support #450
Browse files Browse the repository at this point in the history
  • Loading branch information
tnajdek committed Nov 30, 2024
1 parent a52cf2e commit 52a4f37
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 23 deletions.
2 changes: 1 addition & 1 deletion scripts/server.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const handler = (req, resp) => {
resp.end(buf);
});
};
if(req.url.startsWith('/web') || req.url.startsWith('/search') || req.url.startsWith('/export')) {
if (req.url.startsWith('/web') || req.url.startsWith('/search') || req.url.startsWith('/export') || req.url.startsWith('/import')) {
proxy.web(req, resp, {
changeOrigin: true,
target: `${translateURL}`,
Expand Down
52 changes: 31 additions & 21 deletions src/js/actions/identifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,41 @@ const getNextLinkFromResponse = response => {
return next;
}

const importFromFile = fileData => {
return searchIdentifier(fileData.file, { shouldImport: true });
}

const searchIdentifier = identifier => {
const searchIdentifier = (identifier, { shouldImport = false } = {}) => {
return async (dispatch, getState) => {
identifier = identifier.trim();
const { config } = getState();
const { translateUrl } = config;
const matchDOI = decodeURIComponent(identifier)
.match(/^https?:\/\/doi.org\/(10(?:\.[0-9]{4,})?\/[^\s]*[^\s.,])$/);
if(matchDOI) {
identifier = matchDOI[1];
}
const identifierIsUrl = isLikeURL(identifier);
if(!identifierIsUrl) {
const identifierObjects = extractIdentifiers(identifier);
if(identifierObjects.length === 0) {
// invalid identifier, if we don't return, it will run search for a generic term like zbib
dispatch(reportIdentifierNoResults());
return;
} else {
return dispatch(currentAddMultipleTranslatedItems(identifierObjects.map(io => Object.values(io)[0])));

let identifierIsUrl = false;
let url;

if(!shouldImport) {
identifier = identifier.trim();
const matchDOI = decodeURIComponent(identifier)
.match(/^https?:\/\/doi.org\/(10(?:\.[0-9]{4,})?\/[^\s]*[^\s.,])$/);
if(matchDOI) {
identifier = matchDOI[1];
}
const identifierIsUrl = isLikeURL(identifier);
if(!identifierIsUrl) {
const identifierObjects = extractIdentifiers(identifier);
if(identifierObjects.length === 0) {
// invalid identifier, if we don't return, it will run search for a generic term like zbib
dispatch(reportIdentifierNoResults());
return;
} else {
return dispatch(currentAddMultipleTranslatedItems(identifierObjects.map(io => Object.values(io)[0])));
}
}
url = `${translateUrl}/${((identifierIsUrl ? 'web' : 'search'))}`;
} else {
url = `${translateUrl}/import`;
}
const url = `${translateUrl}/${((identifierIsUrl ? 'web' : 'search'))}`;

dispatch({ type: REQUEST_ADD_BY_IDENTIFIER, identifier, identifierIsUrl });

try {
Expand Down Expand Up @@ -214,9 +226,7 @@ const currentAddTranslatedItem = translatedItem => {
const searchIdentifierMore = () => {
return async (dispatch, getState) => {
const state = getState();
const { config } = state
const { translateUrl } = config;
const { identifier, identifierIsUrl, next, result } = state.identifier;
const { identifier, next, result } = state.identifier;
if(!identifier || result !== CHOICE) {
return;
}
Expand Down Expand Up @@ -262,4 +272,4 @@ const reportIdentifierNoResults = () => ({
errorType: 'info',
});

export { currentAddTranslatedItem, currentAddMultipleTranslatedItems, resetIdentifier, searchIdentifier, searchIdentifierMore, reportIdentifierNoResults };
export { currentAddTranslatedItem, currentAddMultipleTranslatedItems, importFromFile, resetIdentifier, searchIdentifier, searchIdentifierMore, reportIdentifierNoResults };
6 changes: 6 additions & 0 deletions src/js/component/item/actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Button, Dropdown, DropdownToggle, DropdownMenu, DropdownItem, Icon } fr

import { ToolGroup } from '../ui/toolbars';
import NewItemSelector from 'component/item/actions/new-item';
import ImportAction from 'component/item/actions/import';
import ExportActions from 'component/item/actions/export';
import columnProperties from '../../constants/column-properties';
import AddByIdentifier from 'component/item/actions/add-by-identifier';
Expand Down Expand Up @@ -246,6 +247,11 @@ const ItemActionsDesktop = memo(props => {
</Fragment>
) }
<ToolGroup>
<ImportAction
tabIndex={-2}
onFocusNext={onFocusNext}
onFocusPrev={onFocusPrev}
/>
<ExportActions
disabled={ selectedContainsCollection || selectedItemsCount === 0 || selectedItemsCount > 100 }
tabIndex={ -2 }
Expand Down
2 changes: 1 addition & 1 deletion src/js/component/item/actions/add-by-identifier.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ const AddByIdentifier = props => {
}, [addItem, isOpen, item, prevItem]);

useEffect(() => {
if(isOpen && items && prevItems === null && [CHOICE, CHOICE_EXHAUSTED, MULTIPLE].includes(result)) {
if(items && prevItems === null && [CHOICE, CHOICE_EXHAUSTED, MULTIPLE].includes(result)) {
setIsOpen(!isOpen);
dispatch(toggleModal(IDENTIFIER_PICKER, true));
}
Expand Down
70 changes: 70 additions & 0 deletions src/js/component/item/actions/import.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import PropTypes from 'prop-types';
import { memo, useCallback, useId, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { Button, Icon } from 'web-common/components';

import { importFromFile } from '../../../actions';
import { getFileData } from '../../../common/event';


const ImportAction = ({ disabled, onFocusNext, onFocusPrev, tabIndex }) => {
const dispatch = useDispatch();
const uploadFileId = useId();
const fileInputRef = useRef(null);

const handleKeyDown = useCallback(ev => {
if (ev.target !== ev.currentTarget) {
return;
}

if (ev.key === 'ArrowRight') {
onFocusNext(ev);
} else if (ev.key === 'ArrowLeft') {
onFocusPrev(ev);
}
}, [onFocusNext, onFocusPrev]);

const handleImportClick = useCallback(() => {
fileInputRef.current.click();
}, []);

const handleFileInputChange = useCallback(async ev => {
const fileData = await getFileData(ev.currentTarget.files[0]);
if (fileData) {
dispatch(importFromFile(fileData));
}
}, [dispatch]);

return (
<div className="btn-file">
<input
aria-labelledby={uploadFileId}
multiple={false}
onChange={handleFileInputChange}
ref={fileInputRef}
tabIndex={-1}
type="file"
/>
<Button
disabled={disabled}
icon
onClick={handleImportClick}
onKeyDown={handleKeyDown}
tabIndex={tabIndex}
title="Import"
>
<Icon type="16/caret-16" width="16" height="16" />
</Button>

</div>
);
}

ImportAction.propTypes = {
disabled: PropTypes.bool,
onFocusNext: PropTypes.func,
onFocusPrev: PropTypes.func,
tabIndex: PropTypes.number
};

export default memo(ImportAction);

0 comments on commit 52a4f37

Please sign in to comment.