Skip to content
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
49 changes: 49 additions & 0 deletions cypress/e2e/builder/item/settings/itemSettings.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,55 @@ describe('Item Settings', () => {
);
});
});

it('convert to capsule', function () {
const item = PackedFolderItemFactory();
cy.intercept('PATCH', `/api/items/folders/${item.id}/to-capsule`).as(
'switchToCapsule',
);
cy.setUpApi({ items: [item] });
// enable preview by default
cy.visit(buildItemSettingsPath(item.id), {
onBeforeLoad: function (window) {
window.localStorage.setItem('graasp-preview', 'enabled');
},
});
cy.get('[aria-label="Convert to Capsule"]').click();
cy.get('[aria-label="Confirm converting to Capsule"]').click();
cy.wait('@switchToCapsule');
});

it('convert to capsule not visible outside of preview', function () {
const item = PackedFolderItemFactory();
cy.intercept('PATCH', `/api/items/folders/${item.id}/to-capsule`).as(
'switchToCapsule',
);
cy.setUpApi({ items: [item] });
cy.visit(buildItemSettingsPath(item.id));
cy.get(`#${ITEM_PANEL_TABLE_ID}`).should('be.visible');
cy.get('[aria-label="Confirm converting to Capsule"]').should(
'not.exist',
);
});

it('convert to folder', function () {
const item = PackedFolderItemFactory({
extra: { folder: { isCapsule: true } },
});
cy.intercept('PATCH', `/api/items/capsules/${item.id}/to-folder`).as(
'switchToFolder',
);
cy.setUpApi({ items: [item] });
// enable preview by default
cy.visit(buildItemSettingsPath(item.id), {
onBeforeLoad: function (window) {
window.localStorage.setItem('graasp-preview', 'enabled');
},
});
cy.get('[aria-label="Convert to Folder"]').click();
cy.get('[aria-label="Confirm converting to Folder"]').click();
cy.wait('@switchToFolder');
});
});

describe('in item menu', () => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"@emotion/react": "11.14.0",
"@emotion/styled": "11.14.1",
"@fontsource-variable/nunito": "5.2.7",
"@graasp/sdk": "5.17.0",
"@graasp/sdk": "5.18.0",
"@graasp/stylis-plugin-rtl": "2.2.0",
"@lexical/link": "0.33.1",
"@lexical/react": "0.33.1",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 47 additions & 1 deletion src/locales/en/builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -550,5 +550,51 @@
"EDIT_MESSAGE_ERROR": "An error happened when editing the message.",
"DELETE_MESSAGE_ERROR": "An error happened when deleting the message.",
"CLEAR_ERROR": "An error happened when clearing the chat."
}
},
"CONVERT_TO_CAPSULE_MODAL_TITLE": "Convert to Capsule",
"CONVERT_TO_CAPSULE_MODAL_CONTENT": "Converting a Folder into a Capsule preserves all of its existing content and data. Capsules are ideal for organizing a complete learning unit. If you find that a Capsule doesn't fit your needs, you can easily revert to a Folder from the settings panel at any time.",
"CONVERT_TO_CAPSULE_MODAL_CONTENT_DETAILS": {
"TITLE": "Why use Capsules? (Coming soon)",
"SIMPLIFIED_PERMISSIONS": {
"TITLE": "Simplified permissions",
"DESCRIPTION": "Manage access permission only at the Capsule level."
},
"INLINE_EDITOR": {
"TITLE": "Inline editor",
"DESCRIPTION": "Create and edit content directly within the Capsule for a faster, smoother workflow."
},
"CLEAR_ROLES_AND_VIEWS": {
"TITLE": "Clear roles and views",
"DESCRIPTION": "Role-based views for a focused learning experience:",
"READERS": "Readers see only the Player view.",
"WRITERS_AND_ADMINS": "Writers and admins have access to the Builder view for content creation and management."
},
"ACTIVITY_UNITY": {
"TITLE": "Actvity unity",
"DESCRIPTION": "The Player view presents the complete content at once; partial playback isn’t possible."
}
},
"CONVERT_TO_CAPSULE_MODAL_CONFIRM_BUTTON_ARIA_LABEL": "Confirm converting to Capsule",
"CONVERT_TO_CAPSULE_MODAL_CONFIRM_BUTTON": "Convert to Capsule",
"CONVERT_TO_CAPSULE_SUCCESS_MESSAGE": "The item has been successfully converted to a Capsule.",
"CONVERT_TO_CAPSULE_ERROR_MESSAGE": "An error occurred while converting the item to a Capsule. Please try again later.",
"CONVERT_TO_FOLDER_MODAL_TITLE": "Convert to Folder",
"CONVERT_TO_FOLDER_MODAL_CONTENT": "Converting a Capsule into a Folder preserves all of its existing content and data. Folders are best suited for organizing and managing data. If a Folder doesn't meet your needs, you can easily revert to a Folder from the settings panel at any time.",
"CONVERT_TO_FOLDER_MODAL_CONTENT_DETAILS": {
"TITLE": "Why use Folders?",
"FLEXIBLE_PERMISSIONS": {
"TITLE": "Flexible permissions",
"DESCRIPTION": "Manage access rights at every level, giving you more granular control."
},
"FOLDER_EDITOR": {
"TITLE": "Folder editor",
"DESCRIPTION": "Content is displayed as a file structure for easier management."
},
"EXPANDED_VISIBILITY": {
"TITLE": "Expanded visibility",
"DESCRIPTION": "Readers can access both the Player view and the Builder view."
}
},
"CONVERT_TO_FOLDER_MODAL_CONFIRM_BUTTON_ARIA_LABEL": "Confirm converting to Folder",
"CONVERT_TO_FOLDER_MODAL_CONFIRM_BUTTON": "Convert to Folder"
}
1 change: 1 addition & 0 deletions src/locales/en/enums.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"admin": "admin",

