From 087fe3bf97b74d8750468b7f137d6f3224cd2a74 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Fri, 2 Feb 2024 10:57:33 +0100 Subject: [PATCH 01/11] feat: Add initial SingleURLs layout --- app/config-types.ts | 5 +++++ app/configurator/components/configurator.tsx | 21 ++++++++++++++++++++ app/configurator/components/field-i18n.ts | 6 ++++++ app/configurator/components/ui-helpers.ts | 2 ++ app/icons/components/IcLayoutSingleURLs.tsx | 21 ++++++++++++++++++++ app/icons/components/index.tsx | 2 ++ app/icons/svg/ic_layout_single_urls.svg | 19 ++++++++++++++++++ app/locales/de/messages.po | 6 ++++++ app/locales/en/messages.po | 6 ++++++ app/locales/fr/messages.po | 6 ++++++ app/locales/it/messages.po | 6 ++++++ 11 files changed, 100 insertions(+) create mode 100644 app/icons/components/IcLayoutSingleURLs.tsx create mode 100644 app/icons/svg/ic_layout_single_urls.svg diff --git a/app/config-types.ts b/app/config-types.ts index ee37e757f..2bca6dd6d 100644 --- a/app/config-types.ts +++ b/app/config-types.ts @@ -1118,6 +1118,11 @@ const Layout = t.intersection([ layout: t.union([t.literal("vertical"), t.literal("tall")]), meta: Meta, }), + t.type({ + type: t.literal("singleURLs"), + publishableChartKeys: t.array(t.string), + meta: Meta, + }), ]), ]); export type Layout = t.TypeOf; diff --git a/app/configurator/components/configurator.tsx b/app/configurator/components/configurator.tsx index dcd79a4f5..03d139385 100644 --- a/app/configurator/components/configurator.tsx +++ b/app/configurator/components/configurator.tsx @@ -283,6 +283,27 @@ const LayoutingStep = () => { }); }} /> + { + if (state.layout.type === "singleURLs") { + return; + } + + dispatch({ + type: "LAYOUT_CHANGED", + value: { + type: "singleURLs", + publishableChartKeys: state.chartConfigs.map( + (chartConfig) => chartConfig.key + ), + meta: state.layout.meta, + activeField: undefined, + }, + }); + }} + /> { return "time"; case "animation": return "animation"; + case "layoutSingleURLs": + return "layoutSingleURLs"; case "layoutTab": return "layoutTab"; case "layoutDashboard": diff --git a/app/icons/components/IcLayoutSingleURLs.tsx b/app/icons/components/IcLayoutSingleURLs.tsx new file mode 100644 index 000000000..6667fa524 --- /dev/null +++ b/app/icons/components/IcLayoutSingleURLs.tsx @@ -0,0 +1,21 @@ +import * as React from "react"; + +function SvgIcLayoutSingleURLs(props: React.SVGProps) { + return ( + + + + ); +} + +export default SvgIcLayoutSingleURLs; diff --git a/app/icons/components/index.tsx b/app/icons/components/index.tsx index 274cdafe5..5afd05ad6 100644 --- a/app/icons/components/index.tsx +++ b/app/icons/components/index.tsx @@ -69,6 +69,7 @@ import { default as Indeterminate } from "@/icons/components/IcIndeterminate"; import { default as Info } from "@/icons/components/IcInfo"; import { default as Laptop } from "@/icons/components/IcLaptop"; import { default as LayoutDashboard } from "@/icons/components/IcLayoutDashboard"; +import { default as LayoutSingleURLs } from "@/icons/components/IcLayoutSingleURLs"; import { default as LayoutTab } from "@/icons/components/IcLayoutTab"; import { default as LayoutTall } from "@/icons/components/IcLayoutTall"; import { default as LayoutVertical } from "@/icons/components/IcLayoutVertical"; @@ -216,6 +217,7 @@ export const Icons = { info: Info, laptop: Laptop, layoutDashboard: LayoutDashboard, + layoutSingleURLs: LayoutSingleURLs, layoutTall: LayoutTall, layoutTab: LayoutTab, layoutVertical: LayoutVertical, diff --git a/app/icons/svg/ic_layout_single_urls.svg b/app/icons/svg/ic_layout_single_urls.svg new file mode 100644 index 000000000..551ae6d56 --- /dev/null +++ b/app/icons/svg/ic_layout_single_urls.svg @@ -0,0 +1,19 @@ + + + + + + + diff --git a/app/locales/de/messages.po b/app/locales/de/messages.po index 507ff07aa..1c5cb8ced 100644 --- a/app/locales/de/messages.po +++ b/app/locales/de/messages.po @@ -440,6 +440,7 @@ msgstr "Alle auswählen" msgid "controls.filter.select.none" msgstr "Alle abwählen" +#: app/configurator/components/field.tsx #: app/configurator/components/field.tsx msgid "controls.filter.use-most-recent" msgstr "Letztes Datum verwenden" @@ -538,6 +539,10 @@ msgstr "Italienisch" msgid "controls.layout.dashboard" msgstr "Dashboard" +#: app/configurator/components/field-i18n.ts +msgid "controls.layout.singleURLs" +msgstr "Einzelne URLs" + #: app/configurator/components/field-i18n.ts msgid "controls.layout.tab" msgstr "Tab Layout" @@ -674,6 +679,7 @@ msgstr "Spalten" msgid "controls.section.columnstyle" msgstr "Spaltenstil" +#: app/components/chart-filters-list.tsx #: app/configurator/components/chart-configurator.tsx msgid "controls.section.data.filters" msgstr "Filter" diff --git a/app/locales/en/messages.po b/app/locales/en/messages.po index e715fa93d..dc319a346 100644 --- a/app/locales/en/messages.po +++ b/app/locales/en/messages.po @@ -440,6 +440,7 @@ msgstr "Select all" msgid "controls.filter.select.none" msgstr "Select none" +#: app/configurator/components/field.tsx #: app/configurator/components/field.tsx msgid "controls.filter.use-most-recent" msgstr "Use most recent" @@ -538,6 +539,10 @@ msgstr "Italian" msgid "controls.layout.dashboard" msgstr "Dashboard" +#: app/configurator/components/field-i18n.ts +msgid "controls.layout.singleURLs" +msgstr "Single URLs" + #: app/configurator/components/field-i18n.ts msgid "controls.layout.tab" msgstr "Tab Layout" @@ -674,6 +679,7 @@ msgstr "Columns" msgid "controls.section.columnstyle" msgstr "Column Style" +#: app/components/chart-filters-list.tsx #: app/configurator/components/chart-configurator.tsx msgid "controls.section.data.filters" msgstr "Filters" diff --git a/app/locales/fr/messages.po b/app/locales/fr/messages.po index 080e87721..b8f3918fe 100644 --- a/app/locales/fr/messages.po +++ b/app/locales/fr/messages.po @@ -440,6 +440,7 @@ msgstr "Tout sélectionner" msgid "controls.filter.select.none" msgstr "Tout déselectionner" +#: app/configurator/components/field.tsx #: app/configurator/components/field.tsx msgid "controls.filter.use-most-recent" msgstr "Utiliser le plus récent" @@ -538,6 +539,10 @@ msgstr "Italien" msgid "controls.layout.dashboard" msgstr "Dashboard" +#: app/configurator/components/field-i18n.ts +msgid "controls.layout.singleURLs" +msgstr "URL uniques" + #: app/configurator/components/field-i18n.ts msgid "controls.layout.tab" msgstr "Tab Layout" @@ -674,6 +679,7 @@ msgstr "Colonnes" msgid "controls.section.columnstyle" msgstr "Style de la colonne" +#: app/components/chart-filters-list.tsx #: app/configurator/components/chart-configurator.tsx msgid "controls.section.data.filters" msgstr "Filtres" diff --git a/app/locales/it/messages.po b/app/locales/it/messages.po index 4a873d501..94a5b7be8 100644 --- a/app/locales/it/messages.po +++ b/app/locales/it/messages.po @@ -440,6 +440,7 @@ msgstr "Seleziona tutti" msgid "controls.filter.select.none" msgstr "Deseleziona tutto" +#: app/configurator/components/field.tsx #: app/configurator/components/field.tsx msgid "controls.filter.use-most-recent" msgstr "Usare il più recente" @@ -538,6 +539,10 @@ msgstr "Italiano" msgid "controls.layout.dashboard" msgstr "Dashboard" +#: app/configurator/components/field-i18n.ts +msgid "controls.layout.singleURLs" +msgstr "URL singolo" + #: app/configurator/components/field-i18n.ts msgid "controls.layout.tab" msgstr "Tab Layout" @@ -674,6 +679,7 @@ msgstr "Colonne" msgid "controls.section.columnstyle" msgstr "Stile della colonna" +#: app/components/chart-filters-list.tsx #: app/configurator/components/chart-configurator.tsx msgid "controls.section.data.filters" msgstr "Filtri" From eac3f6e54988d802acc3dd43c640dcb3ea316226 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Fri, 2 Feb 2024 10:57:47 +0100 Subject: [PATCH 02/11] feat: Respect debug flag in development mode --- app/components/debug-panel/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/debug-panel/index.tsx b/app/components/debug-panel/index.tsx index 944dfbc65..441d919fa 100644 --- a/app/components/debug-panel/index.tsx +++ b/app/components/debug-panel/index.tsx @@ -9,7 +9,7 @@ import { DebugPanelProps } from "./DebugPanel"; const LazyDebugPanel = dynamic(() => import("./DebugPanel"), { ssr: false }); const DebugPanel = (props: DebugPanelProps) => { - const shouldShow = flag("debug") || process.env.NODE_ENV === "development"; + const shouldShow = flag("debug") ?? process.env.NODE_ENV === "development"; if (!shouldShow) { return null; From 6ee14c15d1ac6f6fb20ac56089663beb6520873b Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Fri, 2 Feb 2024 11:22:29 +0100 Subject: [PATCH 03/11] feat: Add basic SingleURLsPreview component --- app/components/chart-preview.tsx | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/components/chart-preview.tsx b/app/components/chart-preview.tsx index fec724c93..b539d5028 100644 --- a/app/components/chart-preview.tsx +++ b/app/components/chart-preview.tsx @@ -70,6 +70,8 @@ export const ChartPreview = (props: ChartPreviewProps) => { return layout.type === "dashboard" && !editing ? ( + ) : layout.type === "singleURLs" ? ( + ) : ( @@ -246,6 +248,32 @@ const DndChartPreview = (props: DndChartPreviewProps) => { ); }; +const SingleURLsPreview = (props: ChartPreviewProps) => { + const { dataSource } = props; + const [state] = useConfiguratorState(hasChartConfigs); + + const renderChart = React.useCallback( + (chartConfig: ChartConfig) => ( + + + + + + ), + [dataSource] + ); + + return ( + + ); +}; + type ChartPreviewInnerProps = ChartPreviewProps & { chartKey?: string | null; dragHandleSlot?: React.ReactNode; From 34913ba79c1a80ad3b4584b8c0e05a497524a752 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Fri, 2 Feb 2024 11:33:36 +0100 Subject: [PATCH 04/11] feat: Publish layoutSingleURLs charts separately --- app/configurator/configurator-state.tsx | 191 +++++++++++++++--------- 1 file changed, 124 insertions(+), 67 deletions(-) diff --git a/app/configurator/configurator-state.tsx b/app/configurator/configurator-state.tsx index ba4c316b7..bcb2b222a 100644 --- a/app/configurator/configurator-state.tsx +++ b/app/configurator/configurator-state.tsx @@ -4,7 +4,7 @@ import pickBy from "lodash/pickBy"; import setWith from "lodash/setWith"; import sortBy from "lodash/sortBy"; import unset from "lodash/unset"; -import { useRouter } from "next/router"; +import { NextRouter, useRouter } from "next/router"; import { Dispatch, createContext, useContext, useEffect, useMemo } from "react"; import { Reducer, useImmerReducer } from "use-immer"; @@ -1622,74 +1622,70 @@ const ConfiguratorStateProviderInternal = ( case "PUBLISHING": (async () => { try { - let dbConfig: ParsedConfig | undefined; - const key = getRouterChartId(asPath); - - if (key && user) { - const config = await fetchChartConfig(key); - - if (config && config.user_id === user.id) { - dbConfig = config; + switch (state.layout.type) { + case "singleURLs": { + const reversedChartKeys = state.layout.publishableChartKeys + .slice() + .reverse(); + + // Do not use Promise.all here, as we want to publish the charts in order + // and not in parallel and keep the current tab open with first chart + reversedChartKeys.forEach(async (chartKey, i) => { + const preparedConfig = preparePublishingState( + { + ...state, + // Ensure that the layout is reset to single-chart mode + layout: { + type: "tab", + meta: state.layout.meta, + activeField: undefined, + }, + }, + state.chartConfigs.filter( + (d) => d.key === chartKey + ) as ChartConfig[] + ); + + const result = await createConfig(preparedConfig); + + if (i < reversedChartKeys.length - 1) { + // Open new tab for each chart, except the first one + return window.open( + `/${locale}/v/${result.key}`, + "_blank" + ); + } + + await handlePublishSuccess(result.key, push); + }); + } + default: { + let dbConfig: ParsedConfig | undefined; + const key = getRouterChartId(asPath); + + if (key && user) { + const config = await fetchChartConfig(key); + + if (config && config.user_id === user.id) { + dbConfig = config; + } + } + + const preparedConfig = preparePublishingState( + state, + state.chartConfigs + ); + + const result = await (dbConfig && user + ? updateConfig(preparedConfig, { + key: dbConfig.key, + userId: user.id, + }) + : createConfig(preparedConfig)); + + await handlePublishSuccess(result.key, push); } } - - const preparedConfig: ConfiguratorStatePublishing = { - ...state, - chartConfigs: [ - ...state.chartConfigs.map((chartConfig) => { - return { - ...chartConfig, - cubes: chartConfig.cubes.map((cube) => { - return { - ...cube, - // Ensure that the filters are in the correct order, as JSON - // does not guarantee order (and we need this as interactive - // filters are dependent on the order of the filters). - filters: Object.fromEntries( - Object.entries(cube.filters).map(([k, v], i) => { - return [k, { ...v, position: i }]; - }) - ), - }; - }), - }; - }), - ], - // Technically, we do not need to store the active chart key, as - // it's only used in the edit mode, but it makes it easier to manage - // the state when retrieving the chart from the database. Potentially, - // it might also be useful for other things in the future (e.g. when we - // have multiple charts in the "stepper mode", and we'd like to start - // the story from a specific point and e.g. toggle back and forth between - // the different charts). - activeChartKey: state.chartConfigs[0].key, - }; - - const result = await (dbConfig && user - ? updateConfig(preparedConfig, { - key: dbConfig.key, - userId: user.id, - }) - : createConfig(preparedConfig)); - - /** - * EXPERIMENTAL: Post back created chart ID to opener and close window. - * - * This allows the chart creation workflow to be integrated with other tools like a CMS - */ - - // FIXME: Check for more than just opener? - const opener = window.opener; - if (opener) { - opener.postMessage(`CHART_ID:${result.key}`, "*"); - window.close(); - return; - } - - await push({ - pathname: `/v/${result.key}`, - query: { publishSuccess: true }, - }); } catch (e) { console.error(e); dispatch({ type: "PUBLISH_FAILED" }); @@ -1721,6 +1717,67 @@ const ConfiguratorStateProviderInternal = ( ); }; +const preparePublishingState = ( + state: ConfiguratorStatePublishing, + chartConfigs: ChartConfig[] +): ConfiguratorStatePublishing => { + return { + ...state, + chartConfigs: [ + ...chartConfigs.map((chartConfig) => { + return { + ...chartConfig, + cubes: chartConfig.cubes.map((cube) => { + return { + ...cube, + // Ensure that the filters are in the correct order, as JSON + // does not guarantee order (and we need this as interactive + // filters are dependent on the order of the filters). + filters: Object.fromEntries( + Object.entries(cube.filters).map(([k, v], i) => { + return [k, { ...v, position: i }]; + }) + ), + }; + }), + }; + }), + ], + // Technically, we do not need to store the active chart key, as + // it's only used in the edit mode, but it makes it easier to manage + // the state when retrieving the chart from the database. Potentially, + // it might also be useful for other things in the future (e.g. when we + // have multiple charts in the "stepper mode", and we'd like to start + // the story from a specific point and e.g. toggle back and forth between + // the different charts). + activeChartKey: state.chartConfigs[0].key, + }; +}; + +const handlePublishSuccess = async ( + chartId: string, + push: NextRouter["push"] +) => { + /** + * EXPERIMENTAL: Post back created chart ID to opener and close window. + * + * This allows the chart creation workflow to be integrated with other tools like a CMS + */ + + // FIXME: Check for more than just opener? + const opener = window.opener; + if (opener) { + opener.postMessage(`CHART_ID:${chartId}`, "*"); + window.close(); + return; + } + + await push({ + pathname: `/v/${chartId}`, + query: { publishSuccess: true }, + }); +}; + type ConfiguratorStateProviderProps = React.PropsWithChildren<{ chartId: string; initialState?: ConfiguratorState; From 4fa8d0bdf0c68b6f1468cb5b3c64a8b4072f34d7 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Fri, 2 Feb 2024 11:36:38 +0100 Subject: [PATCH 05/11] fix: Single URLs do not make use of global annotations --- app/charts/index.ts | 2 +- app/configurator/components/configurator.tsx | 69 +++++++++++--------- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/app/charts/index.ts b/app/charts/index.ts index cd679dbf9..7a753d02d 100644 --- a/app/charts/index.ts +++ b/app/charts/index.ts @@ -311,7 +311,7 @@ export const getInitialSymbolLayer = ({ }; }; -const META: Meta = { +export const META: Meta = { title: { en: "", de: "", diff --git a/app/configurator/components/configurator.tsx b/app/configurator/components/configurator.tsx index 03d139385..3b13ee478 100644 --- a/app/configurator/components/configurator.tsx +++ b/app/configurator/components/configurator.tsx @@ -6,6 +6,7 @@ import { useRouter } from "next/router"; import React from "react"; import { SelectDatasetStep } from "@/browser/select-dataset-step"; +import { META } from "@/charts"; import { ChartPreview } from "@/components/chart-preview"; import { PublishChartButton } from "@/components/chart-selection-tabs"; import { HEADER_HEIGHT } from "@/components/header"; @@ -298,7 +299,9 @@ const LayoutingStep = () => { publishableChartKeys: state.chartConfigs.map( (chartConfig) => chartConfig.key ), - meta: state.layout.meta, + // Clear the meta data, as it's not used in singleURLs layout, + // but makes the types more consistent + meta: META, activeField: undefined, }, }); @@ -320,38 +323,40 @@ const LayoutingStep = () => { - - { - if (state.layout.activeField !== "title") { - dispatch({ - type: "LAYOUT_ACTIVE_FIELD_CHANGED", - value: "title", - }); - } - }} - /> - <Description - text={state.layout.meta.description[locale]} - onClick={() => { - if (state.layout.activeField !== "description") { - dispatch({ - type: "LAYOUT_ACTIVE_FIELD_CHANGED", - value: "description", - }); - } + {state.layout.type !== "singleURLs" && ( + <Box + sx={{ + display: "flex", + flexDirection: "column", + gap: 1, + mt: 3, + mb: 4, }} - /> - </Box> + > + <Title + text={state.layout.meta.title[locale]} + onClick={() => { + if (state.layout.activeField !== "title") { + dispatch({ + type: "LAYOUT_ACTIVE_FIELD_CHANGED", + value: "title", + }); + } + }} + /> + <Description + text={state.layout.meta.description[locale]} + onClick={() => { + if (state.layout.activeField !== "description") { + dispatch({ + type: "LAYOUT_ACTIVE_FIELD_CHANGED", + value: "description", + }); + } + }} + /> + </Box> + )} <ChartPreview dataSource={state.dataSource} /> </PanelBodyWrapper> <ConfiguratorDrawer From cb978c625912dd27b792911470fc630dbc70aab4 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski <bartosz@interactivethings.com> Date: Fri, 2 Feb 2024 12:12:47 +0100 Subject: [PATCH 06/11] fix: Keep correct chart key --- app/configurator/configurator-state.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/configurator/configurator-state.tsx b/app/configurator/configurator-state.tsx index bcb2b222a..269723b7c 100644 --- a/app/configurator/configurator-state.tsx +++ b/app/configurator/configurator-state.tsx @@ -1643,7 +1643,8 @@ const ConfiguratorStateProviderInternal = ( }, state.chartConfigs.filter( (d) => d.key === chartKey - ) as ChartConfig[] + ) as ChartConfig[], + chartKey ); const result = await createConfig(preparedConfig); @@ -1719,7 +1720,8 @@ const ConfiguratorStateProviderInternal = ( const preparePublishingState = ( state: ConfiguratorStatePublishing, - chartConfigs: ChartConfig[] + chartConfigs: ChartConfig[], + activeChartKey?: string ): ConfiguratorStatePublishing => { return { ...state, @@ -1750,7 +1752,7 @@ const preparePublishingState = ( // have multiple charts in the "stepper mode", and we'd like to start // the story from a specific point and e.g. toggle back and forth between // the different charts). - activeChartKey: state.chartConfigs[0].key, + activeChartKey: activeChartKey ?? state.chartConfigs[0].key, }; }; From e78a14c5aa9c485d62e005e84d44b054d4559ab8 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski <bartosz@interactivethings.com> Date: Fri, 2 Feb 2024 12:30:36 +0100 Subject: [PATCH 07/11] fix: Properly return from switch case --- app/configurator/configurator-state.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/configurator/configurator-state.tsx b/app/configurator/configurator-state.tsx index 269723b7c..fde61dfc1 100644 --- a/app/configurator/configurator-state.tsx +++ b/app/configurator/configurator-state.tsx @@ -1630,7 +1630,7 @@ const ConfiguratorStateProviderInternal = ( // Do not use Promise.all here, as we want to publish the charts in order // and not in parallel and keep the current tab open with first chart - reversedChartKeys.forEach(async (chartKey, i) => { + return reversedChartKeys.forEach(async (chartKey, i) => { const preparedConfig = preparePublishingState( { ...state, From 4cbc76b9d5e4d375ec51fcdcaa8c08ae314852cc Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski <bartosz@interactivethings.com> Date: Fri, 2 Feb 2024 12:32:13 +0100 Subject: [PATCH 08/11] feat: Add checkboxes to select which charts to publish in singleURLs layout mode --- app/components/chart-preview.tsx | 75 +++++++++++++++++++++++--------- app/components/form.tsx | 2 +- 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/app/components/chart-preview.tsx b/app/components/chart-preview.tsx index b539d5028..1af94c3ef 100644 --- a/app/components/chart-preview.tsx +++ b/app/components/chart-preview.tsx @@ -34,6 +34,7 @@ import { import { ChartWithFilters } from "@/components/chart-with-filters"; import DebugPanel from "@/components/debug-panel"; import Flex from "@/components/flex"; +import { Checkbox } from "@/components/form"; import { HintYellow } from "@/components/hint"; import { MetadataPanel } from "@/components/metadata-panel"; import { @@ -71,7 +72,7 @@ export const ChartPreview = (props: ChartPreviewProps) => { return layout.type === "dashboard" && !editing ? ( <DashboardPreview dataSource={dataSource} layoutType={layout.layout} /> ) : layout.type === "singleURLs" ? ( - <SingleURLsPreview dataSource={dataSource} /> + <SingleURLsPreview dataSource={dataSource} layout={layout} /> ) : ( <ChartTablePreviewProvider> <ChartWrapper editing={editing} layoutType={layout.type}> @@ -171,7 +172,7 @@ const DashboardPreview = (props: DashboardPreviewProps) => { <ChartPreviewInner dataSource={dataSource} chartKey={activeChartKey} - dragHandleSlot={<DragHandle dragging />} + actionElementSlot={<DragHandle dragging />} /> </ChartWrapper> </DragOverlay> @@ -235,7 +236,7 @@ const DndChartPreview = (props: DndChartPreviewProps) => { <ChartPreviewInner dataSource={dataSource} chartKey={chartKey} - dragHandleSlot={ + actionElementSlot={ <DragHandle {...listeners} ref={setActivatorNodeRef} @@ -248,22 +249,55 @@ const DndChartPreview = (props: DndChartPreviewProps) => { ); }; -const SingleURLsPreview = (props: ChartPreviewProps) => { - const { dataSource } = props; - const [state] = useConfiguratorState(hasChartConfigs); +type SingleURLsPreviewProps = ChartPreviewProps & { + layout: Extract<Layout, { type: "singleURLs" }>; +}; +const SingleURLsPreview = (props: SingleURLsPreviewProps) => { + const { dataSource, layout } = props; + const [state, dispatch] = useConfiguratorState(hasChartConfigs); const renderChart = React.useCallback( - (chartConfig: ChartConfig) => ( - <ChartTablePreviewProvider> - <ChartWrapper> - <ChartPreviewInner - dataSource={dataSource} - chartKey={chartConfig.key} - /> - </ChartWrapper> - </ChartTablePreviewProvider> - ), - [dataSource] + (chartConfig: ChartConfig) => { + const checked = layout.publishableChartKeys.includes(chartConfig.key); + const { publishableChartKeys: keys } = layout; + const { key } = chartConfig; + + return ( + <ChartTablePreviewProvider> + <ChartWrapper> + <ChartPreviewInner + dataSource={dataSource} + chartKey={chartConfig.key} + actionElementSlot={ + <Checkbox + checked={checked} + onChange={() => { + // At least one chart must be publishable + if (keys.length === 1 && checked) { + return; + } + + dispatch({ + type: "LAYOUT_CHANGED", + value: { + ...layout, + publishableChartKeys: checked + ? keys.filter((k) => k !== key) + : state.chartConfigs + .map((c) => c.key) + .filter((k) => keys.includes(k) || k === key), + }, + }); + }} + label="" + /> + } + /> + </ChartWrapper> + </ChartTablePreviewProvider> + ); + }, + [dataSource, dispatch, layout, state.chartConfigs] ); return ( @@ -276,12 +310,13 @@ const SingleURLsPreview = (props: ChartPreviewProps) => { type ChartPreviewInnerProps = ChartPreviewProps & { chartKey?: string | null; - dragHandleSlot?: React.ReactNode; + actionElementSlot?: React.ReactNode; disableMetadataPanel?: boolean; }; export const ChartPreviewInner = (props: ChartPreviewInnerProps) => { - const { dataSource, chartKey, dragHandleSlot, disableMetadataPanel } = props; + const { dataSource, chartKey, actionElementSlot, disableMetadataPanel } = + props; const [state, dispatch] = useConfiguratorState(); const chartConfig = getChartConfig(state, chartKey); const locale = useLocale(); @@ -421,7 +456,7 @@ export const ChartPreviewInner = (props: ChartPreviewInnerProps) => { top={96} /> )} - {dragHandleSlot} + {actionElementSlot} </Flex> </Flex> {(state.state === "CONFIGURING_CHART" || diff --git a/app/components/form.tsx b/app/components/form.tsx index 496a53245..1b7a95cc9 100644 --- a/app/components/form.tsx +++ b/app/components/form.tsx @@ -206,7 +206,7 @@ export const Checkbox = ({ className, }: CheckboxProps) => ( <FormControlLabel - label={label || "-"} + label={label} htmlFor={`${name}`} disabled={disabled} className={className} From 2dd960d0a5f122f5e0c3b234553f6664ff001d07 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski <bartosz@interactivethings.com> Date: Fri, 2 Feb 2024 12:49:00 +0100 Subject: [PATCH 09/11] feat: Update SingleURLs Configurator layout --- app/configurator/components/configurator.tsx | 93 +++++++++++++------- 1 file changed, 62 insertions(+), 31 deletions(-) diff --git a/app/configurator/components/configurator.tsx b/app/configurator/components/configurator.tsx index 3b13ee478..1273a9afe 100644 --- a/app/configurator/components/configurator.tsx +++ b/app/configurator/components/configurator.tsx @@ -1,5 +1,5 @@ import { Trans } from "@lingui/macro"; -import { SxProps } from "@mui/material"; +import { SxProps, Typography } from "@mui/material"; import Box from "@mui/material/Box"; import Button, { ButtonProps } from "@mui/material/Button"; import { useRouter } from "next/router"; @@ -225,8 +225,14 @@ const LayoutingStep = () => { return null; } + const isSingleURLs = state.layout.type === "singleURLs"; + return ( - <PanelLayout type="LM" sx={{ background: (t) => t.palette.muted.main }}> + <PanelLayout + // SingleURLs layout doesn't have an options panel + type={isSingleURLs ? "M" : "LM"} + sx={{ background: (t) => t.palette.muted.main }} + > <PanelHeaderLayout type="LMR"> <PanelHeaderWrapper type="L"> <BackContainer> @@ -319,45 +325,70 @@ const LayoutingStep = () => { <PublishChartButton /> </PanelHeaderWrapper> </PanelHeaderLayout> - <PanelBodyWrapper type="L"> - <LayoutConfigurator /> - </PanelBodyWrapper> + {!isSingleURLs && ( + <PanelBodyWrapper type="L"> + <LayoutConfigurator /> + </PanelBodyWrapper> + )} <PanelBodyWrapper type="M"> - {state.layout.type !== "singleURLs" && ( + <Box + sx={ + isSingleURLs + ? { + width: "100%", + maxWidth: { xs: "100%", lg: 1280 }, + mx: "auto", + } + : {} + } + > <Box sx={{ display: "flex", flexDirection: "column", gap: 1, mt: 3, - mb: 4, + mb: isSingleURLs ? 6 : 4, }} > - <Title - text={state.layout.meta.title[locale]} - onClick={() => { - if (state.layout.activeField !== "title") { - dispatch({ - type: "LAYOUT_ACTIVE_FIELD_CHANGED", - value: "title", - }); - } - }} - /> - <Description - text={state.layout.meta.description[locale]} - onClick={() => { - if (state.layout.activeField !== "description") { - dispatch({ - type: "LAYOUT_ACTIVE_FIELD_CHANGED", - value: "description", - }); - } - }} - /> + {isSingleURLs ? ( + <Typography + variant="h3" + sx={{ fontWeight: "normal", color: "secondary.active" }} + > + <Trans id="controls.layout.singleURLs.publish"> + Select the charts to publish separately. + </Trans> + </Typography> + ) : ( + <> + <Title + text={state.layout.meta.title[locale]} + onClick={() => { + if (state.layout.activeField !== "title") { + dispatch({ + type: "LAYOUT_ACTIVE_FIELD_CHANGED", + value: "title", + }); + } + }} + /> + <Description + text={state.layout.meta.description[locale]} + onClick={() => { + if (state.layout.activeField !== "description") { + dispatch({ + type: "LAYOUT_ACTIVE_FIELD_CHANGED", + value: "description", + }); + } + }} + /> + </> + )} </Box> - )} - <ChartPreview dataSource={state.dataSource} /> + <ChartPreview dataSource={state.dataSource} /> + </Box> </PanelBodyWrapper> <ConfiguratorDrawer anchor="left" From 7dd8a4d79a0a9d0e4e0ccd5772caa75022b43e35 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski <bartosz@interactivethings.com> Date: Fri, 2 Feb 2024 12:50:07 +0100 Subject: [PATCH 10/11] chore: Update translations --- app/locales/de/messages.po | 4 ++++ app/locales/en/messages.po | 4 ++++ app/locales/fr/messages.po | 4 ++++ app/locales/it/messages.po | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/app/locales/de/messages.po b/app/locales/de/messages.po index 1c5cb8ced..edb5f5825 100644 --- a/app/locales/de/messages.po +++ b/app/locales/de/messages.po @@ -543,6 +543,10 @@ msgstr "Dashboard" msgid "controls.layout.singleURLs" msgstr "Einzelne URLs" +#: app/configurator/components/configurator.tsx +msgid "controls.layout.singleURLs.publish" +msgstr "Wählen Sie die separat zu veröffentlichenden Diagramme aus." + #: app/configurator/components/field-i18n.ts msgid "controls.layout.tab" msgstr "Tab Layout" diff --git a/app/locales/en/messages.po b/app/locales/en/messages.po index dc319a346..03cd36e8e 100644 --- a/app/locales/en/messages.po +++ b/app/locales/en/messages.po @@ -543,6 +543,10 @@ msgstr "Dashboard" msgid "controls.layout.singleURLs" msgstr "Single URLs" +#: app/configurator/components/configurator.tsx +msgid "controls.layout.singleURLs.publish" +msgstr "Select the charts to publish separately." + #: app/configurator/components/field-i18n.ts msgid "controls.layout.tab" msgstr "Tab Layout" diff --git a/app/locales/fr/messages.po b/app/locales/fr/messages.po index b8f3918fe..b2a6e8785 100644 --- a/app/locales/fr/messages.po +++ b/app/locales/fr/messages.po @@ -543,6 +543,10 @@ msgstr "Dashboard" msgid "controls.layout.singleURLs" msgstr "URL uniques" +#: app/configurator/components/configurator.tsx +msgid "controls.layout.singleURLs.publish" +msgstr "Sélectionner les graphiques à publier séparément." + #: app/configurator/components/field-i18n.ts msgid "controls.layout.tab" msgstr "Tab Layout" diff --git a/app/locales/it/messages.po b/app/locales/it/messages.po index 94a5b7be8..00577db09 100644 --- a/app/locales/it/messages.po +++ b/app/locales/it/messages.po @@ -543,6 +543,10 @@ msgstr "Dashboard" msgid "controls.layout.singleURLs" msgstr "URL singolo" +#: app/configurator/components/configurator.tsx +msgid "controls.layout.singleURLs.publish" +msgstr "Selezionare i grafici da pubblicare separatamente." + #: app/configurator/components/field-i18n.ts msgid "controls.layout.tab" msgstr "Tab Layout" From bd1bb84ffef2426c15060d5c23eeb3c271749606 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski <bartosz@interactivethings.com> Date: Fri, 2 Feb 2024 12:57:21 +0100 Subject: [PATCH 11/11] feat: Properly disable Checkbox --- app/components/chart-preview.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/components/chart-preview.tsx b/app/components/chart-preview.tsx index 1af94c3ef..e7cae79f1 100644 --- a/app/components/chart-preview.tsx +++ b/app/components/chart-preview.tsx @@ -271,12 +271,8 @@ const SingleURLsPreview = (props: SingleURLsPreviewProps) => { actionElementSlot={ <Checkbox checked={checked} + disabled={keys.length === 1 && checked} onChange={() => { - // At least one chart must be publishable - if (keys.length === 1 && checked) { - return; - } - dispatch({ type: "LAYOUT_CHANGED", value: {