Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement#449 excel import UI #504

Merged
merged 3 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions 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 All @@ -24,6 +24,14 @@ interface TabsProps {
* Navigation link style.
*/
navLinkStyle?: string;
/**
* Classname for the Nav component
*/
navClassName?: string;
/**
* Classname for the TabContent component
*/
contentClassName?: string;
}

const Tabs: React.FC<TabsProps> = (props) => {
Expand Down Expand Up @@ -72,8 +80,15 @@ const Tabs: React.FC<TabsProps> = (props) => {

return (
<div>
<Nav tabs={true}>{navLinks}</Nav>
<TabContent activeTab={props.activeTabLabelKey}>{tabs}</TabContent>
<Nav tabs={true} className={props.navClassName}>
{navLinks}
</Nav>
<TabContent
activeTab={props.activeTabLabelKey}
className={props.contentClassName}
>
{tabs}
</TabContent>
</div>
);
};
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;
58 changes: 20 additions & 38 deletions src/component/vocabulary/importing/ImportVocabularyPage.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,33 @@
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)}
contentClassName="pt-3"
/>
</IfUserIsEditor>
);
};
Expand Down
11 changes: 8 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 Expand Up @@ -791,6 +792,10 @@ const cs = {
"Soubor nemohl být nahrán, protože jeho velikost přesahuje nastavený limit.",
"error.term.state.terminal.liveChildren":
"Pojmu nelze nastavit koncový stav, dokud má alespoň jednoho potomka v jiném než koncovém stavu.",
"error.vocabulary.import.excel.duplicateIdentifier":
"Excel obsahuje více pojmů se stejným identifikátorem.",
"error.vocabulary.import.excel.duplicateLabel":
"Excel obsahuje více pojmů se stejným názvem.",

"history.label": "Historie změn",
"history.loading": "Načítám historii...",
Expand Down
Loading