"app": "app",
"capsule": "capsule",
"document": "document",
"embeddedLink": "link",
"file": "file",
Expand Down
1 change: 1 addition & 0 deletions src/locales/fr/enums.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"write": "écriture",
"admin": "administration",
"app": "application",
"capsule": "capsule",
"document": "document",
"embeddedLink": "lien",
"file": "fichier",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { useTranslation } from 'react-i18next';

import { DialogActions, DialogContent } from '@mui/material';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';

import { getParentFromPath } from '@graasp/sdk';

import { useMutation, useQueryClient } from '@tanstack/react-query';

import { NS } from '@/config/constants';
import { convertFolderToCapsuleMutation } from '@/openapi/client/@tanstack/react-query.gen';
import { getKeyForParentId, itemKeys } from '@/query/keys';

import useModalStatus from '~builder/components/hooks/useModalStatus';

export function ConvertToCapsuleButton({
itemId,
itemPath,
}: Readonly<{ itemId: string; itemPath: string }>) {
const { t } = useTranslation(NS.Builder);
const { t: translateCommon } = useTranslation(NS.Common);
const { isOpen, openModal, closeModal } = useModalStatus();

const queryClient = useQueryClient();
const { mutate: switchToCapsule } = useMutation({
...convertFolderToCapsuleMutation(),
onSuccess: async () => {
const parentKey = getKeyForParentId(getParentFromPath(itemPath));
// refetchOnMount is false by default, so we need to remove the queries for a correct refetch
queryClient.removeQueries({
queryKey: parentKey,
});
await queryClient.invalidateQueries({
queryKey: itemKeys.single(itemId).content,
});
},
});

const convert = () => {
switchToCapsule({
path: { id: itemId },
});
closeModal();
};

return (
<>
<Button
variant="outlined"
size="small"
sx={{ ml: 1 }}
onClick={openModal}
type="button"
aria-label={t('CONVERT_TO_CAPSULE_MODAL_TITLE')}
>
{t('CONVERT_TO_CAPSULE_MODAL_TITLE')}
</Button>
<Dialog onClose={closeModal} open={isOpen}>
<DialogTitle>{t('CONVERT_TO_CAPSULE_MODAL_TITLE')}</DialogTitle>
<DialogContent>
<p>{t('CONVERT_TO_CAPSULE_MODAL_CONTENT')}</p>
<p>
{t('CONVERT_TO_CAPSULE_MODAL_CONTENT_DETAILS.TITLE')}
<ul>
<li>
<strong>
{t(
'CONVERT_TO_CAPSULE_MODAL_CONTENT_DETAILS.SIMPLIFIED_PERMISSIONS.TITLE',
)}
</strong>
{': '}
{t(
'CONVERT_TO_CAPSULE_MODAL_CONTENT_DETAILS.SIMPLIFIED_PERMISSIONS.DESCRIPTION',
)}
</li>
<li>
<strong>
{t(
'CONVERT_TO_CAPSULE_MODAL_CONTENT_DETAILS.INLINE_EDITOR.TITLE',
)}
</strong>
{': '}
{t(
'CONVERT_TO_CAPSULE_MODAL_CONTENT_DETAILS.INLINE_EDITOR.DESCRIPTION',
)}
</li>
<li>
<strong>
{t(
'CONVERT_TO_CAPSULE_MODAL_CONTENT_DETAILS.CLEAR_ROLES_AND_VIEWS.TITLE',
)}
</strong>
{': '}
<ul>
<li>
{t(
'CONVERT_TO_CAPSULE_MODAL_CONTENT_DETAILS.CLEAR_ROLES_AND_VIEWS.READERS',
)}
</li>
<li>
{t(
'CONVERT_TO_CAPSULE_MODAL_CONTENT_DETAILS.CLEAR_ROLES_AND_VIEWS.WRITERS_AND_ADMINS',
)}
</li>
</ul>
</li>
<li>
<strong>
{t(
'CONVERT_TO_CAPSULE_MODAL_CONTENT_DETAILS.ACTIVITY_UNITY.TITLE',
)}
</strong>
{': '}
{t(
'CONVERT_TO_CAPSULE_MODAL_CONTENT_DETAILS.ACTIVITY_UNITY.DESCRIPTION',
)}
</li>
</ul>
</p>
</DialogContent>
<DialogActions>
<Button onClick={closeModal}>
{translateCommon('CANCEL.BUTTON_TEXT')}
</Button>
<Button
type="button"
variant="contained"
onClick={convert}
aria-label={t('CONVERT_TO_CAPSULE_MODAL_CONFIRM_BUTTON_ARIA_LABEL')}
>
{t('CONVERT_TO_CAPSULE_MODAL_CONFIRM_BUTTON')}
</Button>
</DialogActions>
</Dialog>
</>
);
}
Loading