Skip to content

Commit

Permalink
feat: add component sidebar manage tab
Browse files Browse the repository at this point in the history
  • Loading branch information
rpenido committed Sep 12, 2024
1 parent 513309c commit 6208ded
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 189 deletions.
3 changes: 2 additions & 1 deletion src/library-authoring/component-info/ComponentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@openedx/paragon';

import { ComponentMenu } from '../components';
import ComponentManagement from './ComponentManagement';
import messages from './messages';

interface ComponentInfoProps {
Expand Down Expand Up @@ -37,7 +38,7 @@ const ComponentInfo = ({ usageKey } : ComponentInfoProps) => {
Preview tab placeholder
</Tab>
<Tab eventKey="manage" title={intl.formatMessage(messages.manageTabTitle)}>
Manage tab placeholder
<ComponentManagement usageKey={usageKey} />
</Tab>
<Tab eventKey="details" title={intl.formatMessage(messages.detailsTabTitle)}>
Details tab placeholder
Expand Down
41 changes: 41 additions & 0 deletions src/library-authoring/component-info/ComponentManagement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { getConfig } from '@edx/frontend-platform';
import { Collapsible, Icon, Stack } from '@openedx/paragon';
import { Tag } from '@openedx/paragon/icons';

import { useLibraryBlockMetadata } from '../data/apiHooks';
import StatusWidget from '../generic/status-widget';

interface ComponentManagementProps {
usageKey: string;
}
const ComponentManagement = ({ usageKey }: ComponentManagementProps) => {
const { data: componentMetadata } = useLibraryBlockMetadata(usageKey);

if (!componentMetadata) {
return null;
}

return (
<Stack gap={3}>
<StatusWidget
{...componentMetadata}
/>
{[true, 'true'].includes(getConfig().ENABLE_TAGGING_TAXONOMY_PAGES)
&& (
<Collapsible
defaultOpen
title={(
<Stack direction="horizontal">
<Icon src={Tag} className="mr-1 pb-1.5 text-info-500" />
Tags
</Stack>
)}
>
Tags will be displayed here
</Collapsible>
)}
</Stack>
);
};

export default ComponentManagement;
21 changes: 18 additions & 3 deletions src/library-authoring/data/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ export const getLibraryBlockTypesUrl = (libraryId: string) => `${getApiBaseUrl()
* Get the URL for create content in library.
*/
export const getCreateLibraryBlockUrl = (libraryId: string) => `${getApiBaseUrl()}/api/libraries/v2/${libraryId}/blocks/`;
/**
* Get the URL for library block metadata.
*/
export const getLibraryBlockMetadataUrl = (usageKey: string) => `${getApiBaseUrl()}/api/libraries/v2/blocks/${usageKey}/`;
/**
* Get the URL for content library list API.
*/
export const getContentLibraryV2ListApiUrl = () => `${getApiBaseUrl()}/api/libraries/v2/`;
/**
* Get the URL for commit/revert changes in library.
Expand Down Expand Up @@ -96,7 +103,7 @@ export interface CreateBlockDataRequest {
definitionId: string;
}

export interface CreateBlockDataResponse {
export interface LibraryBlockMetadata {
id: string;
blockType: string;
defKey: string | null;
Expand Down Expand Up @@ -155,7 +162,7 @@ export async function createLibraryBlock({
libraryId,
blockType,
definitionId,
}: CreateBlockDataRequest): Promise<CreateBlockDataResponse> {
}: CreateBlockDataRequest): Promise<LibraryBlockMetadata> {
const client = getAuthenticatedHttpClient();
const { data } = await client.post(
getCreateLibraryBlockUrl(libraryId),
Expand Down Expand Up @@ -218,7 +225,7 @@ export async function revertLibraryChanges(libraryId: string) {
export async function libraryPasteClipboard({
libraryId,
blockId,
}: LibraryPasteClipboardRequest): Promise<CreateBlockDataResponse> {
}: LibraryPasteClipboardRequest): Promise<LibraryBlockMetadata> {
const client = getAuthenticatedHttpClient();
const { data } = await client.post(
getLibraryPasteClipboardUrl(libraryId),
Expand All @@ -229,6 +236,14 @@ export async function libraryPasteClipboard({
return data;
}

/**
* Fetch library block metadata.
*/
export async function getLibraryBlockMetadata(usageKey: string): Promise<LibraryBlockMetadata> {
const { data } = await getAuthenticatedHttpClient().get(getLibraryBlockMetadataUrl(usageKey));
return camelCaseObject(data);
}

/**
* Fetch xblock fields.
*/
Expand Down
16 changes: 16 additions & 0 deletions src/library-authoring/data/apiHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
revertLibraryChanges,
updateLibraryMetadata,
libraryPasteClipboard,
getLibraryBlockMetadata,
getXBlockFields,
updateXBlockFields,
} from './api';
Expand Down Expand Up @@ -52,6 +53,14 @@ export const libraryAuthoringQueryKeys = {
'content',
'libraryBlockTypes',
],
// FixMe: Move this to another key map
componentMetadata: (usageKey: string) => [
...libraryAuthoringQueryKeys.all,
...libraryAuthoringQueryKeys.contentLibrary('lib'),
'content',
usageKey,
'componentMetadata',
],
xblockFields: (contentLibraryId: string, usageKey: string) => [
...libraryAuthoringQueryKeys.all,
...libraryAuthoringQueryKeys.contentLibrary(contentLibraryId),
Expand Down Expand Up @@ -167,6 +176,13 @@ export const useLibraryPasteClipboard = () => {
});
};

export const useLibraryBlockMetadata = (usageId: string) => (
useQuery({
queryKey: libraryAuthoringQueryKeys.componentMetadata(usageId),
queryFn: () => getLibraryBlockMetadata(usageId),
})
);

export const useXBlockFields = (contentLibrayId: string, usageKey: string) => (
useQuery({
queryKey: libraryAuthoringQueryKeys.xblockFields(contentLibrayId, usageKey),
Expand Down
1 change: 1 addition & 0 deletions src/library-authoring/generic/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "./status-widget/StatusWidget";
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.library-publish-status {
.status-widget {
&.draft-status {
background-color: #FDF3E9;
border-top: 4px solid #F4B57B;
Expand All @@ -9,3 +9,4 @@
border-top: 4px solid $info-400;
}
}

191 changes: 191 additions & 0 deletions src/library-authoring/generic/status-widget/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import {
FormattedDate,
FormattedMessage,
FormattedTime,
useIntl,
} from '@edx/frontend-platform/i18n';
import { Button, Container, Stack } from '@openedx/paragon';
import classNames from 'classnames';

import messages from './messages';

const CustomFormattedDate = ({ date }: { date: string }) => (
<b>
<FormattedDate
value={date}
year="numeric"
month="long"
day="2-digit"
/>
</b>
);

const CustomFormattedTime = ({ date }: { date: string }) => (
<b>
<FormattedTime
value={date}
hour12={false}
/>
</b>
);

type DraftBodyMessageProps = {
lastDraftCreatedBy?: string | null;
lastDraftCreated?: string | null;
created?: string | null;
};
const DraftBodyMessage = ({ lastDraftCreatedBy, lastDraftCreated, created }: DraftBodyMessageProps) => {
if (lastDraftCreatedBy && lastDraftCreated) {
return (
<FormattedMessage
{...messages.lastDraftMsg}
values={{
date: <CustomFormattedDate date={lastDraftCreated} />,
time: <CustomFormattedTime date={lastDraftCreated} />,
user: <b>{lastDraftCreatedBy}</b>,
}}
/>
);
}

if (lastDraftCreated) {
return (
<FormattedMessage
{...messages.lastDraftMsgWithoutUser}
values={{
date: <CustomFormattedDate date={lastDraftCreated} />,
time: <CustomFormattedTime date={lastDraftCreated} />,
}}
/>
);
}

if (created) {
return (
<FormattedMessage
{...messages.lastDraftMsgWithoutUser}
values={{
date: <CustomFormattedDate date={created} />,
time: <CustomFormattedTime date={created} />,
}}
/>
);
}

return null;
};

type StatusWidgedProps = {
lastPublished?: string | null;
hasUnpublishedChanges: boolean;
hasUnpublishedDeletes?: boolean;
lastDraftCreatedBy?: string | null;
lastDraftCreated?: string | null;
created?: string | null;
publishedBy?: string | null;
numBlocks?: number;
onCommit?: () => void;
onRevert?: () => void;
};

const StatusWidget = ({
lastPublished,
hasUnpublishedChanges,
hasUnpublishedDeletes,
lastDraftCreatedBy,
lastDraftCreated,
created,
publishedBy,
numBlocks,
onCommit,
onRevert,
}: StatusWidgedProps) => {
const intl = useIntl();

let isNew: boolean | undefined;
let isPublished: boolean;
let statusMessage: string;
let extraStatusMessage: string | undefined;
let bodyMessage: React.ReactNode | undefined;

if (!lastPublished) {
// Entity is never published (new)
isNew = numBlocks != null && numBlocks === 0; // allow discarding if components are added
isPublished = false;
statusMessage = intl.formatMessage(messages.draftStatusLabel);
extraStatusMessage = intl.formatMessage(messages.neverPublishedLabel);
bodyMessage = (<DraftBodyMessage {...{ lastDraftCreatedBy, lastDraftCreated, created }} />);
} else if (hasUnpublishedChanges || hasUnpublishedDeletes) {
// Entity is on Draft state
isPublished = false;
statusMessage = intl.formatMessage(messages.draftStatusLabel);
extraStatusMessage = intl.formatMessage(messages.unpublishedStatusLabel);
bodyMessage = (<DraftBodyMessage {...{ lastDraftCreatedBy, lastDraftCreated, created }} />);
} else {
// Entity is published
isPublished = true;
statusMessage = intl.formatMessage(messages.publishedStatusLabel);
if (publishedBy) {
bodyMessage = (
<FormattedMessage
{...messages.lastPublishedMsg}
values={{
date: <CustomFormattedDate date={lastPublished} />,
time: <CustomFormattedTime date={lastPublished} />,
user: <b>{publishedBy}</b>,
}}
/>
);
} else {
bodyMessage = (
<FormattedMessage
{...messages.lastPublishedMsgWithoutUser}
values={{
date: <CustomFormattedDate date={lastPublished} />,
time: <CustomFormattedTime date={lastPublished} />,
}}
/>
);
}
}

return (
<Stack>
<Container className={classNames('status-widget', {
'draft-status': !isPublished,
'published-status': isPublished,
})}
>
<span className="font-weight-bold">
{statusMessage}
</span>
{ extraStatusMessage && (
<span className="ml-1">
{extraStatusMessage}
</span>
)}
</Container>
<Container className="mt-3">
<Stack gap={3}>
<span>
{bodyMessage}
</span>
{onCommit && (
<Button disabled={isPublished} onClick={onCommit}>
{intl.formatMessage(messages.publishButtonLabel)}
</Button>
)}
{onRevert && (
<div className="d-flex justify-content-end">
<Button disabled={isPublished || isNew} variant="link" onClick={onRevert}>
{intl.formatMessage(messages.discardChangesButtonLabel)}
</Button>
</div>
)}
</Stack>
</Container>
</Stack>
);
};

export default StatusWidget;
Loading

0 comments on commit 6208ded

Please sign in to comment.