Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
138 changes: 138 additions & 0 deletions src/containers/StructurePage/resourceComponents/RemoveResource.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* Copyright (c) 2023-present, NDLA.
*
* This source code is licensed under the GPLv3 license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import styled from '@emotion/styled';
import { ButtonV2, CloseButton } from '@ndla/button';
import { colors, spacing } from '@ndla/core';
import { ModalBody, ModalHeaderV2 } from '@ndla/modal';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery, useQueryClient } from 'react-query';
import Spinner from '../../../components/Spinner';
import { PUBLISHED, UNPUBLISHED } from '../../../constants';
import { updateStatusDraft } from '../../../modules/draft/draftApi';
import { fetchLearningpathsWithArticle } from '../../../modules/learningpath/learningpathApi';
import { ResourceWithNodeConnection } from '../../../modules/nodes/nodeApiTypes';
import { useDeleteResourceForNodeMutation } from '../../../modules/nodes/nodeMutations';
import { resourcesWithNodeConnectionQueryKey } from '../../../modules/nodes/nodeQueries';
import { getIdFromUrn } from '../../../util/taxonomyHelpers';
import { useTaxonomyVersion } from '../../StructureVersion/TaxonomyVersionProvider';
import { ResourceWithNodeConnectionAndMeta } from './StructureResources';

interface Props {
onClose: () => void;
nodeId: string;
deleteResource: ResourceWithNodeConnectionAndMeta;
}

const ButtonWrapper = styled.div`
width: 100%;
display: flex;
gap: ${spacing.small};
justify-content: flex-end;
`;

const RightAlign = styled.div`
display: flex;
flex-direction: column;
align-items: flex-end;
`;

const ErrorText = styled.p`
color: ${colors.support.red};
margin-bottom: 0;
`;

const RemoveResource = ({ deleteResource, nodeId, onClose }: Props) => {
const { t, i18n } = useTranslation();
const [error, setError] = useState<boolean>(false);
const { taxonomyVersion } = useTaxonomyVersion();
const qc = useQueryClient();
const articleId = useMemo(
() =>
deleteResource.contentUri?.includes('article')
? getIdFromUrn(deleteResource.contentUri)
: undefined,
[deleteResource.contentUri],
);

const compKey = useMemo(
() =>
resourcesWithNodeConnectionQueryKey({
id: nodeId,
language: i18n.language,
taxonomyVersion,
}),
[i18n.language, nodeId, taxonomyVersion],
);

const { data, isLoading } = useQuery(
['contains-article', articleId],
() => fetchLearningpathsWithArticle(articleId!),
{
enabled: !!articleId,
},
);

const { mutateAsync, isLoading: isDeleting } = useDeleteResourceForNodeMutation({
onMutate: async ({ id }) => {
await qc.cancelQueries(compKey);
const prevData = qc.getQueryData<ResourceWithNodeConnection[]>(compKey) ?? [];
const withoutDeleted = prevData.filter(res => res.connectionId !== id);
qc.setQueryData<ResourceWithNodeConnection[]>(compKey, withoutDeleted);
return prevData;
},
onSuccess: () => qc.invalidateQueries(compKey),
});

const deleteText = useMemo(
() =>
data?.length || deleteResource.paths.length > 1
? t('taxonomy.resource.confirmDelete')
: t('taxonomy.resource.confirmDeleteAndUnpublish'),
[data?.length, t, deleteResource.paths],
);

const onDelete = async () => {
try {
setError(false);
if (!data?.length && articleId && deleteResource.contentMeta?.status?.current === PUBLISHED) {
await updateStatusDraft(articleId, UNPUBLISHED);
}
await mutateAsync({ id: deleteResource.connectionId, taxonomyVersion });
onClose();
} catch (e) {
setError(true);
}
};

return (
<>
<ModalHeaderV2>
<h1>{t('taxonomy.removeResource')}</h1>
<CloseButton onClick={onClose} />
</ModalHeaderV2>
<ModalBody>
{isLoading ? <Spinner /> : deleteText}
<RightAlign>
<ButtonWrapper>
<ButtonV2 variant="outline" onClick={onClose}>
{t('form.abort')}
</ButtonV2>
<ButtonV2 colorTheme="danger" disabled={isLoading || isDeleting} onClick={onDelete}>
{isDeleting ? <Spinner /> : t('form.remove')}
</ButtonV2>
</ButtonWrapper>
{error && <ErrorText aria-live="assertive">{t('taxonomy.errorMessage')}</ErrorText>}
</RightAlign>
</ModalBody>
</>
);
};

