Skip to content

Commit

Permalink
feat(app-page-builder): add translations features
Browse files Browse the repository at this point in the history
  • Loading branch information
Pavel910 committed Sep 14, 2024
1 parent c764f63 commit 8b11fe8
Show file tree
Hide file tree
Showing 39 changed files with 1,042 additions and 210 deletions.
13 changes: 9 additions & 4 deletions packages/app-page-builder/src/admin/graphql/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,15 @@ export const LIST_PAGES = gql`
*/
export interface GetPageQueryResponse<T extends PageResponseData = PageResponseData> {
pageBuilder: {
getPage: {
data: T | null;
error: PbErrorResponse | null;
};
getPage:
| {
data: T;
error: null;
}
| {
data: null;
error: PbErrorResponse;
};
};
}

Expand Down
23 changes: 23 additions & 0 deletions packages/app-page-builder/src/admin/hooks/usePage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useQuery } from "@apollo/react-hooks";
import {
GET_PAGE,
GetPageQueryResponse,
GetPageQueryVariables,
PageResponseData
} from "~/admin/graphql/pages";
import { PbErrorResponse } from "~/types";

export type Page = PageResponseData & { settings: Record<string, any> };

export const usePage = (
pageId: string
): { loading: boolean; page: Page | undefined; error: PbErrorResponse | undefined } => {
const query = useQuery<GetPageQueryResponse, GetPageQueryVariables>(GET_PAGE, {
variables: { id: String(pageId) },
skip: !pageId
});

const { data, error } = query.data?.pageBuilder.getPage ?? { data: null, error: null };

return { loading: query.loading, page: (data as Page) || undefined, error: error || undefined };
};
7 changes: 7 additions & 0 deletions packages/app-page-builder/src/admin/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export * from "./hooks/usePage";
export * from "./hooks/useNavigatePage";
export * from "./hooks/usePreviewPage";
export * from "./hooks/usePageBuilderSettings";
export * from "./hooks/useConfigureWebsiteUrl";
export * from "./hooks/useSiteStatus";
export * from "./hooks/useAdminPageBuilder";
7 changes: 0 additions & 7 deletions packages/app-page-builder/src/admin/views/Menus/validators.ts

This file was deleted.

3 changes: 2 additions & 1 deletion packages/app-page-builder/src/admin/views/Pages/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,11 @@ export const removeRevisionFromEntryCache = (
): PbPageRevision[] => {
const gqlParams = {
query: GQL.GET_PAGE,
variables: { id: revision.pid }
variables: { id: revision.id }
};

const data = cache.readQuery(gqlParams);

const revisions = get(
data,
"pageBuilder.getPage.data.revisions"
Expand Down
2 changes: 0 additions & 2 deletions packages/app-page-builder/src/pageEditor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import { useNavigatePage } from "~/admin/hooks/useNavigatePage";
import { usePageBlocks } from "~/admin/contexts/AdminPageBuilder/PageBlocks/usePageBlocks";
import { DefaultEditorConfig } from "~/editor";
import { DefaultPageEditorConfig } from "~/pageEditor/config/DefaultPageEditorConfig";
import { TranslationsPageEditorConfig } from "~/translations/PageEditorConfig";

interface PageDataAndRevisionsState {
page: PageWithContent | null;
Expand Down Expand Up @@ -173,7 +172,6 @@ export const PageEditor = () => {
<PageProvider page={page as Page}>
<DefaultEditorConfig />
<DefaultPageEditorConfig />
<TranslationsPageEditorConfig />
<LoadData>
<PbEditor stateInitializerFactory={createStateInitializer(page!, revisions)} />
</LoadData>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from "react";
import { createDecorator } from "@webiny/react-composition";
import { EditorProvider } from "~/editor";
import { TranslationProvider } from "./TranslationContext";

export const AddTranslatableItemsContext = createDecorator(EditorProvider, Original => {
return function EditorProvider(props) {
return (
<TranslationProvider>
<Original {...props} />
</TranslationProvider>
);
};
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { useEffect } from "react";
import { ElementRendererInputs } from "@webiny/app-page-builder-elements/contexts/ElementRendererInputs";
import { usePage } from "~/pageEditor";
import {
TranslatableItem,
useTranslations
} from "~/translations/ExtractTranslatableValues/TranslationContext";
import { PbEditorElement } from "~/types";
import { ElementInputType } from "@webiny/app-page-builder-elements";

export interface CreateTranslatableItemParams {
value: any;
element: PbEditorElement;
input: {
name: string;
type: ElementInputType;
};
}

export interface CreateTranslatableItem {
(params: CreateTranslatableItemParams): Omit<TranslatableItem, "collectionId">;
}

export const createElementRendererInputsDecorator = (
createTranslatableItem: CreateTranslatableItem
) => {
return ElementRendererInputs.createDecorator(Original => {
return function CollectElementValues(props) {
const translations = useTranslations();
const [page] = usePage();
const { element, inputs, values } = props;

useEffect(() => {
if (!inputs || !translations) {
return;
}

Object.entries(props.values).forEach(([key, value]) => {
if (!value || !inputs[key].isTranslatable()) {
return;
}

translations.setTranslationItem({
collectionId: `page:${page.id}`,
...createTranslatableItem({
element,
value,
input: {
name: key,
type: inputs[key].getType()
}
})
});
});
}, [element.id, values]);

return <Original {...props} />;
};
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react";
import { AddTranslatableItemsContext } from "./AddTranslatableItemsContext";
import { SaveTranslatableValues } from "~/translations/ExtractTranslatableValues/SaveTranslatableValues";
import { PageEditorConfig } from "~/pageEditor";
import {
createElementRendererInputsDecorator,
CreateTranslatableItem
} from "~/translations/ExtractTranslatableValues/CollectElementValues";

interface ExtractTranslatableValuesProps {
createTranslatableItem: CreateTranslatableItem;
}

export const ExtractTranslatableValues = ({
createTranslatableItem
}: ExtractTranslatableValuesProps) => {
const CollectElementValues = createElementRendererInputsDecorator(createTranslatableItem);

return (
<>
<AddTranslatableItemsContext />
<PageEditorConfig>
<CollectElementValues />
<SaveTranslatableValues />
</PageEditorConfig>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useEffect } from "react";
import debounce from "lodash/debounce";
import { PbEditorElement } from "~/types";
import { useEventActionHandler } from "~/editor";
import { PageEditorEventActionCallableState } from "~/pageEditor/types";
import { useSaveTranslatableCollection } from "~/translations";
import { ToggleSaveRevisionStateActionEvent } from "~/pageEditor/config/eventActions/saveRevision";
import {
TranslatableItem,
useTranslations
} from "~/translations/ExtractTranslatableValues/TranslationContext";

const extractElementIds = (elements: PbEditorElement[]): string[] => {
return [
...elements
.map(element => [
element.id,
...extractElementIds(element.elements as PbEditorElement[])
])
.flat()
];
};

export const SaveTranslatableValues = () => {
const eventActionHandler = useEventActionHandler<PageEditorEventActionCallableState>();
const translations = useTranslations();
const { saveTranslatableCollection } = useSaveTranslatableCollection();

const saveTranslations = debounce(async (orderedElementIds: string[]) => {
if (!translations) {
return;
}

const items = translations.getTranslationItems();
if (!items.length) {
return;
}

const filteredAndSortedItems: TranslatableItem[] = [];
orderedElementIds.forEach(elementId => {
items
.filter(item => item.itemId.startsWith(`element:${elementId}.`))
.forEach(item => filteredAndSortedItems.push(item));
});

const collection = {
collectionId: items[0].collectionId,
items: filteredAndSortedItems.map(item => ({
itemId: item.itemId,
value: item.value,
context: item.context
}))
};

await saveTranslatableCollection(collection);
}, 1000);

useEffect(() => {
if (!translations) {
return;
}

const offSaveRevisionAction = eventActionHandler.on(
ToggleSaveRevisionStateActionEvent,
async (_, context, meta) => {
const tree = await context.eventActionHandler.getRawElementTree();
const orderedElementIds = extractElementIds([tree]);

if (meta.saving) {
saveTranslations(orderedElementIds);
}

return {
actions: []
};
}
);

return () => {
offSaveRevisionAction();
};
}, []);

return null;
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { useMemo, useRef } from "react";
import { GenericRecord } from "@webiny/app/types";

export interface TranslatableItem {
collectionId: string;
itemId: string;
value: any;
context?: Record<string, any>;
context?: GenericRecord<string>;
}

export interface TranslationContext {
Expand Down
63 changes: 0 additions & 63 deletions packages/app-page-builder/src/translations/HeadlessCms.tsx

This file was deleted.

Loading

0 comments on commit 8b11fe8

Please sign in to comment.