From 1e310799ef5b3e4ebd7e8e67304f1a7350bf3581 Mon Sep 17 00:00:00 2001 From: Kim Lan Phan Hoang Date: Fri, 16 Feb 2024 11:16:05 +0100 Subject: [PATCH] feat: merge metadata and settings, add tags and geoloc (#996) * feat: merge metadata and settings, add tags and geoloc * test: add tests, docs * test: fix tests * refactor: apply PR requested changes --- cypress/e2e/item/settings/itemSettings.cy.ts | 495 ++++++++++-------- .../uploadThumbnail.cy.ts | 6 +- .../{thumbnails => view}/viewThumbnails.cy.ts | 0 cypress/fixtures/documents.ts | 70 +-- cypress/fixtures/etherpad.ts | 6 +- cypress/fixtures/files.ts | 250 ++++----- cypress/fixtures/h5p.ts | 8 +- cypress/fixtures/items.ts | 7 +- cypress/fixtures/links.ts | 79 +-- cypress/support/commands.ts | 5 - cypress/support/index.ts | 2 - cypress/support/viewUtils.ts | 38 +- docs/itemSettings.md | 14 + package.json | 10 +- src/components/common/CollapseButton.tsx | 27 +- src/components/common/ItemMetadataButton.tsx | 28 - src/components/common/PinButton.tsx | 19 +- src/components/common/ShowChatboxButton.tsx | 47 ++ src/components/item/form/DocumentForm.tsx | 13 +- .../item/header/ItemHeaderActions.tsx | 13 +- .../item/publish/CustomizedTagsEdit.tsx | 45 +- .../item/publish/ItemPublishTab.tsx | 1 - .../item/settings/AdminChatSettings.tsx | 11 +- .../item/settings/ClearChatButton.tsx | 22 +- src/components/item/settings/FileSettings.tsx | 2 +- .../item/settings/GeolocationPicker.tsx | 141 +++++ .../{ => settings}/ItemMetadataContent.tsx | 40 +- .../item/settings/ItemSettingProperty.tsx | 54 ++ src/components/item/settings/ItemSettings.tsx | 233 +-------- .../item/settings/ItemSettingsProperties.tsx | 165 ++++++ .../item/settings/LanguageSelect.tsx | 34 ++ src/components/item/settings/LinkSettings.tsx | 2 +- src/components/item/settings/hooks.ts | 79 +++ .../pages/item/ItemInformationPage.tsx | 2 +- src/config/constants.ts | 1 - src/config/selectors.ts | 2 +- src/enums/index.ts | 14 +- src/hooks/authorization.ts | 24 + src/langs/constants.ts | 32 ++ src/langs/en.json | 32 +- src/langs/fr.json | 30 +- yarn.lock | 346 +++++++++--- 42 files changed, 1581 insertions(+), 868 deletions(-) rename cypress/e2e/item/{thumbnails => settings}/uploadThumbnail.cy.ts (81%) rename cypress/e2e/item/{thumbnails => view}/viewThumbnails.cy.ts (100%) create mode 100644 docs/itemSettings.md delete mode 100644 src/components/common/ItemMetadataButton.tsx create mode 100644 src/components/common/ShowChatboxButton.tsx create mode 100644 src/components/item/settings/GeolocationPicker.tsx rename src/components/item/{ => settings}/ItemMetadataContent.tsx (79%) create mode 100644 src/components/item/settings/ItemSettingProperty.tsx create mode 100644 src/components/item/settings/ItemSettingsProperties.tsx create mode 100644 src/components/item/settings/LanguageSelect.tsx create mode 100644 src/components/item/settings/hooks.ts create mode 100644 src/hooks/authorization.ts diff --git a/cypress/e2e/item/settings/itemSettings.cy.ts b/cypress/e2e/item/settings/itemSettings.cy.ts index e6bc87b76..ef537f15e 100644 --- a/cypress/e2e/item/settings/itemSettings.cy.ts +++ b/cypress/e2e/item/settings/itemSettings.cy.ts @@ -1,13 +1,23 @@ -import { MaxWidth } from '@graasp/sdk'; +import { ItemType, MaxWidth, getFileExtra } from '@graasp/sdk'; +import { langs } from '@graasp/translations'; -import { buildItemPath } from '../../../../src/config/paths'; +import { getMemberById } from '@/utils/member'; + +import { + buildItemPath, + buildItemSettingsPath, +} from '../../../../src/config/paths'; import { CLEAR_CHAT_CONFIRM_BUTTON_ID, CLEAR_CHAT_DIALOG_ID, CLEAR_CHAT_SETTING_ID, DOWNLOAD_CHAT_BUTTON_ID, FILE_SETTING_MAX_WIDTH_ID, + ITEM_MAIN_CLASS, + ITEM_PANEL_NAME_ID, + ITEM_PANEL_TABLE_ID, ITEM_SETTINGS_BUTTON_CLASS, + LANGUAGE_SELECTOR_ID, SETTINGS_CHATBOX_TOGGLE_ID, SETTINGS_LINK_SHOW_BUTTON_ID, SETTINGS_LINK_SHOW_IFRAME_ID, @@ -22,268 +32,345 @@ import { IMAGE_ITEM_DEFAULT, IMAGE_ITEM_DEFAULT_WITH_MAX_WIDTH, } from '../../../fixtures/files'; -import { ITEMS_SETTINGS } from '../../../fixtures/items'; +import { ITEMS_SETTINGS, SAMPLE_ITEMS } from '../../../fixtures/items'; import { GRAASP_LINK_ITEM } from '../../../fixtures/links'; +import { MEMBERS } from '../../../fixtures/members'; import { EDIT_ITEM_PAUSE } from '../../../support/constants'; describe('Item Settings', () => { - beforeEach(() => { - cy.setUpApi({ - ...ITEMS_SETTINGS, - items: [ - ...ITEMS_SETTINGS.items, - GRAASP_LINK_ITEM, - ITEM_WITH_CHATBOX_MESSAGES, - ITEM_WITH_CHATBOX_MESSAGES_AND_ADMIN, - IMAGE_ITEM_DEFAULT, - IMAGE_ITEM_DEFAULT_WITH_MAX_WIDTH, - ], + describe('read rights', () => { + beforeEach(() => { + cy.setUpApi({ + ...SAMPLE_ITEMS, + currentMember: MEMBERS.BOB, + }); + }); + + it('settings button does not open settings page', () => { + const item = SAMPLE_ITEMS.items[1]; + // manual click to verify settings button works correctly + cy.visit(buildItemPath(item.id)); + cy.get(`.${ITEM_SETTINGS_BUTTON_CLASS}`).should('not.exist'); + }); + + it('settings page redirects to item', () => { + const item = SAMPLE_ITEMS.items[1]; + // manual click to verify settings button works correctly + cy.visit(buildItemSettingsPath(item.id)); + cy.get(`.${ITEM_MAIN_CLASS}`).should('contain', item.name); }); }); - describe('Chatbox Settings', () => { - it('Disabling Chatbox', () => { - const itemId = ITEMS_SETTINGS.items[1].id; + describe('admin rights', () => { + beforeEach(() => { + cy.setUpApi({ + ...ITEMS_SETTINGS, + items: [ + ...ITEMS_SETTINGS.items, + GRAASP_LINK_ITEM, + ITEM_WITH_CHATBOX_MESSAGES, + ITEM_WITH_CHATBOX_MESSAGES_AND_ADMIN, + IMAGE_ITEM_DEFAULT, + IMAGE_ITEM_DEFAULT_WITH_MAX_WIDTH, + IMAGE_ITEM_DEFAULT, + ], + }); + }); + it('setting button opens settings page', () => { + const itemId = ITEMS_SETTINGS.items[1].id; + // manual click to verify settings button works correctly cy.visit(buildItemPath(itemId)); cy.get(`.${ITEM_SETTINGS_BUTTON_CLASS}`).click(); cy.get(`#${SETTINGS_CHATBOX_TOGGLE_ID}`).should('be.checked'); - - cy.get(`#${SETTINGS_CHATBOX_TOGGLE_ID}`).click(); - - cy.wait('@editItem').then( - ({ - response: { - body: { settings }, - }, - }) => { - expect(settings?.showChatbox).equals(false); - cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getItem').its('response.url').should('contain', itemId); - }, - ); }); - it('Enabling Chatbox', () => { - const itemId = ITEMS_SETTINGS.items[2].id; + describe('Metadata table', () => { + it('folder', () => { + const { id, name, type, creator } = ITEMS_SETTINGS.items[1]; + cy.visit(buildItemSettingsPath(id)); - cy.visit(buildItemPath(itemId)); - cy.get(`.${ITEM_SETTINGS_BUTTON_CLASS}`).click(); + cy.get(`#${ITEM_PANEL_NAME_ID}`).contains(name); + cy.get(`#${ITEM_PANEL_TABLE_ID}`).contains(type); - cy.get(`#${SETTINGS_CHATBOX_TOGGLE_ID}`).should('not.be.checked'); + const creatorName = getMemberById( + Object.values(MEMBERS), + creator?.id, + ).name; - cy.get(`#${SETTINGS_CHATBOX_TOGGLE_ID}`).click(); + cy.get(`#${ITEM_PANEL_TABLE_ID}`).should('exist').contains(creatorName); + }); - cy.wait('@editItem').then( - ({ - response: { - body: { settings }, - }, - }) => { - expect(settings?.showChatbox).equals(true); - cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getItem').its('response.url').should('contain', itemId); - }, - ); - }); + it.only('file', () => { + const { id, name, type, extra, creator } = IMAGE_ITEM_DEFAULT; + cy.visit(buildItemSettingsPath(id)); - it('Clear Chat', () => { - const itemId = ITEM_WITH_CHATBOX_MESSAGES_AND_ADMIN.id; - // navigate to the item - cy.visit(buildItemPath(itemId)); - // open the settings of the item - cy.get(`.${ITEM_SETTINGS_BUTTON_CLASS}`).click(); + cy.get(`#${ITEM_PANEL_NAME_ID}`).contains(name); + cy.get(`#${ITEM_PANEL_TABLE_ID}`).contains(extra.file.mimetype); - // click on the clear chat button - cy.get(`#${CLEAR_CHAT_SETTING_ID}`).scrollIntoView(); - cy.get(`#${CLEAR_CHAT_SETTING_ID}`).should('exist').and('be.visible'); - cy.get(`#${CLEAR_CHAT_SETTING_ID}`).click(); + const creatorName = getMemberById( + Object.values(MEMBERS), + creator?.id, + ).name; - // check that the dialog is open - cy.get(`#${CLEAR_CHAT_DIALOG_ID}`); + cy.get(`#${ITEM_PANEL_TABLE_ID}`).should('exist').contains(creatorName); - // check that the buttons are there - cy.get(`#${CLEAR_CHAT_CONFIRM_BUTTON_ID}`) - .should('exist') - .and('be.visible') - .click(); + if (type === ItemType.LOCAL_FILE || type === ItemType.S3_FILE) { + const { mimetype, size } = getFileExtra(extra); + cy.get(`#${ITEM_PANEL_TABLE_ID}`).contains(mimetype); - cy.wait('@clearItemChat'); + cy.get(`#${ITEM_PANEL_TABLE_ID}`).contains(size); + } + }); }); - it('Unauthorized to clear Chat', () => { - const itemId = ITEM_WITH_CHATBOX_MESSAGES.id; - // navigate to the item - cy.visit(buildItemPath(itemId)); - // open the settings of the item - cy.get(`.${ITEM_SETTINGS_BUTTON_CLASS}`).click(); - - // check that the clear button is not shown - cy.get(`#${CLEAR_CHAT_SETTING_ID}`).should('not.exist'); - - // check that the download button is not shown - cy.get(`#${DOWNLOAD_CHAT_BUTTON_ID}`).should('not.exist'); + describe('Language', () => { + it('change item language', () => { + const { id, lang } = ITEMS_SETTINGS.items[1]; + cy.visit(buildItemSettingsPath(id)); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + const langName = langs[lang]; + cy.get(`#${LANGUAGE_SELECTOR_ID}`).should('contain', langName); + cy.get(`#${LANGUAGE_SELECTOR_ID}`).click(); + cy.get(`[role="option"][data-value="de"]`).click(); + + cy.wait('@editItem').then(({ request: { body } }) => { + expect(body.lang).to.equal('de'); + }); + }); }); - }); - describe('Pinned Settings', () => { - it('Unpin Items', () => { - const itemId = ITEMS_SETTINGS.items[1].id; + describe('Chatbox Settings', () => { + it('Disabling Chatbox', () => { + const itemId = ITEMS_SETTINGS.items[1].id; - cy.visit(buildItemPath(itemId)); - cy.get(`.${ITEM_SETTINGS_BUTTON_CLASS}`).click(); + cy.visit(buildItemSettingsPath(itemId)); - cy.get(`#${SETTINGS_PINNED_TOGGLE_ID}`).should('be.checked'); + cy.get(`#${SETTINGS_CHATBOX_TOGGLE_ID}`).should('be.checked'); - cy.get(`#${SETTINGS_PINNED_TOGGLE_ID}`).click(); + cy.get(`#${SETTINGS_CHATBOX_TOGGLE_ID}`).click(); - cy.wait('@editItem').then( - ({ - response: { - body: { settings }, + cy.wait('@editItem').then( + ({ + response: { + body: { settings }, + }, + }) => { + expect(settings?.showChatbox).equals(false); + cy.wait(EDIT_ITEM_PAUSE); + cy.get('@getItem').its('response.url').should('contain', itemId); }, - }) => { - expect(settings.isPinned).equals(false); - cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getItem').its('response.url').should('contain', itemId); - }, - ); - }); + ); + }); - it('Pin Item', () => { - const itemId = ITEMS_SETTINGS.items[2].id; - cy.visit(buildItemPath(itemId)); - cy.get(`.${ITEM_SETTINGS_BUTTON_CLASS}`).click(); + it('Enabling Chatbox', () => { + const itemId = ITEMS_SETTINGS.items[2].id; - cy.get(`#${SETTINGS_PINNED_TOGGLE_ID}`).should('not.be.checked'); + cy.visit(buildItemSettingsPath(itemId)); - cy.get(`#${SETTINGS_PINNED_TOGGLE_ID}`).click(); + cy.get(`#${SETTINGS_CHATBOX_TOGGLE_ID}`).should('not.be.checked'); - cy.wait('@editItem').then( - ({ - response: { - body: { settings }, + cy.get(`#${SETTINGS_CHATBOX_TOGGLE_ID}`).click(); + + cy.wait('@editItem').then( + ({ + response: { + body: { settings }, + }, + }) => { + expect(settings?.showChatbox).equals(true); + cy.wait(EDIT_ITEM_PAUSE); + cy.get('@getItem').its('response.url').should('contain', itemId); }, - }) => { - expect(settings.isPinned).equals(true); - cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getItem').its('response.url').should('contain', itemId); - }, - ); + ); + }); + + it('Clear Chat', () => { + const itemId = ITEM_WITH_CHATBOX_MESSAGES_AND_ADMIN.id; + // navigate to the item settings + cy.visit(buildItemSettingsPath(itemId)); + + // click on the clear chat button + cy.get(`#${CLEAR_CHAT_SETTING_ID}`).scrollIntoView(); + cy.get(`#${CLEAR_CHAT_SETTING_ID}`).should('exist').and('be.visible'); + cy.get(`#${CLEAR_CHAT_SETTING_ID}`).click(); + + // check that the dialog is open + cy.get(`#${CLEAR_CHAT_DIALOG_ID}`); + + // check that the buttons are there + cy.get(`#${CLEAR_CHAT_CONFIRM_BUTTON_ID}`) + .should('exist') + .and('be.visible') + .click(); + + cy.wait('@clearItemChat'); + }); + + it('Unauthorized to clear Chat', () => { + const itemId = ITEM_WITH_CHATBOX_MESSAGES.id; + // navigate to the item settings + cy.visit(buildItemSettingsPath(itemId)); + + // check that the clear button is not shown + cy.get(`#${CLEAR_CHAT_SETTING_ID}`).should('not.exist'); + + // check that the download button is not shown + cy.get(`#${DOWNLOAD_CHAT_BUTTON_ID}`).should('not.exist'); + }); }); - }); - describe('Analytics Settings', () => { - it('Layout', () => { - const itemId = ITEMS_SETTINGS.items[2].id; - cy.visit(buildItemPath(itemId)); - cy.get(`.${ITEM_SETTINGS_BUTTON_CLASS}`).click(); + describe('Pinned Settings', () => { + it('Unpin items', () => { + const itemId = ITEMS_SETTINGS.items[1].id; - cy.get(`#${SETTINGS_SAVE_ACTIONS_TOGGLE_ID}`) - .should('exist') - .should('be.disabled') - .should('not.be.checked'); - }); - }); + cy.visit(buildItemSettingsPath(itemId)); - describe('Link Settings', () => { - it('Does not show link settings for folder item', () => { - const itemId = ITEMS_SETTINGS.items[0].id; + cy.get(`#${SETTINGS_PINNED_TOGGLE_ID}`).should('be.checked'); - cy.visit(buildItemPath(itemId)); - cy.get(`.${ITEM_SETTINGS_BUTTON_CLASS}`).click(); + cy.get(`#${SETTINGS_PINNED_TOGGLE_ID}`).click(); - cy.get(`#${SETTINGS_LINK_SHOW_IFRAME_ID}`).should('not.exist'); - cy.get(`#${SETTINGS_LINK_SHOW_BUTTON_ID}`).should('not.exist'); + cy.wait('@editItem').then( + ({ + response: { + body: { settings }, + }, + }) => { + expect(settings.isPinned).equals(false); + cy.wait(EDIT_ITEM_PAUSE); + cy.get('@getItem').its('response.url').should('contain', itemId); + }, + ); + }); + + it('Pin Item', () => { + const itemId = ITEMS_SETTINGS.items[2].id; + cy.visit(buildItemSettingsPath(itemId)); + cy.get(`#${SETTINGS_PINNED_TOGGLE_ID}`).should('not.be.checked'); + + cy.get(`#${SETTINGS_PINNED_TOGGLE_ID}`).click(); + + cy.wait('@editItem').then( + ({ + response: { + body: { settings }, + }, + }) => { + expect(settings.isPinned).equals(true); + cy.wait(EDIT_ITEM_PAUSE); + cy.get('@getItem').its('response.url').should('contain', itemId); + }, + ); + }); }); - it('Toggle Iframe', () => { - const itemId = GRAASP_LINK_ITEM.id; - - cy.visit(buildItemPath(itemId)); - cy.get(`.${ITEM_SETTINGS_BUTTON_CLASS}`).click(); - - cy.get(`#${SETTINGS_LINK_SHOW_IFRAME_ID}`).should('not.be.checked'); - - cy.get(`#${SETTINGS_LINK_SHOW_IFRAME_ID}`).click(); + describe('Analytics Settings', () => { + it('Layout', () => { + const itemId = ITEMS_SETTINGS.items[2].id; + cy.visit(buildItemSettingsPath(itemId)); - cy.wait('@editItem').then( - ({ - response: { - body: { settings }, - }, - }) => { - expect(settings.showLinkIframe).equals(true); - cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getItem').its('response.url').should('contain', itemId); - }, - ); + cy.get(`#${SETTINGS_SAVE_ACTIONS_TOGGLE_ID}`) + .should('exist') + .should('be.disabled') + .should('not.be.checked'); + }); }); - it('Toggle Button', () => { - const itemId = GRAASP_LINK_ITEM.id; - cy.visit(buildItemPath(itemId)); - cy.get(`.${ITEM_SETTINGS_BUTTON_CLASS}`).click(); - - cy.get(`#${SETTINGS_LINK_SHOW_BUTTON_ID}`).should('be.checked'); + describe('Link Settings', () => { + it('Does not show link settings for folder item', () => { + const itemId = ITEMS_SETTINGS.items[0].id; - cy.get(`#${SETTINGS_LINK_SHOW_BUTTON_ID}`).click(); + cy.visit(buildItemSettingsPath(itemId)); - cy.wait('@editItem').then( - ({ - response: { - body: { settings }, - }, - }) => { - expect(settings.showLinkButton).equals(false); - cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getItem').its('response.url').should('contain', itemId); - }, - ); - }); - }); + cy.get(`#${SETTINGS_LINK_SHOW_IFRAME_ID}`).should('not.exist'); + cy.get(`#${SETTINGS_LINK_SHOW_BUTTON_ID}`).should('not.exist'); + }); - describe('File Settings', () => { - it('Change default maximum width', () => { - const itemId = IMAGE_ITEM_DEFAULT.id; + it('Toggle Iframe', () => { + const itemId = GRAASP_LINK_ITEM.id; - cy.visit(buildItemPath(itemId)); - cy.get(`.${ITEM_SETTINGS_BUTTON_CLASS}`).click(); + cy.visit(buildItemSettingsPath(itemId)); - // default value - cy.get(`#${FILE_SETTING_MAX_WIDTH_ID} + input`).should( - 'have.value', - MaxWidth.ExtraLarge, - ); + cy.get(`#${SETTINGS_LINK_SHOW_IFRAME_ID}`).should('not.be.checked'); - const newMaxWidth = MaxWidth.Small; - cy.get(`#${FILE_SETTING_MAX_WIDTH_ID}`).click(); - cy.get(`[role="option"][data-value="${newMaxWidth}"]`).click(); + cy.get(`#${SETTINGS_LINK_SHOW_IFRAME_ID}`).click(); - cy.wait('@editItem').then( - ({ - response: { - body: { settings }, + cy.wait('@editItem').then( + ({ + response: { + body: { settings }, + }, + }) => { + expect(settings.showLinkIframe).equals(true); + cy.wait(EDIT_ITEM_PAUSE); + cy.get('@getItem').its('response.url').should('contain', itemId); }, - }) => { - expect(settings.maxWidth).equals(newMaxWidth); - cy.wait(EDIT_ITEM_PAUSE); - cy.get('@getItem').its('response.url').should('contain', itemId); - }, - ); + ); + }); + + it('Toggle Button', () => { + const itemId = GRAASP_LINK_ITEM.id; + cy.visit(buildItemSettingsPath(itemId)); + + cy.get(`#${SETTINGS_LINK_SHOW_BUTTON_ID}`).should('be.checked'); + + cy.get(`#${SETTINGS_LINK_SHOW_BUTTON_ID}`).click(); + + cy.wait('@editItem').then( + ({ + response: { + body: { settings }, + }, + }) => { + expect(settings.showLinkButton).equals(false); + cy.wait(EDIT_ITEM_PAUSE); + cy.get('@getItem').its('response.url').should('contain', itemId); + }, + ); + }); }); - it('Shows set maximum width for file', () => { - const itemId = IMAGE_ITEM_DEFAULT_WITH_MAX_WIDTH.id; + describe('File Settings', () => { + it('Change default maximum width', () => { + const itemId = IMAGE_ITEM_DEFAULT.id; + + cy.visit(buildItemSettingsPath(itemId)); + + // default value + cy.get(`#${FILE_SETTING_MAX_WIDTH_ID} + input`).should( + 'have.value', + MaxWidth.ExtraLarge, + ); + + const newMaxWidth = MaxWidth.Small; + cy.get(`#${FILE_SETTING_MAX_WIDTH_ID}`).click(); + cy.get(`[role="option"][data-value="${newMaxWidth}"]`).click(); + + cy.wait('@editItem').then( + ({ + response: { + body: { settings }, + }, + }) => { + expect(settings.maxWidth).equals(newMaxWidth); + cy.wait(EDIT_ITEM_PAUSE); + cy.get('@getItem').its('response.url').should('contain', itemId); + }, + ); + }); - cy.visit(buildItemPath(itemId)); - cy.get(`.${ITEM_SETTINGS_BUTTON_CLASS}`).click(); + it('Shows set maximum width for file', () => { + const itemId = IMAGE_ITEM_DEFAULT_WITH_MAX_WIDTH.id; + + cy.visit(buildItemSettingsPath(itemId)); - cy.get(`#${FILE_SETTING_MAX_WIDTH_ID} + input`).should( - 'have.value', - IMAGE_ITEM_DEFAULT_WITH_MAX_WIDTH.settings.maxWidth, - ); + cy.get(`#${FILE_SETTING_MAX_WIDTH_ID} + input`).should( + 'have.value', + IMAGE_ITEM_DEFAULT_WITH_MAX_WIDTH.settings.maxWidth, + ); + }); }); }); }); diff --git a/cypress/e2e/item/thumbnails/uploadThumbnail.cy.ts b/cypress/e2e/item/settings/uploadThumbnail.cy.ts similarity index 81% rename from cypress/e2e/item/thumbnails/uploadThumbnail.cy.ts rename to cypress/e2e/item/settings/uploadThumbnail.cy.ts index 77fe38174..1b68a7fda 100644 --- a/cypress/e2e/item/thumbnails/uploadThumbnail.cy.ts +++ b/cypress/e2e/item/settings/uploadThumbnail.cy.ts @@ -1,7 +1,6 @@ -import { buildItemPath } from '../../../../src/config/paths'; +import { buildItemSettingsPath } from '../../../../src/config/paths'; import { CROP_MODAL_CONFIRM_BUTTON_CLASSNAME, - ITEM_INFORMATION_BUTTON_ID, THUMBNAIL_SETTING_UPLOAD_BUTTON_CLASSNAME, } from '../../../../src/config/selectors'; import { @@ -17,8 +16,7 @@ describe('Upload Thumbnails', () => { it(`upload item thumbnail`, () => { const { items } = SAMPLE_ITEMS_WITH_THUMBNAILS; - cy.visit(buildItemPath(items[0].id)); - cy.get(`#${ITEM_INFORMATION_BUTTON_ID}`).click(); + cy.visit(buildItemSettingsPath(items[0].id)); // change item thumbnail // selectFile ??? diff --git a/cypress/e2e/item/thumbnails/viewThumbnails.cy.ts b/cypress/e2e/item/view/viewThumbnails.cy.ts similarity index 100% rename from cypress/e2e/item/thumbnails/viewThumbnails.cy.ts rename to cypress/e2e/item/view/viewThumbnails.cy.ts diff --git a/cypress/fixtures/documents.ts b/cypress/fixtures/documents.ts index 841045e86..91ed8acf9 100644 --- a/cypress/fixtures/documents.ts +++ b/cypress/fixtures/documents.ts @@ -1,11 +1,16 @@ -import { DocumentItemType, Item, ItemType } from '@graasp/sdk'; +import { + DocumentItemFactory, + DocumentItemType, + Item, + ItemType, +} from '@graasp/sdk'; import { DEFAULT_LANG } from '@graasp/translations'; import { buildDocumentExtra } from '../../src/utils/itemExtra'; import { DEFAULT_FOLDER_ITEM } from './items'; import { CURRENT_USER } from './members'; -export const GRAASP_DOCUMENT_ITEM: DocumentItemType = { +export const GRAASP_DOCUMENT_ITEM: DocumentItemType = DocumentItemFactory({ id: 'ecafbd2a-5688-12eb-ae91-0242ac130002', type: ItemType.DOCUMENT, name: 'graasp text', @@ -19,23 +24,23 @@ export const GRAASP_DOCUMENT_ITEM: DocumentItemType = { extra: buildDocumentExtra({ content: '

Some Title

', }), -}; +}); -export const GRAASP_DOCUMENT_BLANK_NAME_ITEM: DocumentItemType = { - id: 'ecafbd2a-5688-12eb-ae91-0242ac130004', - type: ItemType.DOCUMENT, - name: ' ', - description: 'a description for graasp text', - path: 'ecafbd2a_5688_12eb_ae93_0242ac130002', - settings: {}, - createdAt: '2021-08-11T12:56:36.834Z', - updatedAt: '2021-08-11T12:56:36.834Z', - lang: DEFAULT_LANG, - creator: CURRENT_USER, - extra: buildDocumentExtra({ - content: '

Some Title

', - }), -}; +export const GRAASP_DOCUMENT_BLANK_NAME_ITEM: DocumentItemType = + DocumentItemFactory({ + id: 'ecafbd2a-5688-12eb-ae91-0242ac130004', + type: ItemType.DOCUMENT, + name: ' ', + description: 'a description for graasp text', + path: 'ecafbd2a_5688_12eb_ae93_0242ac130002', + settings: {}, + createdAt: '2021-08-11T12:56:36.834Z', + updatedAt: '2021-08-11T12:56:36.834Z', + creator: CURRENT_USER, + extra: buildDocumentExtra({ + content: '

Some Title

', + }), + }); export const GRAASP_DOCUMENT_PARENT_FOLDER: Item = { ...DEFAULT_FOLDER_ITEM, @@ -44,21 +49,20 @@ export const GRAASP_DOCUMENT_PARENT_FOLDER: Item = { path: 'bdf09f5a_5688_11eb_ae93_0242ac130002', }; -export const GRAASP_DOCUMENT_CHILDREN_ITEM: DocumentItemType = { - id: '1cafbd2a-5688-12eb-ae91-0242ac130002', - type: ItemType.DOCUMENT, - name: 'children graasp text', - description: 'a description for graasp text', - path: 'bdf09f5a_5688_11eb_ae93_0242ac130002.1cafbd2a_5688_12eb_ae93_0242ac130002', - creator: CURRENT_USER, - settings: {}, - lang: DEFAULT_LANG, - createdAt: '2021-08-11T12:56:36.834Z', - updatedAt: '2021-08-11T12:56:36.834Z', - extra: buildDocumentExtra({ - content: '

Some Title

', - }), -}; +export const GRAASP_DOCUMENT_CHILDREN_ITEM: DocumentItemType = + DocumentItemFactory({ + type: ItemType.DOCUMENT, + name: 'children graasp text', + description: 'a description for graasp text', + creator: CURRENT_USER, + settings: {}, + createdAt: '2021-08-11T12:56:36.834Z', + updatedAt: '2021-08-11T12:56:36.834Z', + extra: buildDocumentExtra({ + content: '

Some Title

', + }), + parentItem: GRAASP_DOCUMENT_PARENT_FOLDER, + }); export const GRAASP_DOCUMENT_ITEMS_FIXTURE = [ GRAASP_DOCUMENT_ITEM, diff --git a/cypress/fixtures/etherpad.ts b/cypress/fixtures/etherpad.ts index c3a492464..c279ebd2d 100644 --- a/cypress/fixtures/etherpad.ts +++ b/cypress/fixtures/etherpad.ts @@ -1,10 +1,10 @@ -import { EtherpadItemType, ItemType } from '@graasp/sdk'; +import { EtherpadItemFactory, EtherpadItemType, ItemType } from '@graasp/sdk'; import { DEFAULT_LANG } from '@graasp/translations'; import { CURRENT_USER } from './members'; // eslint-disable-next-line import/prefer-default-export -export const GRAASP_ETHERPAD_ITEM: EtherpadItemType = { +export const GRAASP_ETHERPAD_ITEM: EtherpadItemType = EtherpadItemFactory({ id: 'ecaf1d2a-5688-11eb-ae91-0242ac130002', type: ItemType.ETHERPAD, name: 'graasp etherpad', @@ -21,4 +21,4 @@ export const GRAASP_ETHERPAD_ITEM: EtherpadItemType = { groupID: 'groupId', }, }, -}; +}); diff --git a/cypress/fixtures/files.ts b/cypress/fixtures/files.ts index a3d9775b0..c4b84efb6 100644 --- a/cypress/fixtures/files.ts +++ b/cypress/fixtures/files.ts @@ -1,5 +1,10 @@ -import { ItemType, MaxWidth, MimeTypes } from '@graasp/sdk'; -import { DEFAULT_LANG } from '@graasp/translations'; +import { + ItemType, + LocalFileItemFactory, + MaxWidth, + MimeTypes, + S3FileItemFactory, +} from '@graasp/sdk'; import { InternalItemType } from '../../src/config/types'; import { buildFileExtra, buildS3FileExtra } from '../../src/utils/itemExtra'; @@ -12,23 +17,24 @@ export const VIDEO_FILEPATH = 'files/video.mp4'; export const TEXT_FILEPATH = 'files/sometext.txt'; export const IMAGE_ITEM_DEFAULT: LocalFileItemForTest = { - id: 'bd5519a2-5ba9-4305-b221-185facbe6a99', - name: 'icon.png', - description: 'a default image description', - type: ItemType.LOCAL_FILE, - path: 'bd5519a2_5ba9_4305_b221_185facbe6a99', - creator: CURRENT_USER, - createdAt: '2021-03-16T16:00:50.968Z', - updatedAt: '2021-03-16T16:00:52.655Z', - settings: {}, - lang: DEFAULT_LANG, - extra: buildFileExtra({ + ...LocalFileItemFactory({ + id: 'bd5519a2-5ba9-4305-b221-185facbe6a99', name: 'icon.png', - path: '9a95/e2e1/2a7b-1615910428274', - size: 32439, - mimetype: 'image/png', - altText: 'myAltText', - content: '', + description: 'a default image description', + type: ItemType.LOCAL_FILE, + path: 'bd5519a2_5ba9_4305_b221_185facbe6a99', + creator: CURRENT_USER, + createdAt: '2021-03-16T16:00:50.968Z', + updatedAt: '2021-03-16T16:00:52.655Z', + settings: {}, + extra: buildFileExtra({ + name: 'icon.png', + path: '9a95/e2e1/2a7b-1615910428274', + size: 32439, + mimetype: 'image/png', + altText: 'myAltText', + content: '', + }), }), // for testing: creating needs a fixture, reading needs an url createFilepath: ICON_FILEPATH, @@ -36,25 +42,26 @@ export const IMAGE_ITEM_DEFAULT: LocalFileItemForTest = { }; export const IMAGE_ITEM_DEFAULT_WITH_MAX_WIDTH: LocalFileItemForTest = { - id: 'bd5519a2-5ba9-4305-b221-185facbe6a29', - name: 'icon.png', - description: 'a default image description', - type: ItemType.LOCAL_FILE, - path: 'bd5519a2_5ba9_4305_b221_185facbe6a29', - creator: CURRENT_USER, - createdAt: '2021-03-16T16:00:50.968Z', - updatedAt: '2021-03-16T16:00:52.655Z', - settings: { - maxWidth: MaxWidth.Medium, - }, - lang: DEFAULT_LANG, - extra: buildFileExtra({ + ...LocalFileItemFactory({ + id: 'bd5519a2-5ba9-4305-b221-185facbe6a29', name: 'icon.png', - path: '9a95/e2e1/2a7b-1615910428274', - size: 32439, - mimetype: 'image/png', - altText: 'myAltText', - content: '', + description: 'a default image description', + type: ItemType.LOCAL_FILE, + path: 'bd5519a2_5ba9_4305_b221_185facbe6a29', + creator: CURRENT_USER, + createdAt: '2021-03-16T16:00:50.968Z', + updatedAt: '2021-03-16T16:00:52.655Z', + settings: { + maxWidth: MaxWidth.Medium, + }, + extra: buildFileExtra({ + name: 'icon.png', + path: '9a95/e2e1/2a7b-1615910428274', + size: 32439, + mimetype: 'image/png', + altText: 'myAltText', + content: '', + }), }), // for testing: creating needs a fixture, reading needs an url createFilepath: ICON_FILEPATH, @@ -62,23 +69,24 @@ export const IMAGE_ITEM_DEFAULT_WITH_MAX_WIDTH: LocalFileItemForTest = { }; export const VIDEO_ITEM_DEFAULT: LocalFileItemForTest = { - id: 'qd5519a2-5ba9-4305-b221-185facbe6a99', - name: 'video.mp4', - description: 'a default video description', - type: ItemType.LOCAL_FILE, - path: 'qd5519a2_5ba9_4305_b221_185facbe6a99', - creator: CURRENT_USER, - createdAt: '2021-03-16T16:00:50.968Z', - updatedAt: '2021-03-16T16:00:52.655Z', - settings: {}, - lang: DEFAULT_LANG, - extra: buildFileExtra({ + ...LocalFileItemFactory({ + id: 'qd5519a2-5ba9-4305-b221-185facbe6a99', name: 'video.mp4', - path: '9a95/e2e1/2a7b-1615910428274', - size: 52345, - mimetype: MimeTypes.Video.MP4, - altText: 'myAltText', - content: '', + description: 'a default video description', + type: ItemType.LOCAL_FILE, + path: 'qd5519a2_5ba9_4305_b221_185facbe6a99', + creator: CURRENT_USER, + createdAt: '2021-03-16T16:00:50.968Z', + updatedAt: '2021-03-16T16:00:52.655Z', + settings: {}, + extra: buildFileExtra({ + name: 'video.mp4', + path: '9a95/e2e1/2a7b-1615910428274', + size: 52345, + mimetype: MimeTypes.Video.MP4, + altText: 'myAltText', + content: '', + }), }), // for testing: creating needs a fixture, reading needs an url createFilepath: VIDEO_FILEPATH, @@ -86,23 +94,24 @@ export const VIDEO_ITEM_DEFAULT: LocalFileItemForTest = { }; export const PDF_ITEM_DEFAULT: LocalFileItemForTest = { - id: 'cd5519a2-5ba9-4305-b221-185facbe6a99', - name: 'doc.pdf', - description: 'a default pdf description', - type: ItemType.LOCAL_FILE, - path: 'cd5519a2_5ba9_4305_b221_185facbe6a99', - creator: CURRENT_USER, - createdAt: '2021-03-16T16:00:50.968Z', - updatedAt: '2021-03-16T16:00:52.655Z', - settings: {}, - lang: DEFAULT_LANG, - extra: buildFileExtra({ + ...LocalFileItemFactory({ + id: 'cd5519a2-5ba9-4305-b221-185facbe6a99', name: 'doc.pdf', - path: '9a95/e2e1/2a7b-1615910428274', - size: 54321, - mimetype: MimeTypes.PDF, - altText: 'myAltText', - content: '', + description: 'a default pdf description', + type: ItemType.LOCAL_FILE, + path: 'cd5519a2_5ba9_4305_b221_185facbe6a99', + creator: CURRENT_USER, + createdAt: '2021-03-16T16:00:50.968Z', + updatedAt: '2021-03-16T16:00:52.655Z', + settings: {}, + extra: buildFileExtra({ + name: 'doc.pdf', + path: '9a95/e2e1/2a7b-1615910428274', + size: 54321, + mimetype: MimeTypes.PDF, + altText: 'myAltText', + content: '', + }), }), // for testing: creating needs a fixture, reading needs an url createFilepath: ICON_FILEPATH, @@ -115,23 +124,24 @@ export const ZIP_DEFAULT: ZIPInternalItem = { }; export const IMAGE_ITEM_S3: S3FileItemForTest = { - id: 'ad5519a2-5ba9-4305-b221-185facbe6a99', - name: 'icon.png', - description: 'a default image description', - type: ItemType.S3_FILE, - path: 'ad5519a2_5ba9_4305_b221_185facbe6a99', - creator: CURRENT_USER, - createdAt: '2021-03-16T16:00:50.968Z', - updatedAt: '2021-03-16T16:00:52.655Z', - settings: {}, - lang: DEFAULT_LANG, - extra: buildS3FileExtra({ - path: MOCK_IMAGE_URL, // for testing - size: 32439, - mimetype: MimeTypes.Image.PNG, - name: 'myfile', - altText: 'myAltText', - content: '', + ...S3FileItemFactory({ + id: 'ad5519a2-5ba9-4305-b221-185facbe6a99', + name: 'icon.png', + description: 'a default image description', + type: ItemType.S3_FILE, + path: 'ad5519a2_5ba9_4305_b221_185facbe6a99', + creator: CURRENT_USER, + createdAt: '2021-03-16T16:00:50.968Z', + updatedAt: '2021-03-16T16:00:52.655Z', + settings: {}, + extra: buildS3FileExtra({ + path: MOCK_IMAGE_URL, // for testing + size: 32439, + mimetype: MimeTypes.Image.PNG, + name: 'myfile', + altText: 'myAltText', + content: '', + }), }), // for testing: creating needs a fixture, reading needs an url createFilepath: ICON_FILEPATH, @@ -139,23 +149,24 @@ export const IMAGE_ITEM_S3: S3FileItemForTest = { }; export const VIDEO_ITEM_S3: S3FileItemForTest = { - id: 'qd5519a2-5ba9-4305-b221-185facbe6a93', - name: 'video.mp4', - description: 'a default video description', - type: ItemType.S3_FILE, - path: 'qd5519a2_5ba9_4305_b221_185facbe6a93', - creator: CURRENT_USER, - createdAt: '2021-03-16T16:00:50.968Z', - updatedAt: '2021-03-16T16:00:52.655Z', - settings: {}, - lang: DEFAULT_LANG, - extra: buildS3FileExtra({ - path: MOCK_VIDEO_URL, // for testing - size: 52345, - mimetype: MimeTypes.Video.MP4, - name: 'myfile', - altText: 'myAltText', - content: '', + ...S3FileItemFactory({ + id: 'qd5519a2-5ba9-4305-b221-185facbe6a93', + name: 'video.mp4', + description: 'a default video description', + type: ItemType.S3_FILE, + path: 'qd5519a2_5ba9_4305_b221_185facbe6a93', + creator: CURRENT_USER, + createdAt: '2021-03-16T16:00:50.968Z', + updatedAt: '2021-03-16T16:00:52.655Z', + settings: {}, + extra: buildS3FileExtra({ + path: MOCK_VIDEO_URL, // for testing + size: 52345, + mimetype: MimeTypes.Video.MP4, + name: 'myfile', + altText: 'myAltText', + content: '', + }), }), // for testing: creating needs a fixture, reading needs an url createFilepath: VIDEO_FILEPATH, @@ -163,23 +174,24 @@ export const VIDEO_ITEM_S3: S3FileItemForTest = { }; export const PDF_ITEM_S3: S3FileItemForTest = { - id: 'bd5519a2-5ba9-4305-b221-185facbe6a99', - name: 'doc.pdf', - description: 'a default pdf description', - type: ItemType.S3_FILE, - path: 'bd5519a2_5ba9_4305_b221_185facbe6a99', - creator: CURRENT_USER, - createdAt: '2021-03-16T16:00:50.968Z', - updatedAt: '2021-03-16T16:00:52.655Z', - settings: {}, - lang: DEFAULT_LANG, - extra: buildS3FileExtra({ - path: MOCK_PDF_URL, // for testing - size: 54321, - mimetype: MimeTypes.PDF, - name: 'myfile', - altText: 'myAltText', - content: '', + ...S3FileItemFactory({ + id: 'bd5519a2-5ba9-4305-b221-185facbe6a99', + name: 'doc.pdf', + description: 'a default pdf description', + type: ItemType.S3_FILE, + path: 'bd5519a2_5ba9_4305_b221_185facbe6a99', + creator: CURRENT_USER, + createdAt: '2021-03-16T16:00:50.968Z', + updatedAt: '2021-03-16T16:00:52.655Z', + settings: {}, + extra: buildS3FileExtra({ + path: MOCK_PDF_URL, // for testing + size: 54321, + mimetype: MimeTypes.PDF, + name: 'myfile', + altText: 'myAltText', + content: '', + }), }), // for testing: creating needs a fixture, reading needs an url createFilepath: ICON_FILEPATH, diff --git a/cypress/fixtures/h5p.ts b/cypress/fixtures/h5p.ts index e23e07fe0..c7376b316 100644 --- a/cypress/fixtures/h5p.ts +++ b/cypress/fixtures/h5p.ts @@ -1,10 +1,9 @@ -import { H5PItemType, ItemType } from '@graasp/sdk'; -import { DEFAULT_LANG } from '@graasp/translations'; +import { H5PItemFactory, H5PItemType, ItemType } from '@graasp/sdk'; import { CURRENT_USER } from './members'; // eslint-disable-next-line import/prefer-default-export -export const GRAASP_H5P_ITEM: H5PItemType = { +export const GRAASP_H5P_ITEM: H5PItemType = H5PItemFactory({ id: 'ecaf1d2a-5688-11eb-ae91-0242ac130002', type: ItemType.H5P, name: 'graasp h5p', @@ -12,7 +11,6 @@ export const GRAASP_H5P_ITEM: H5PItemType = { path: 'ecafbd2a_5688_11eb_ae93_0242ac130002', creator: CURRENT_USER, settings: {}, - lang: DEFAULT_LANG, createdAt: '2021-08-11T12:56:36.834Z', updatedAt: '2021-08-11T12:56:36.834Z', extra: { @@ -22,4 +20,4 @@ export const GRAASP_H5P_ITEM: H5PItemType = { contentFilePath: 'contentFilePath', }, }, -}; +}); diff --git a/cypress/fixtures/items.ts b/cypress/fixtures/items.ts index 747013676..42c3c612c 100644 --- a/cypress/fixtures/items.ts +++ b/cypress/fixtures/items.ts @@ -1,5 +1,6 @@ import { DiscriminatedItem, + FolderItemFactory, FolderItemType, ItemFavorite, ItemLoginSchemaType, @@ -12,12 +13,11 @@ import { RecycledItemData, ShortcutItemType, } from '@graasp/sdk'; -import { DEFAULT_LANG } from '@graasp/translations'; import { ApiConfig, ItemForTest } from '../support/types'; import { CURRENT_USER, MEMBERS } from './members'; -export const DEFAULT_FOLDER_ITEM: FolderItemType = { +export const DEFAULT_FOLDER_ITEM: FolderItemType = FolderItemFactory({ id: 'folder-id', name: 'folder', path: 'folder-path', @@ -28,8 +28,7 @@ export const DEFAULT_FOLDER_ITEM: FolderItemType = { updatedAt: '2020-01-01T01:01:01Z', description: 'mydescription', settings: {}, - lang: DEFAULT_LANG, -}; +}); export const CREATED_ITEM: Partial = { name: 'created item', diff --git a/cypress/fixtures/links.ts b/cypress/fixtures/links.ts index 8f122e71b..fc0777d0c 100644 --- a/cypress/fixtures/links.ts +++ b/cypress/fixtures/links.ts @@ -1,10 +1,13 @@ -import { EmbeddedLinkItemType, ItemType } from '@graasp/sdk'; -import { DEFAULT_LANG } from '@graasp/translations'; +import { + EmbeddedLinkItemFactory, + EmbeddedLinkItemType, + ItemType, +} from '@graasp/sdk'; import { buildEmbeddedLinkExtra } from '../../src/utils/itemExtra'; import { CURRENT_USER } from './members'; -export const GRAASP_LINK_ITEM: EmbeddedLinkItemType = { +export const GRAASP_LINK_ITEM: EmbeddedLinkItemType = EmbeddedLinkItemFactory({ id: 'ecafbd2a-5688-11eb-ae91-0242ac130002', type: ItemType.LINK, name: 'graasp link', @@ -12,7 +15,6 @@ export const GRAASP_LINK_ITEM: EmbeddedLinkItemType = { path: 'ecafbd2a_5688_11eb_ae93_0242ac130002', creator: CURRENT_USER, settings: {}, - lang: DEFAULT_LANG, createdAt: '2021-08-11T12:56:36.834Z', updatedAt: '2021-08-11T12:56:36.834Z', extra: buildEmbeddedLinkExtra({ @@ -23,45 +25,45 @@ export const GRAASP_LINK_ITEM: EmbeddedLinkItemType = { 'https://graasp.eu/cdn/img/epfl/favicons/favicon-32x32.png?v=yyxJ380oWY', ], }), -}; +}); -export const GRAASP_LINK_ITEM_NO_PROTOCOL: EmbeddedLinkItemType = { - id: 'ecafbd2a-5688-11eb-ae91-0242ac130002', - type: ItemType.LINK, - name: 'graasp link', - description: 'a description for graasp link', - path: 'ecafbd2a_5688_11eb_ae93_0242ac130002', - creator: CURRENT_USER, - settings: {}, - lang: DEFAULT_LANG, - createdAt: '2021-08-11T12:56:36.834Z', - updatedAt: '2021-08-11T12:56:36.834Z', - extra: buildEmbeddedLinkExtra({ - url: 'graasp.eu', - html: '', - thumbnails: ['https://graasp.eu/img/epfl/logo-tile.png'], - icons: [ - 'https://graasp.eu/cdn/img/epfl/favicons/favicon-32x32.png?v=yyxJ380oWY', - ], - }), -}; +export const GRAASP_LINK_ITEM_NO_PROTOCOL: EmbeddedLinkItemType = + EmbeddedLinkItemFactory({ + id: 'ecafbd2a-5688-11eb-ae91-0242ac130002', + type: ItemType.LINK, + name: 'graasp link', + description: 'a description for graasp link', + path: 'ecafbd2a_5688_11eb_ae93_0242ac130002', + creator: CURRENT_USER, + settings: {}, + createdAt: '2021-08-11T12:56:36.834Z', + updatedAt: '2021-08-11T12:56:36.834Z', + extra: buildEmbeddedLinkExtra({ + url: 'graasp.eu', + html: '', + thumbnails: ['https://graasp.eu/img/epfl/logo-tile.png'], + icons: [ + 'https://graasp.eu/cdn/img/epfl/favicons/favicon-32x32.png?v=yyxJ380oWY', + ], + }), + }); -export const GRAASP_LINK_ITEM_IFRAME_ONLY: EmbeddedLinkItemType = { - ...GRAASP_LINK_ITEM, - id: 'ecafbd2a-5688-11eb-ae91-0242ac130122', - settings: { - showLinkIframe: true, - showLinkButton: false, - }, -}; +export const GRAASP_LINK_ITEM_IFRAME_ONLY: EmbeddedLinkItemType = + EmbeddedLinkItemFactory({ + ...GRAASP_LINK_ITEM, + id: 'ecafbd2a-5688-11eb-ae91-0242ac130122', + settings: { + showLinkIframe: true, + showLinkButton: false, + }, + }); -export const YOUTUBE_LINK_ITEM: EmbeddedLinkItemType = { +export const YOUTUBE_LINK_ITEM: EmbeddedLinkItemType = EmbeddedLinkItemFactory({ id: 'gcafbd2a-5688-11eb-ae93-0242ac130002', type: ItemType.LINK, name: 'graasp youtube link', description: 'a description for graasp youtube link', settings: {}, - lang: DEFAULT_LANG, createdAt: '2021-08-11T12:56:36.834Z', updatedAt: '2021-08-11T12:56:36.834Z', creator: CURRENT_USER, @@ -72,15 +74,14 @@ export const YOUTUBE_LINK_ITEM: EmbeddedLinkItemType = { thumbnails: ['https://i.ytimg.com/vi/FmiEgBMTPLo/maxresdefault.jpg'], icons: ['https://www.youtube.com/s/desktop/f0ff6c1d/img/favicon_96.png'], }), -}; +}); -export const INVALID_LINK_ITEM: EmbeddedLinkItemType = { +export const INVALID_LINK_ITEM: EmbeddedLinkItemType = EmbeddedLinkItemFactory({ id: 'gcafbd2a-5688-11eb-ae93-0242ac130001', path: 'gcafbd2a_5688_11eb_ae93_0242ac130001', type: ItemType.LINK, creator: CURRENT_USER, settings: {}, - lang: DEFAULT_LANG, createdAt: '2021-08-11T12:56:36.834Z', updatedAt: '2021-08-11T12:56:36.834Z', name: 'graasp youtube link', @@ -91,4 +92,4 @@ export const INVALID_LINK_ITEM: EmbeddedLinkItemType = { thumbnails: [], icons: [], }), -}; +}); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 1a1566470..6aaf90c92 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -5,7 +5,6 @@ import 'cypress-localstorage-commands'; import { DEFAULT_ITEM_LAYOUT_MODE } from '../../src/config/constants'; import { - ITEM_INFORMATION_BUTTON_ID, MODE_GRID_BUTTON_ID, MODE_LIST_BUTTON_ID, } from '../../src/config/selectors'; @@ -395,10 +394,6 @@ Cypress.Commands.add( ), ); -Cypress.Commands.add('openMetadataPanel', () => { - cy.get(`#${ITEM_INFORMATION_BUTTON_ID}`).click(); -}); - Cypress.Commands.add('attachFile', (selector, file, options = {}) => { selector.selectFile(`cypress/fixtures/${file}`, options); }); diff --git a/cypress/support/index.ts b/cypress/support/index.ts index b5223cdd9..b0b2573f3 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -28,8 +28,6 @@ declare global { text: string, ): void; - openMetadataPanel(): void; - attachFile(selector: Chainable, file: string, options?: object): void; attachFiles( selector: Chainable, diff --git a/cypress/support/viewUtils.ts b/cypress/support/viewUtils.ts index 32ec08fbc..92af44c8b 100644 --- a/cypress/support/viewUtils.ts +++ b/cypress/support/viewUtils.ts @@ -2,11 +2,8 @@ import { CompleteMember, DocumentItemType, EmbeddedLinkItemType, - ItemType, getDocumentExtra, getEmbeddedLinkExtra, - getFileExtra, - getS3FileExtra, } from '@graasp/sdk'; import { @@ -17,41 +14,16 @@ import { import { DOCUMENT_ITEM_TEXT_EDITOR_SELECTOR, ITEM_HEADER_ID, - ITEM_PANEL_NAME_ID, - ITEM_PANEL_TABLE_ID, TEXT_EDITOR_CLASS, buildEditButtonId, buildFileItemId, buildSettingsButtonId, buildShareButtonId, } from '../../src/config/selectors'; -import { getMemberById } from '../../src/utils/member'; import { isSettingsEditionAllowedForUser } from '../../src/utils/membership'; -import { CURRENT_USER, MEMBERS } from '../fixtures/members'; +import { CURRENT_USER } from '../fixtures/members'; import { ItemForTest, MemberForTest } from './types'; -const expectPanelLayout = ({ item }: { item: ItemForTest }) => { - cy.openMetadataPanel(); - - const { name, creator } = item; - - cy.get(`#${ITEM_PANEL_NAME_ID}`).contains(name); - - const creatorName = getMemberById(Object.values(MEMBERS), creator?.id).name; - - cy.get(`#${ITEM_PANEL_TABLE_ID}`).should('exist').contains(creatorName); - - if (item.type === ItemType.LOCAL_FILE || item.type === ItemType.S3_FILE) { - const { mimetype = 'invalid-mimetype', size = 'invalid-size' } = - item.type === ItemType.LOCAL_FILE - ? getFileExtra(item.extra) || {} - : getS3FileExtra(item.extra) || {}; - cy.get(`#${ITEM_PANEL_TABLE_ID}`).contains(mimetype); - - cy.get(`#${ITEM_PANEL_TABLE_ID}`).contains(size); - } -}; - export const expectItemHeaderLayout = ({ item: { id, type, memberships }, currentMember, @@ -88,9 +60,6 @@ export const expectDocumentViewScreenLayout = ({ }); expectItemHeaderLayout({ item, currentMember }); - expectPanelLayout({ - item, - }); }; export const expectFileViewScreenLayout = ({ @@ -106,8 +75,6 @@ export const expectFileViewScreenLayout = ({ cy.get(`.${TEXT_EDITOR_CLASS}`).should('contain', item.description); expectItemHeaderLayout({ item, currentMember }); - // table - expectPanelLayout({ item }); }; export const expectLinkViewScreenLayout = ({ @@ -140,8 +107,6 @@ export const expectLinkViewScreenLayout = ({ } expectItemHeaderLayout({ item, currentMember }); - // table - expectPanelLayout({ item }); }; export const expectFolderViewScreenLayout = ({ @@ -153,5 +118,4 @@ export const expectFolderViewScreenLayout = ({ }): void => { // table expectItemHeaderLayout({ item, currentMember }); - expectPanelLayout({ item }); }; diff --git a/docs/itemSettings.md b/docs/itemSettings.md new file mode 100644 index 000000000..a36a81054 --- /dev/null +++ b/docs/itemSettings.md @@ -0,0 +1,14 @@ +# Item Settings + +The settings page is accessible for at least **write** permission. Readers are redirected to the item page. + +The following settings are available: + +- item thumbnail +- item language +- tags +- geolocation +- pin the item +- collapse the item +- show chat in player +- enable analytics (disabled and default to true) diff --git a/package.json b/package.json index a0bba17e2..1f7ef1659 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,10 @@ "@emotion/react": "11.11.3", "@emotion/styled": "11.11.0", "@graasp/chatbox": "3.0.3", - "@graasp/query-client": "2.6.0", - "@graasp/sdk": "3.8.1", + "@graasp/query-client": "2.6.1", + "@graasp/sdk": "3.8.3", "@graasp/translations": "1.23.0", - "@graasp/ui": "4.7.0", + "@graasp/ui": "4.8.0", "@mui/icons-material": "5.15.8", "@mui/lab": "5.0.0-alpha.162", "@mui/material": "5.15.8", @@ -42,6 +42,7 @@ "filesize": "10.1.0", "http-status-codes": "2.3.0", "katex": "0.16.9", + "leaflet-geosearch": "3.11.0", "lodash.debounce": "4.0.8", "lodash.groupby": "4.6.0", "lodash.isequal": "4.5.0", @@ -149,5 +150,8 @@ "vite-plugin-checker": "0.6.2", "vite-plugin-istanbul": "5.0.0" }, + "resolutions": { + "@graasp/sdk": "3.8.3" + }, "packageManager": "yarn@4.1.0" } diff --git a/src/components/common/CollapseButton.tsx b/src/components/common/CollapseButton.tsx index 05659b0a3..0ec037431 100644 --- a/src/components/common/CollapseButton.tsx +++ b/src/components/common/CollapseButton.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'; import { ExpandLess, ExpandMore } from '@mui/icons-material'; import { IconButton, ListItemIcon, MenuItem, Tooltip } from '@mui/material'; -import { Item } from '@graasp/sdk'; +import { DiscriminatedItem, ItemType } from '@graasp/sdk'; import { ActionButton, ActionButtonVariant } from '@graasp/ui'; import { useBuilderTranslation } from '../../config/i18n'; @@ -12,15 +12,19 @@ import { COLLAPSE_ITEM_BUTTON_CLASS } from '../../config/selectors'; import { BUILDER } from '../../langs/constants'; type Props = { - item: Item; + item: DiscriminatedItem; type?: ActionButtonVariant; onClick?: () => void; + isCollapsedTextKey?: string; + notCollapsedTextKey?: string; }; const CollapseButton = ({ item, type = ActionButton.ICON_BUTTON, onClick, + isCollapsedTextKey = BUILDER.COLLAPSE_ITEM_UNCOLLAPSE_TEXT, + notCollapsedTextKey = BUILDER.COLLAPSE_ITEM_COLLAPSE_TEXT, }: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); @@ -33,6 +37,8 @@ const CollapseButton = ({ setIsCollapsible(item?.settings?.isCollapsible ?? false); }, [item]); + const disabled = item.type === ItemType.FOLDER; + const handleCollapse = () => { editItem({ id: item.id, @@ -44,10 +50,15 @@ const CollapseButton = ({ onClick?.(); }; - const icon = isCollapsible ? : ; - const text = isCollapsible - ? translateBuilder(BUILDER.COLLAPSE_ITEM_UNCOLLAPSE_TEXT) - : translateBuilder(BUILDER.COLLAPSE_ITEM_COLLAPSE_TEXT); + const icon = isCollapsible ? : ; + let text; + if (disabled) { + text = translateBuilder(BUILDER.SETTINGS_COLLAPSE_FOLDER_INFORMATION); + } else { + text = translateBuilder( + isCollapsible ? isCollapsedTextKey : notCollapsedTextKey, + ); + } switch (type) { case ActionButton.MENU_ITEM: @@ -56,17 +67,21 @@ const CollapseButton = ({ key={text} onClick={handleCollapse} className={COLLAPSE_ITEM_BUTTON_CLASS} + disabled={disabled} > {icon} {text} ); + case ActionButton.ICON: + return icon; case ActionButton.ICON_BUTTON: default: return ( { - const { t: translateBuilder } = useBuilderTranslation(); - - return ( - - - - - - ); -}; - -export default ItemMetadataButton; diff --git a/src/components/common/PinButton.tsx b/src/components/common/PinButton.tsx index bd6b40cda..43254fed7 100644 --- a/src/components/common/PinButton.tsx +++ b/src/components/common/PinButton.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; -import { Item } from '@graasp/sdk'; +import { DiscriminatedItem } from '@graasp/sdk'; import { ActionButtonVariant, PinButton as GraaspPinButton } from '@graasp/ui'; import { useBuilderTranslation } from '../../config/i18n'; @@ -10,11 +10,19 @@ import { BUILDER } from '../../langs/constants'; type Props = { type?: ActionButtonVariant; - item: Item; + item: DiscriminatedItem; onClick?: () => void; + pinTextKey?: string; + unPinTextKey?: string; }; -const PinButton = ({ item, type, onClick }: Props): JSX.Element => { +const PinButton = ({ + item, + type, + onClick, + pinTextKey = BUILDER.PIN_ITEM_PIN_TEXT, + unPinTextKey = BUILDER.PIN_ITEM_UNPIN_TEXT, +}: Props): JSX.Element => { const { t: translateBuilder } = useBuilderTranslation(); const editItem = mutations.useEditItem(); @@ -33,11 +41,12 @@ const PinButton = ({ item, type, onClick }: Props): JSX.Element => { onClick?.(); }; - const pinText = translateBuilder(BUILDER.PIN_ITEM_PIN_TEXT); - const unPinText = translateBuilder(BUILDER.PIN_ITEM_UNPIN_TEXT); + const pinText = translateBuilder(pinTextKey); + const unPinText = translateBuilder(unPinTextKey); return ( { + const { t: translateBuilder } = useBuilderTranslation(); + + const editItem = mutations.useEditItem(); + const showChatbox = item?.settings?.showChatbox; + + const onClick = () => { + editItem.mutate({ + id: item.id, + name: item.name, + settings: { + showChatbox: !showChatbox, + }, + }); + }; + + return ( + + ); +}; + +export default ShowChatboxButton; diff --git a/src/components/item/form/DocumentForm.tsx b/src/components/item/form/DocumentForm.tsx index 54abc9964..be4e1a32e 100644 --- a/src/components/item/form/DocumentForm.tsx +++ b/src/components/item/form/DocumentForm.tsx @@ -68,12 +68,13 @@ export const DocumentExtraForm = ({ ], ); - const withFlavor = (textView: JSX.Element): JSX.Element => - extra?.flavor ? ( - {textView} - ) : ( - textView - ); + const withFlavor = (textView: JSX.Element): JSX.Element => { + if (!extra.flavor || extra.flavor === DocumentItemExtraFlavor.None) { + return textView; + } + + return {textView}; + }; const handleChangeEditorMode = (mode: string) => { // send editor mode change diff --git a/src/components/item/header/ItemHeaderActions.tsx b/src/components/item/header/ItemHeaderActions.tsx index 8dd2ae341..14637dd1b 100644 --- a/src/components/item/header/ItemHeaderActions.tsx +++ b/src/components/item/header/ItemHeaderActions.tsx @@ -17,7 +17,6 @@ import { getHighestPermissionForMemberFromMemberships, isItemUpdateAllowedForUser, } from '../../../utils/membership'; -import ItemMetadataButton from '../../common/ItemMetadataButton'; import PublishButton from '../../common/PublishButton'; import ShareButton from '../../common/ShareButton'; import { useCurrentUserContext } from '../../context/CurrentUserContext'; @@ -62,9 +61,10 @@ const ItemHeaderActions = (): JSX.Element => { ITEM_TYPES_WITH_CAPTIONS.includes(item.type) && canEdit; - const activeActions = ( + return ( <> {showEditButton && } + {/* prevent moving from top header to avoid confusion */} { {canAdmin && } - - ); - - return ( - <> - {activeActions} {canEdit && } - ); } @@ -101,7 +95,6 @@ const ItemHeaderActions = (): JSX.Element => { // show only for content with tables : root or folders (item?.type === ItemType.FOLDER || !item?.id) && } - {item?.id && } ); }; diff --git a/src/components/item/publish/CustomizedTagsEdit.tsx b/src/components/item/publish/CustomizedTagsEdit.tsx index cbc61265d..544e8e9ef 100644 --- a/src/components/item/publish/CustomizedTagsEdit.tsx +++ b/src/components/item/publish/CustomizedTagsEdit.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; -import { Chip, TextField, Typography } from '@mui/material'; +import { Chip, Stack, TextField, Typography } from '@mui/material'; import type { TextFieldProps } from '@mui/material'; import { DiscriminatedItem } from '@graasp/sdk'; @@ -67,27 +67,36 @@ const CustomizedTagsEdit = ({ item, disabled }: Props): JSX.Element => { {translateBuilder(BUILDER.ITEM_TAGS_INFORMATION)} - - + + + + + + + + {settings?.tags?.length && ( <> - + {translateBuilder(BUILDER.ITEM_TAGS_PREVIEW_TITLE)} +
{settings?.tags?.map((tag, index) => ( { const itemId = item.id; + const { t } = useBuilderTranslation(); const { data: itemPermissions, isLoading: isLoadingItemPermissions } = hooks.useItemMemberships(item.id); const { data: currentMember } = useCurrentUserContext(); @@ -28,7 +32,10 @@ const AdminChatSettings = ({ item }: Props): JSX.Element | null => { } return ( - + + + {t(BUILDER.ITEM_SETTINGS_CHAT_SETTINGS_TITLE)} + { const [openConfirmation, setOpenConfirmation] = useState(false); const { t } = useChatboxTranslation(); + const { t: translateBuilder } = useBuilderTranslation(); if (!clearChat) { return null; @@ -72,7 +78,7 @@ const ClearChatButton = ({ color="error" onClick={() => setOpenConfirmation(true)} > - {text} + {text} ); } @@ -80,13 +86,11 @@ const ClearChatButton = ({ return ( <> - - {getContent(variant)} - - {t( - 'Careful, this will delete all the messages in this item. Make sure you have a backup. You can download a backup from Graasp Analytics.', - )} + + + {translateBuilder(BUILDER.ITEM_SETTINGS_CLEAR_CHAT_EXPLANATION)} + {getContent(variant)} {t(CHATBOX.CLEAR_ALL_CHAT_TITLE)} @@ -96,7 +100,7 @@ const ClearChatButton = ({ {t(CHATBOX.CLEAR_ALL_CHAT_CONTENT)} - {t('You can download a backup from Graasp Analytics.')} + {translateBuilder(BUILDER.ITEM_SETTINGS_CLEAR_CHAT_BACKUP_TEXT)} diff --git a/src/components/item/settings/FileSettings.tsx b/src/components/item/settings/FileSettings.tsx index d5f50f83b..1a10bd42d 100644 --- a/src/components/item/settings/FileSettings.tsx +++ b/src/components/item/settings/FileSettings.tsx @@ -26,7 +26,7 @@ const FileSettings = ({ return ( - + {translateBuilder(BUILDER.SETTINGS_FILE_SETTINGS_TITLE)} diff --git a/src/components/item/settings/GeolocationPicker.tsx b/src/components/item/settings/GeolocationPicker.tsx new file mode 100644 index 000000000..41f47048d --- /dev/null +++ b/src/components/item/settings/GeolocationPicker.tsx @@ -0,0 +1,141 @@ +import { ChangeEventHandler } from 'react'; + +import Clear from '@mui/icons-material/Clear'; +import { + Box, + IconButton, + LinearProgress, + List, + ListItemButton, + Stack, + TextField, + Tooltip, + Typography, +} from '@mui/material'; + +import { DiscriminatedItem } from '@graasp/sdk'; + +import { useBuilderTranslation } from '@/config/i18n'; +import { hooks, mutations } from '@/config/queryClient'; +import { BUILDER } from '@/langs/constants'; + +import { OpenStreetMapResult, useSearchAddress } from './hooks'; + +const GeolocationPicker = ({ + item, +}: { + item: DiscriminatedItem; +}): JSX.Element => { + const { t } = useBuilderTranslation(); + const { data: geoloc } = hooks.useItemGeolocation(item.id); + const { mutate: putGeoloc } = mutations.usePutItemGeolocation(); + const { mutate: deleteGeoloc } = mutations.useDeleteItemGeolocation(); + + const { + query, + setQuery, + isDebounced, + setResults, + results, + loading, + clearQuery, + } = useSearchAddress({ + lang: item.lang, + geoloc, + }); + + const onChange: ChangeEventHandler = (e) => { + setQuery(e.target.value); + }; + + const onChangeOption = (option: OpenStreetMapResult): void => { + const { + raw: { lat, lon: lng }, + label, + } = option; + putGeoloc({ + itemId: item.id, + geolocation: { + lng: parseFloat(lng), + lat: parseFloat(lat), + addressLabel: label, + }, + }); + setResults([]); + }; + + const clearGeoloc = () => { + deleteGeoloc({ itemId: item.id }); + clearQuery(); + }; + + // the input is disabled if the geoloc is defined in parent + // but it should be enabled if the geoloc is not defined + const isDisabled = Boolean(geoloc && geoloc?.item?.id !== item.id); + + return ( + + + + {t(BUILDER.ITEM_SETTINGS_GEOLOCATION_TITLE)} + + + {t(BUILDER.ITEM_SETTINGS_GEOLOCATION_EXPLANATION)} + + + + + + {loading && ( + + + + )} + + {query && !isDisabled && ( + + + + + + + + )} + + {isDisabled && ( + + {t(BUILDER.ITEM_SETTINGS_GEOLOCATION_INHERITED_EXPLANATION)} + + )} + + {results && ( + + {results.map((r) => ( + onChangeOption(r)} + > + {r.label} + + ))} + {!results.length && + query && + query !== geoloc?.addressLabel && + !loading && + !isDebounced && + t(BUILDER.ITEM_SETTINGS_GEOLOCATION_NO_ADDRESS)} + + )} + + + ); +}; + +export default GeolocationPicker; diff --git a/src/components/item/ItemMetadataContent.tsx b/src/components/item/settings/ItemMetadataContent.tsx similarity index 79% rename from src/components/item/ItemMetadataContent.tsx rename to src/components/item/settings/ItemMetadataContent.tsx index c6f4c5d43..ad7d97a18 100644 --- a/src/components/item/ItemMetadataContent.tsx +++ b/src/components/item/settings/ItemMetadataContent.tsx @@ -1,14 +1,11 @@ import { useOutletContext } from 'react-router-dom'; -import { - Container, - Table, - TableBody, - TableCell, - TableContainer, - TableRow, - Typography, -} from '@mui/material'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableRow from '@mui/material/TableRow'; +import Typography from '@mui/material/Typography'; import { ItemType, @@ -21,15 +18,16 @@ import { COMMON } from '@graasp/translations'; import i18n, { useBuilderTranslation, useCommonTranslation, -} from '../../config/i18n'; -import { hooks } from '../../config/queryClient'; +} from '../../../config/i18n'; +import { hooks } from '../../../config/queryClient'; import { ITEM_PANEL_NAME_ID, ITEM_PANEL_TABLE_ID, -} from '../../config/selectors'; -import { BUILDER } from '../../langs/constants'; -import { OutletType } from '../pages/item/type'; -import ThumbnailSetting from './settings/ThumbnailSetting'; +} from '../../../config/selectors'; +import { BUILDER } from '../../../langs/constants'; +import { OutletType } from '../../pages/item/type'; +import LanguageSelect from './LanguageSelect'; +import ThumbnailSetting from './ThumbnailSetting'; const { useMember } = hooks; @@ -71,7 +69,7 @@ const ItemMetadataContent = (): JSX.Element => { }; return ( - + <> @@ -127,10 +125,18 @@ const ItemMetadataContent = (): JSX.Element => { {renderLink()} + + + {translateBuilder(BUILDER.ITEM_METADATA_LANGUAGE_TITLE)} + + + + + - + ); }; diff --git a/src/components/item/settings/ItemSettingProperty.tsx b/src/components/item/settings/ItemSettingProperty.tsx new file mode 100644 index 000000000..7e86307e3 --- /dev/null +++ b/src/components/item/settings/ItemSettingProperty.tsx @@ -0,0 +1,54 @@ +import { Stack, Switch, Typography } from '@mui/material'; + +type Props = { + onClick: (checked: boolean) => void; + title: string; + checked: boolean; + valueText: string; + id?: string; + disabled?: boolean; + icon?: JSX.Element; +}; + +const ItemSettingProperty = ({ + onClick, + title, + checked = false, + id, + disabled, + icon, + valueText, +}: Props): JSX.Element => ( + + + {icon} + + + + {title} + + + + {valueText} + + + + + { + onClick(e.target.checked); + }} + /> + + +); + +export default ItemSettingProperty; diff --git a/src/components/item/settings/ItemSettings.tsx b/src/components/item/settings/ItemSettings.tsx index 7db92a59e..f718b5ba0 100644 --- a/src/components/item/settings/ItemSettings.tsx +++ b/src/components/item/settings/ItemSettings.tsx @@ -1,236 +1,27 @@ -import { useEffect, useState } from 'react'; -import { Trans } from 'react-i18next'; -import { Link, useOutletContext } from 'react-router-dom'; +import { useOutletContext } from 'react-router-dom'; -import { Info } from '@mui/icons-material'; -import { - Alert, - Container, - FormControlLabel, - FormGroup, - Switch, - Tooltip, - Typography, -} from '@mui/material'; - -import { DiscriminatedItem, ItemType } from '@graasp/sdk'; +import Container from '@mui/material/Container'; import { OutletType } from '@/components/pages/item/type'; -import { buildItemInformationPath } from '@/config/paths'; -import { - DEFAULT_COLLAPSIBLE_SETTING, - DEFAULT_PINNED_SETTING, - DEFAULT_RESIZE_SETTING, - DEFAULT_SAVE_ACTIONS_SETTING, - DEFAULT_SHOW_CHATBOX_SETTING, -} from '../../../config/constants'; -import { useBuilderTranslation } from '../../../config/i18n'; -import { mutations } from '../../../config/queryClient'; -import { - SETTINGS_CHATBOX_TOGGLE_ID, - SETTINGS_COLLAPSE_TOGGLE_ID, - SETTINGS_PINNED_TOGGLE_ID, - SETTINGS_RESIZE_TOGGLE_ID, - SETTINGS_SAVE_ACTIONS_TOGGLE_ID, -} from '../../../config/selectors'; -import { BUILDER } from '../../../langs/constants'; +import CustomizedTagsEdit from '../publish/CustomizedTagsEdit'; import AdminChatSettings from './AdminChatSettings'; -import FileSettings from './FileSettings'; -import LinkSettings from './LinkSettings'; +import GeolocationPicker from './GeolocationPicker'; +import ItemMetadataContent from './ItemMetadataContent'; +import ItemSettingsProperties from './ItemSettingsProperties'; const ItemSettings = (): JSX.Element => { - const { t: translateBuilder } = useBuilderTranslation(); const { item } = useOutletContext(); - const { mutate: editItem } = mutations.useEditItem(); - - const { settings } = item; - - const [settingLocal, setSettingLocal] = - useState(settings); - - useEffect( - () => { - if (settings) { - // this is used because we get a response where the setting only contains the modified setting - // so it make the toggles flicker. - // by only overriding keys that changes we are able to remove the flicker effect - - setSettingLocal((previousSettings) => ({ - ...previousSettings, - ...settings, - })); - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [settings], - ); - - const handleOnToggle = ( - event: { target: { checked: boolean } }, - settingKey: string, - ): void => { - const newValue = event.target.checked; - setSettingLocal({ - ...settingLocal, - [settingKey]: newValue, - }); - editItem({ - id: item.id, - name: item.name, - settings: { - [settingKey]: newValue, - }, - }); - }; - - const renderPinSetting = () => { - const control = ( - handleOnToggle(e, 'isPinned')} - checked={settingLocal?.isPinned || DEFAULT_PINNED_SETTING} - color="primary" - /> - ); - return ( - - ); - }; - - const renderChatSetting = () => { - const control = ( - handleOnToggle(e, 'showChatbox')} - checked={settingLocal?.showChatbox || DEFAULT_SHOW_CHATBOX_SETTING} - color="primary" - /> - ); - return ( - - ); - }; - - const renderResizeSetting = () => { - const control = ( - handleOnToggle(e, 'isResizable')} - checked={settingLocal?.isResizable || DEFAULT_RESIZE_SETTING} - color="primary" - /> - ); - return ( - - ); - }; - - const renderCollapseSetting = () => { - const disabled = item.type === ItemType.FOLDER; - const control = ( - handleOnToggle(e, 'isCollapsible')} - checked={settingLocal?.isCollapsible || DEFAULT_COLLAPSIBLE_SETTING} - color="primary" - disabled={disabled} - /> - ); - const formLabel = ( - - ); - const tooltip = disabled ? ( - - - - - - ) : null; - return ( -
- {formLabel} - {tooltip} -
- ); - }; - - const renderSaveActionsSetting = () => { - const control = ( - - - handleOnToggle(e, 'enableSaveActions')} - checked={ - settingLocal?.enableSaveActions ?? DEFAULT_SAVE_ACTIONS_SETTING - } - color="primary" - disabled - /> - - - ); - return ( - - ); - }; + return ( + + + - const renderSettingsPerType = () => { - switch (item.type) { - case ItemType.LINK: - return ; - case ItemType.S3_FILE: - case ItemType.LOCAL_FILE: - return ; - default: - return null; - } - }; + - return ( - - - , - }} - /> - - - {translateBuilder(BUILDER.SETTINGS_TITLE)} - + - - {renderPinSetting()} - {renderCollapseSetting()} - {item.type === ItemType.APP && renderResizeSetting()} - {renderChatSetting()} - {renderSaveActionsSetting()} - - {renderSettingsPerType()} ); diff --git a/src/components/item/settings/ItemSettingsProperties.tsx b/src/components/item/settings/ItemSettingsProperties.tsx new file mode 100644 index 000000000..ddf3f723a --- /dev/null +++ b/src/components/item/settings/ItemSettingsProperties.tsx @@ -0,0 +1,165 @@ +import { Typography } from '@mui/material'; + +import { DiscriminatedItem, ItemType } from '@graasp/sdk'; +import { ActionButton, ChatboxButton, PinButton } from '@graasp/ui'; + +import CollapseButton from '@/components/common/CollapseButton'; +import { + DEFAULT_RESIZE_SETTING, + DEFAULT_SAVE_ACTIONS_SETTING, +} from '@/config/constants'; +import { useBuilderTranslation } from '@/config/i18n'; +import { mutations } from '@/config/queryClient'; +import { + SETTINGS_CHATBOX_TOGGLE_ID, + SETTINGS_PINNED_TOGGLE_ID, + SETTINGS_RESIZE_TOGGLE_ID, + SETTINGS_SAVE_ACTIONS_TOGGLE_ID, +} from '@/config/selectors'; +import { BUILDER } from '@/langs/constants'; + +import FileSettings from './FileSettings'; +import ItemSettingProperty from './ItemSettingProperty'; +import LinkSettings from './LinkSettings'; + +type Props = { + item: DiscriminatedItem; +}; + +const ItemSettingsProperties = ({ item }: Props): JSX.Element => { + const { t: translateBuilder } = useBuilderTranslation(); + const { mutate: editItem } = mutations.useEditItem(); + + const { settings } = item; + + const handleOnToggle = ( + event: { target: { checked: boolean } }, + settingKey: string, + ): void => { + const newValue = event.target.checked; + editItem({ + id: item.id, + name: item.name, + settings: { + [settingKey]: newValue, + }, + }); + }; + + const renderSettingsPerType = () => { + switch (item.type) { + case ItemType.LINK: + return ; + case ItemType.S3_FILE: + case ItemType.LOCAL_FILE: + return ; + case ItemType.APP: + return ( + { + handleOnToggle({ target: { checked } }, 'isResizable'); + }} + valueText={ + settings?.isResizable + ? translateBuilder(BUILDER.ITEM_SETTINGS_RESIZABLE_ENABLED_TEXT) + : translateBuilder( + BUILDER.ITEM_SETTINGS_RESIZABLE_DISABLED_TEXT, + ) + } + /> + ); + default: + return null; + } + }; + + return ( + <> + + {translateBuilder(BUILDER.SETTINGS_TITLE)} + + } + checked={Boolean(settings.isCollapsible)} + disabled={item.type === ItemType.FOLDER} + onClick={(checked: boolean): void => { + handleOnToggle({ target: { checked } }, 'isCollapsible'); + }} + valueText={(() => { + if (item.type === ItemType.FOLDER) { + return translateBuilder( + BUILDER.SETTINGS_COLLAPSE_FOLDER_INFORMATION, + ); + } + return settings.isCollapsible + ? translateBuilder(BUILDER.ITEM_SETTINGS_IS_COLLAPSED_ENABLED_TEXT) + : translateBuilder( + BUILDER.ITEM_SETTINGS_IS_COLLAPSED_DISABLED_TEXT, + ); + })()} + /> + + + } + title={translateBuilder(BUILDER.ITEM_SETTINGS_IS_PINNED_TITLE)} + checked={Boolean(settings.isPinned)} + onClick={(checked: boolean): void => { + handleOnToggle({ target: { checked } }, 'isPinned'); + }} + valueText={ + settings.isPinned + ? translateBuilder(BUILDER.ITEM_SETTINGS_IS_PINNED_ENABLED_TEXT) + : translateBuilder(BUILDER.ITEM_SETTINGS_IS_PINNED_DISABLED_TEXT) + } + /> + + + } + id={SETTINGS_CHATBOX_TOGGLE_ID} + title={translateBuilder(BUILDER.ITEM_SETTINGS_SHOW_CHAT_TITLE)} + checked={Boolean(settings.showChatbox)} + onClick={(checked: boolean): void => { + handleOnToggle({ target: { checked } }, 'showChatbox'); + }} + valueText={ + settings.showChatbox + ? translateBuilder(BUILDER.ITEM_SETTINGS_SHOW_CHAT_ENABLED_TEXT) + : translateBuilder(BUILDER.ITEM_SETTINGS_SHOW_CHAT_DISABLED_TEXT) + } + /> + { + handleOnToggle({ target: { checked } }, 'enableAnalytics'); + }} + valueText="Coming soon" + disabled + /> + + {renderSettingsPerType()} + + ); +}; + +export default ItemSettingsProperties; diff --git a/src/components/item/settings/LanguageSelect.tsx b/src/components/item/settings/LanguageSelect.tsx new file mode 100644 index 000000000..7c7e92fcb --- /dev/null +++ b/src/components/item/settings/LanguageSelect.tsx @@ -0,0 +1,34 @@ +import { SelectProps } from '@mui/material'; + +import { DiscriminatedItem } from '@graasp/sdk'; +import { langs } from '@graasp/translations'; +import { Select } from '@graasp/ui'; + +import { mutations } from '@/config/queryClient'; +import { LANGUAGE_SELECTOR_ID } from '@/config/selectors'; +import { useCanUpdateItem } from '@/hooks/authorization'; + +const LanguageSelect = ({ item }: { item: DiscriminatedItem }): JSX.Element => { + const { mutate: changeLang } = mutations.useEditItem(); + const { allowed: canEdit } = useCanUpdateItem(item); + + const onChange: SelectProps['onChange'] = (e) => { + const { value: newLang } = e.target; + changeLang({ id: item.id, lang: newLang as string }); + }; + + const values = Object.entries(langs).map(([k, v]) => ({ value: k, text: v })); + + return ( +