Skip to content

Commit

Permalink
[Enhancement #449] Split new vocabulary import into separate tabs for…
Browse files Browse the repository at this point in the history
… SKOS and Excel.
  • Loading branch information
ledsoft committed Aug 26, 2024
1 parent 9171e73 commit 8630055
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 47 deletions.
2 changes: 1 addition & 1 deletion src/component/misc/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface TabsProps {
/**
* Map of IDs to the actual components
*/
tabs: { [activeTabLabelKey: string]: JSX.Element };
tabs: { [activeTabLabelKey: string]: React.JSX.Element };
/**
* Map of IDs to the tab badge (no badge shown if the key is missing)
*/
Expand Down
9 changes: 6 additions & 3 deletions src/component/resource/file/UploadFile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,13 @@ function limitStringToBytes(limitStr: string) {

interface UploadFileProps {
setFile: (file: File) => void;
labelKey?: string;
}

export const UploadFile: React.FC<UploadFileProps> = (props) => {
const { setFile } = props;
export const UploadFile: React.FC<UploadFileProps> = ({
setFile,
labelKey = "resource.create.file.select.label",
}) => {
const [currentFile, setCurrentFile] = React.useState<File | undefined>();
const [dragActive, setDragActive] = React.useState(false);
const { i18n, formatMessage } = useI18n();
Expand Down Expand Up @@ -82,7 +85,7 @@ export const UploadFile: React.FC<UploadFileProps> = (props) => {
<input {...getInputProps()} />
<div>
<Label className="placeholder-text w-100 text-center">
{i18n("resource.create.file.select.label")}
{i18n(labelKey)}
</Label>
</div>
<div className="w-100 icon-container text-center">
Expand Down
6 changes: 6 additions & 0 deletions src/component/vocabulary/CreateVocabularyForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ interface CreateVocabularyFormProps {
onCancel: () => void;
language: string;
selectLanguage: (lang: string) => void;
childrenBefore?: React.ReactNode;
childrenAfter?: React.ReactNode;
}

function generateIri(
Expand All @@ -51,6 +53,8 @@ const CreateVocabularyForm: React.FC<CreateVocabularyFormProps> = ({
onCancel,
language,
selectLanguage,
childrenBefore,
childrenAfter,
}) => {
const { i18n, formatMessage } = useI18n();
const [iri, setIri] = useState<string>("");
Expand Down Expand Up @@ -141,6 +145,7 @@ const CreateVocabularyForm: React.FC<CreateVocabularyFormProps> = ({
/>
<Card id="create-vocabulary">
<CardBody>
{childrenBefore}
<Row>
<Col xs={12}>
<Row>
Expand Down Expand Up @@ -203,6 +208,7 @@ const CreateVocabularyForm: React.FC<CreateVocabularyFormProps> = ({
/>,
]}
/>
{childrenAfter}
<Row>
<Col xs={12}>
<ButtonToolbar className="d-flex justify-content-center mt-4">
Expand Down
138 changes: 138 additions & 0 deletions src/component/vocabulary/importing/CreateVocabularyFromExcel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import TermItState from "../../../model/TermItState";
import PromiseTrackingMask from "../../misc/PromiseTrackingMask";
import CreateVocabularyForm from "../CreateVocabularyForm";
import Routes from "../../../util/Routes";
import Routing from "../../../util/Routing";
import { ThunkDispatch } from "../../../util/Types";
import Vocabulary from "../../../model/Vocabulary";
import TermItFile from "../../../model/File";
import { trackPromise } from "react-promise-tracker";
import {
createFileInDocument,
createVocabulary,
uploadFileContent,
} from "../../../action/AsyncActions";
import Utils from "../../../util/Utils";
import VocabularyUtils from "../../../util/VocabularyUtils";
import { publishNotification } from "../../../action/SyncActions";
import NotificationType from "../../../model/NotificationType";
import IdentifierResolver from "../../../util/IdentifierResolver";
import { Col, Label, Row } from "reactstrap";
import UploadFile from "../../resource/file/UploadFile";
import {
downloadExcelTemplate,
importIntoExistingVocabulary,
} from "../../../action/AsyncImportActions";
import { FormattedMessage } from "react-intl";
import { useI18n } from "../../hook/useI18n";

const CreateVocabularyFromExcel: React.FC = () => {
const { i18n } = useI18n();
const configuredLanguage = useSelector(
(state: TermItState) => state.configuration.language
);
const [language, setLanguage] = React.useState(configuredLanguage);
const [file, setFile] = useState<File>();
const dispatch: ThunkDispatch = useDispatch();
const downloadTemplate = () => {
dispatch(downloadExcelTemplate());
};
const onCreate = (
vocabulary: Vocabulary,
files: TermItFile[],
fileContents: File[]
) => {
trackPromise(
dispatch(createVocabulary(vocabulary)).then((location) => {
if (!location) {
return;
}
return Promise.all(
Utils.sanitizeArray(files).map((f, fIndex) =>
dispatch(
createFileInDocument(
f,
VocabularyUtils.create(vocabulary.document!.iri)
)
)
.then(() =>
dispatch(
uploadFileContent(
VocabularyUtils.create(f.iri),
fileContents[fIndex]
)
)
)
.then(() =>
dispatch(
publishNotification({
source: { type: NotificationType.FILE_CONTENT_UPLOADED },
})
)
)
)
)
.then(() => {
if (file) {
return dispatch(
importIntoExistingVocabulary(
VocabularyUtils.create(vocabulary.iri),
file
)
);
}
return Promise.resolve({});
})
.then(() =>
Routing.transitionTo(
Routes.vocabularySummary,
IdentifierResolver.routingOptionsFromLocation(location)
)
);
}),
"import-excel-vocabulary"
);
};

return (
<>
<PromiseTrackingMask area="import-excel-vocabulary" />
<CreateVocabularyForm
onSave={onCreate}
onCancel={() => Routing.transitionTo(Routes.vocabularies)}
language={language}
selectLanguage={setLanguage}
childrenBefore={
<Row className="mb-3">
<Col xs={12}>
<Label className="attribute-label mb-2">
<FormattedMessage
id="vocabulary.summary.import.dialog.excelImport"
values={{
a: (chunks: any) => (
<span
role="button"
className="bold btn-link link-like"
onClick={downloadTemplate}
title={i18n(
"vocabulary.summary.import.excel.template.tooltip"
)}
>
{chunks}
</span>
),
}}
/>
</Label>
<UploadFile setFile={(file) => setFile(file)} />
</Col>
</Row>
}
/>
</>
);
};

export default CreateVocabularyFromExcel;
48 changes: 48 additions & 0 deletions src/component/vocabulary/importing/CreateVocabularyFromSkos.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from "react";
import { Card, CardBody, Label } from "reactstrap";
import { useI18n } from "src/component/hook/useI18n";
import PromiseTrackingMask from "../../misc/PromiseTrackingMask";
import ImportVocabularyDialog from "./ImportVocabularyDialog";
import Routing from "../../../util/Routing";
import Routes from "../../../util/Routes";
import { ThunkDispatch } from "../../../util/Types";
import { useDispatch } from "react-redux";
import { trackPromise } from "react-promise-tracker";
import { importSkosAsNewVocabulary } from "../../../action/AsyncImportActions";
import IdentifierResolver from "../../../util/IdentifierResolver";

const CreateVocabularyFromSkos: React.FC = () => {
const { i18n } = useI18n();
const dispatch: ThunkDispatch = useDispatch();
const importSkos = (file: File, rename: Boolean) =>
trackPromise(
dispatch(importSkosAsNewVocabulary(file, rename)),
"import-vocabulary"
).then((location?: string) => {
if (location) {
Routing.transitionTo(
Routes.vocabularySummary,
IdentifierResolver.routingOptionsFromLocation(location)
);
}
});

return (
<Card id="vocabulary-import" className="mb-3">
<CardBody>
<PromiseTrackingMask area="import-vocabulary" />
<Label className="attribute-label mb-2">
{i18n("vocabulary.import.dialog.message")}
</Label>
<ImportVocabularyDialog
propKeyPrefix="vocabulary.import"
onCreate={importSkos}
onCancel={() => Routing.transitionTo(Routes.vocabularies)}
allowRename={true}
/>
</CardBody>
</Card>
);
};

export default CreateVocabularyFromSkos;
57 changes: 19 additions & 38 deletions src/component/vocabulary/importing/ImportVocabularyPage.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,32 @@
import React from "react";
import IfUserIsEditor from "../../authorization/IfUserIsEditor";
import Routing from "../../../util/Routing";
import Routes from "../../../util/Routes";
import { useDispatch } from "react-redux";
import { ThunkDispatch } from "../../../util/Types";
import { importSkosAsNewVocabulary } from "../../../action/AsyncImportActions";
import { useI18n } from "../../hook/useI18n";
import HeaderWithActions from "../../misc/HeaderWithActions";
import { Card, CardBody, Label } from "reactstrap";
import IdentifierResolver from "../../../util/IdentifierResolver";
import PromiseTrackingMask from "../../misc/PromiseTrackingMask";
import { trackPromise } from "react-promise-tracker";
import ImportVocabularyDialog from "./ImportVocabularyDialog";
import Tabs from "../../misc/Tabs";
import CreateVocabularyFromExcel from "./CreateVocabularyFromExcel";
import CreateVocabularyFromSkos from "./CreateVocabularyFromSkos";

declare type ImportType =
| "vocabulary.import.type.skos"
| "vocabulary.import.type.excel";

const ImportVocabularyPage = () => {
const { i18n } = useI18n();
const dispatch: ThunkDispatch = useDispatch();
const createFile = (file: File, rename: Boolean) =>
trackPromise(
dispatch(importSkosAsNewVocabulary(file, rename)),
"import-vocabulary"
).then((location?: string) => {
if (location) {
Routing.transitionTo(
Routes.vocabularySummary,
IdentifierResolver.routingOptionsFromLocation(location)
);
}
});
const onCancel = () => Routing.transitionTo(Routes.vocabularies);
const [activeTab, setActiveTab] = React.useState<ImportType>(
"vocabulary.import.type.skos"
);

return (
<IfUserIsEditor>
<HeaderWithActions title={i18n("vocabulary.import.dialog.title")} />
<Card id="vocabulary-import" className="mb-3">
<CardBody>
<PromiseTrackingMask area="import-vocabulary" />
<Label className="attribute-label mb-2">
{i18n("vocabulary.import.dialog.message")}
</Label>
<ImportVocabularyDialog
propKeyPrefix="vocabulary.import"
onCreate={createFile}
onCancel={onCancel}
allowRename={true}
/>
</CardBody>
</Card>
<Tabs
activeTabLabelKey={activeTab}
tabs={{
"vocabulary.import.type.skos": <CreateVocabularyFromSkos />,
"vocabulary.import.type.excel": <CreateVocabularyFromExcel />,
}}
changeTab={(k) => setActiveTab(k as ImportType)}
/>
</IfUserIsEditor>
);
};
Expand Down
7 changes: 4 additions & 3 deletions src/i18n/cs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,10 @@ const cs = {
"Stáhnout šablonu pro MS Excel",
"vocabulary.summary.import.nonEmpty.warning":
"Slovník není prázdný, stávající data budou přepsána importovanými.",
"vocabulary.import.type.skos": "SKOS",
"vocabulary.import.type.excel": "MS Excel",
"vocabulary.import.action": "Importovat",
"vocabulary.import.action.tooltip": "Import SKOS slovníku.",
"vocabulary.import.dialog.title": "Importovat SKOS slovník",
"vocabulary.import.dialog.title": "Importovat slovník",
"vocabulary.import.dialog.message":
"Importovaný soubor musí být formátu SKOS. " +
"Soubor musí obsahovat jediný skos:ConceptScheme.",
Expand Down Expand Up @@ -436,7 +437,7 @@ const cs = {
"Přidat nový soubor do tohoto dokumentu",
"resource.metadata.document.files.actions.add.dialog.title": "Nový soubor",
"resource.metadata.document.files.empty":
"Žádné soubory nenalezeny. Vytvořte nějaký...",
"Žádné soubory nenalezeny. Přidejte nějaký...",
"resource.file.vocabulary.create": "Přidat soubor",

"term.language.selector.item":
Expand Down
5 changes: 3 additions & 2 deletions src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,10 @@ const en = {
"Download a MS Excel template",
"vocabulary.summary.import.nonEmpty.warning":
"Vocabulary is not empty, existing data will be overwritten by the imported.",
"vocabulary.import.type.skos": "SKOS",
"vocabulary.import.type.excel": "MS Excel",
"vocabulary.import.action": "Import",
"vocabulary.import.action.tooltip": "SKOS vocabulary import.",
"vocabulary.import.dialog.title": "Import SKOS vocabulary",
"vocabulary.import.dialog.title": "Import vocabulary",
"vocabulary.import.dialog.message":
"Imported file must be in the SKOS format. " +
"The file must contain exactly one instance of skos:ConceptScheme.",
Expand Down

0 comments on commit 8630055

Please sign in to comment.