export default RemoveResource;
8 changes: 2 additions & 6 deletions src/containers/StructurePage/resourceComponents/Resource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ interface Props {
connectionId?: string; // required for MakeDndList, otherwise ignored
id?: string; // required for MakeDndList, otherwise ignored
resource: ResourceWithNodeConnectionAndMeta;
onDelete?: (connectionId: string) => void;
onDelete?: () => void;
updateResource?: (resource: ResourceWithNodeConnection) => void;
dragHandleProps?: DraggableProvidedDragHandleProps;
}
Expand Down Expand Up @@ -353,11 +353,7 @@ const Resource = ({ resource, onDelete, dragHandleProps, currentNodeId }: Props)
{t(`form.status.${resource.contentMeta.status.current.toLowerCase()}`)}
</StatusButton>
)}
<RemoveButton
onClick={() => (onDelete ? onDelete(resource.connectionId) : null)}
size="xsmall"
colorTheme="danger"
disabled={!onDelete}>
<RemoveButton onClick={onDelete} size="xsmall" colorTheme="danger" disabled={!onDelete}>
{t('form.remove')}
</RemoveButton>
</ButtonRow>
Expand Down
79 changes: 25 additions & 54 deletions src/containers/StructurePage/resourceComponents/ResourceItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@ import { useQueryClient } from 'react-query';
import { DropResult } from 'react-beautiful-dnd';
import sortBy from 'lodash/sortBy';
import styled from '@emotion/styled';
import { ModalV2 } from '@ndla/modal';
import Resource from './Resource';
import handleError from '../../../util/handleError';
import {
useDeleteResourceForNodeMutation,
usePutResourceForNodeMutation,
} from '../../../modules/nodes/nodeMutations';
import { usePutResourceForNodeMutation } from '../../../modules/nodes/nodeMutations';
import { ResourceWithNodeConnection } from '../../../modules/nodes/nodeApiTypes';
import AlertModal from '../../../components/AlertModal';
import MakeDndList from '../../../components/MakeDndList';
import { useTaxonomyVersion } from '../../StructureVersion/TaxonomyVersionProvider';
import {
Expand All @@ -28,29 +25,25 @@ import {
} from '../../../modules/nodes/nodeQueries';
import { ResourceWithNodeConnectionAndMeta } from './StructureResources';
import { Dictionary } from '../../../interfaces';
import RemoveResource from './RemoveResource';

const StyledResourceItems = styled.ul`
list-style: none;
margin: 0;
padding: 0;
`;

const StyledErrorMessage = styled.div`
text-align: center;
color: #fe5f55;
`;

interface Props {
resources: ResourceWithNodeConnectionAndMeta[];
currentNodeId: string;
contentMeta: Dictionary<NodeResourceMeta>;
}

const isError = (error: unknown): error is Error => (error as Error).message !== undefined;

const ResourceItems = ({ resources, currentNodeId, contentMeta }: Props) => {
const { t, i18n } = useTranslation();
const [deleteId, setDeleteId] = useState<string>('');
const { i18n } = useTranslation();
const [deleteResource, setDeleteResource] = useState<
ResourceWithNodeConnectionAndMeta | undefined
>(undefined);
const { taxonomyVersion } = useTaxonomyVersion();

const qc = useQueryClient();
Expand All @@ -59,15 +52,6 @@ const ResourceItems = ({ resources, currentNodeId, contentMeta }: Props) => {
language: i18n.language,
taxonomyVersion,
});
const deleteNodeResource = useDeleteResourceForNodeMutation({
onMutate: async ({ id }) => {
await qc.cancelQueries(compKey);
const prevData = qc.getQueryData<ResourceWithNodeConnection[]>(compKey) ?? [];
const withoutDeleted = prevData.filter(res => res.connectionId !== id);
qc.setQueryData<ResourceWithNodeConnection[]>(compKey, withoutDeleted);
return prevData;
},
});

const onUpdateRank = async (id: string, newRank: number) => {
await qc.cancelQueries(compKey);
Expand All @@ -89,14 +73,6 @@ const ResourceItems = ({ resources, currentNodeId, contentMeta }: Props) => {
onSuccess: () => qc.invalidateQueries(compKey),
});

const onDelete = async (deleteId: string) => {
setDeleteId('');
await deleteNodeResource.mutateAsync(
{ id: deleteId, taxonomyVersion },
{ onSuccess: () => qc.invalidateQueries(compKey) },
);
};

const onDragEnd = async ({ destination, source }: DropResult) => {
if (!destination) return;
const { connectionId, primary, relevanceId, rank: currentRank } = resources[source.index];
Expand All @@ -115,8 +91,11 @@ const ResourceItems = ({ resources, currentNodeId, contentMeta }: Props) => {
});
};

const toggleDelete = (newDeleteId: string) => {
setDeleteId(newDeleteId);
const toggleDelete = (resource: ResourceWithNodeConnection) => {
setDeleteResource({
...resource,
contentMeta: resource.contentUri ? contentMeta[resource.contentUri] : undefined,
});
};

return (
Expand All @@ -132,30 +111,22 @@ const ResourceItems = ({ resources, currentNodeId, contentMeta }: Props) => {
contentMeta: resource.contentUri ? contentMeta[resource.contentUri] : undefined,
}}
key={resource.id}
onDelete={toggleDelete}
onDelete={() => toggleDelete(resource)}
/>
))}
</MakeDndList>
{deleteNodeResource.error && isError(deleteNodeResource.error) && (
<StyledErrorMessage data-testid="inlineEditErrorMessage">
{`${t('taxonomy.errorMessage')}: ${deleteNodeResource.error.message}`}
</StyledErrorMessage>
)}
<AlertModal
show={!!deleteId}
text={t('taxonomy.resource.confirmDelete')}
actions={[
{
text: t('form.abort'),
onClick: () => toggleDelete(''),
},
{
text: t('alertModal.delete'),
onClick: () => onDelete(deleteId!),
},
]}
onCancel={() => toggleDelete('')}
/>
<ModalV2 controlled isOpen={!!deleteResource} onClose={() => setDeleteResource(undefined)}>
{close =>
deleteResource && (
<RemoveResource
key={deleteResource.id}
deleteResource={deleteResource!}
nodeId={currentNodeId}
onClose={close}
/>
)
}
</ModalV2>
</StyledResourceItems>
);
};
Expand Down
4 changes: 3 additions & 1 deletion src/phrases/phrases-en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1254,7 +1254,7 @@ const phrases = {
},
errorMessage: {
title: 'Oops, something went wrong',
description: 'Sorry, an error occurd.',
description: 'Sorry, an error occurred.',
back: 'Back',
goToFrontPage: 'Go to frontpage',
invalidUrl: 'Invalid url',
Expand Down Expand Up @@ -1384,6 +1384,8 @@ const phrases = {
resource: {
confirmDelete:
'Do you want to delete the resource from this folder? This will not affect the placement other places',
confirmDeleteAndUnpublish:
'Do you want to delete the resource from this folder. This is the last place this resource is used. It will be unpublished if you delete it.',
copyError:
'An error occurred while copying resources. Double check the copied resources and try to fix deficiencies manually, or delete the copied resources and try to copy again',
addResourceConflict: 'The resource you attempted to add already exists on the topic.',
Expand Down
2 changes: 2 additions & 0 deletions src/phrases/phrases-nb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1386,6 +1386,8 @@ const phrases = {
resource: {
confirmDelete:
'Vil du fjerne ressursen fra denne mappen? Dette vil ikke påvirke plasseringen andre steder',
confirmDeleteAndUnpublish:
'Vil du fjerne ressursen fra denne mappen? Dette er det siste stedet ressursen brukes. Den vil bli avpublisert dersom du fjerner den.',
copyError:
'Det oppsto en feil ved kopiering av ressurser. Dobbeltsjekk de kopierte ressursene og prøv å fikse mangler manuelt, eller slett de kopierte ressursene og prøv å kopiere på nytt',
addResourceConflict: 'Ressursen du forsøkte å legge til finnes allerede på emnet.',
Expand Down
2 changes: 2 additions & 0 deletions src/phrases/phrases-nn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1386,6 +1386,8 @@ const phrases = {
resource: {
confirmDelete:
'Vil du fjerne ressursen frå denne mappa? Dette vil ikkje påverke plasseringa andre steder',
confirmDeleteAndUnpublish:
'Vil du fjerne ressursen frå denne mappa? Dette er det siste stadet ressursen brukes. Den vil bli avpublisert dersom du fjernar den.',
Comment thread
Jonas-C marked this conversation as resolved.
Outdated
copyError:
'Det oppstod ein feil ved kopiering av ressursar. Dobbeltsjekk dei kopierte ressursane og prøv å fikse manglar manuelt, eller slett dei kopierte ressursane og prøv å kopiere på nytt',
addResourceConflict: 'Ressursen du forsøkte å legge til finnes allerede på emnet.',
Expand Down