diff --git a/CHANGELOG.md b/CHANGELOG.md
index 914f338f..7ffbd820 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,3 @@
-
# Changelog
All notable changes to this project will be documented in this file.
diff --git a/e2e/feed-sources.spec.js b/e2e/feed-sources.spec.js
index fe46480c..2380d695 100644
--- a/e2e/feed-sources.spec.js
+++ b/e2e/feed-sources.spec.js
@@ -219,7 +219,7 @@ test.describe("fest", () => {
test("It loads create datakilde page", async ({ page }) => {
page.getByText("Opret ny datakilde").click();
- await expect(page.locator("#save_feed-source")).toBeVisible();
+ await expect(page.locator("#save")).toBeVisible();
});
test("It display error toast on save error", async ({ page }) => {
@@ -238,7 +238,7 @@ test.describe("fest", () => {
await expect(
page.locator(".Toastify").locator(".Toastify__toast--error")
).not.toBeVisible();
- await page.locator("#save_feed-source").click();
+ await page.locator("#save").click();
await expect(
page.locator(".Toastify").locator(".Toastify__toast--error")
).toBeVisible();
@@ -253,9 +253,9 @@ test.describe("fest", () => {
});
test("Cancel create datakilde", async ({ page }) => {
page.getByText("Opret ny datakilde").click();
- await expect(page.locator("#cancel_feed-source")).toBeVisible();
- await page.locator("#cancel_feed-source").click();
- await expect(page.locator("#cancel_feed-source")).not.toBeVisible();
+ await expect(page.locator("#cancel")).toBeVisible();
+ await page.locator("#cancel").click();
+ await expect(page.locator("#cancel")).not.toBeVisible();
});
});
diff --git a/e2e/playlist.spec.js b/e2e/playlist.spec.js
index 465a1e94..0e118c20 100644
--- a/e2e/playlist.spec.js
+++ b/e2e/playlist.spec.js
@@ -59,7 +59,7 @@ test.describe("Playlist create tests", () => {
await expect(
page.locator(".Toastify").locator(".Toastify__toast--success")
).not.toBeVisible();
- await page.locator("#save_playlist").click();
+ await page.locator("#save_slide_and_close").click();
await expect(
page
.locator(".Toastify")
diff --git a/infrastructure/itkdev/etc/confd/templates/config.tmpl b/infrastructure/itkdev/etc/confd/templates/config.tmpl
index 90ce5b67..e88daea4 100644
--- a/infrastructure/itkdev/etc/confd/templates/config.tmpl
+++ b/infrastructure/itkdev/etc/confd/templates/config.tmpl
@@ -1,6 +1,7 @@
{
"api": "{{ getenv "API_PATH" "/" }}",
"touchButtonRegions": "{{ getenv "APP_TOUCH_BUTTON_REGIONS" "false"}}",
+ "previewClient": "{{ getenv "APP_PREVIEW_CLIENT" "null"}}",
"rejseplanenApiKey": "{{ getenv "APP_REJSEPLANEN_API_KEY" "null"}}",
"loginMethods": [
{
diff --git a/infrastructure/os2display/etc/confd/templates/config.tmpl b/infrastructure/os2display/etc/confd/templates/config.tmpl
index 90ce5b67..e88daea4 100644
--- a/infrastructure/os2display/etc/confd/templates/config.tmpl
+++ b/infrastructure/os2display/etc/confd/templates/config.tmpl
@@ -1,6 +1,7 @@
{
"api": "{{ getenv "API_PATH" "/" }}",
"touchButtonRegions": "{{ getenv "APP_TOUCH_BUTTON_REGIONS" "false"}}",
+ "previewClient": "{{ getenv "APP_PREVIEW_CLIENT" "null"}}",
"rejseplanenApiKey": "{{ getenv "APP_REJSEPLANEN_API_KEY" "null"}}",
"loginMethods": [
{
diff --git a/public/example_config.json b/public/example_config.json
index 3ecbf9a6..66a93888 100644
--- a/public/example_config.json
+++ b/public/example_config.json
@@ -1,6 +1,7 @@
{
"api": "/",
"touchButtonRegions": false,
+ "previewClient": null,
"rejseplanenApiKey": null,
"loginMethods": [
{
diff --git a/src/app.jsx b/src/app.jsx
index 6f1d9c1a..1826fc41 100644
--- a/src/app.jsx
+++ b/src/app.jsx
@@ -213,7 +213,7 @@ function App() {
{accessConfig && (
-
+
+
- >
+
);
}
diff --git a/src/components/activation-code/activation-code-form.jsx b/src/components/activation-code/activation-code-form.jsx
index 6d4a6d82..6d4b4c63 100644
--- a/src/components/activation-code/activation-code-form.jsx
+++ b/src/components/activation-code/activation-code-form.jsx
@@ -1,14 +1,14 @@
import { React } from "react";
import { useNavigate } from "react-router-dom";
-import { Button } from "react-bootstrap";
+import { Button, Col, Row } from "react-bootstrap";
import { useTranslation } from "react-i18next";
import PropTypes from "prop-types";
import Form from "react-bootstrap/Form";
import LoadingComponent from "../util/loading-component/loading-component";
import ContentBody from "../util/content-body/content-body";
-import ContentFooter from "../util/content-footer/content-footer";
import FormInput from "../util/forms/form-input";
import RadioButtons from "../util/forms/radio-buttons";
+import StickyFooter from "../util/sticky-footer";
/**
* The user form component.
@@ -48,44 +48,48 @@ function ActivationCodeForm({
<>
>
);
diff --git a/src/components/activation-code/activation-code-list.jsx b/src/components/activation-code/activation-code-list.jsx
index a5910ebc..cbf72dd9 100644
--- a/src/components/activation-code/activation-code-list.jsx
+++ b/src/components/activation-code/activation-code-list.jsx
@@ -185,7 +185,7 @@ function ActivationCodeList() {
}, [listData]);
return (
- <>
+
- >
+
);
}
diff --git a/src/components/feed-sources/feed-source-form.jsx b/src/components/feed-sources/feed-source-form.jsx
index 2cce65ae..4ad26434 100644
--- a/src/components/feed-sources/feed-source-form.jsx
+++ b/src/components/feed-sources/feed-source-form.jsx
@@ -1,5 +1,5 @@
import { React } from "react";
-import { Button } from "react-bootstrap";
+import { Button, Row, Col } from "react-bootstrap";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
@@ -8,11 +8,11 @@ import LoadingComponent from "../util/loading-component/loading-component";
import FormInputArea from "../util/forms/form-input-area";
import FormSelect from "../util/forms/select";
import ContentBody from "../util/content-body/content-body";
-import ContentFooter from "../util/content-footer/content-footer";
import FormInput from "../util/forms/form-input";
import CalendarApiFeedType from "./templates/calendar-api-feed-type";
import NotifiedFeedType from "./templates/notified-feed-type";
import EventDatabaseApiFeedType from "./templates/event-database-feed-type";
+import StickyFooter from "../util/sticky-footer";
/**
* The feed-source form component.
@@ -21,6 +21,7 @@ import EventDatabaseApiFeedType from "./templates/event-database-feed-type";
* @param {object} props.feedSource The feed-source object to modify in the form.
* @param {Function} props.handleInput Handles form input.
* @param {Function} props.handleSubmit Handles form submit.
+ * @param {Function} props.handleSaveNoClose Handles form submit with close.
* @param {string} props.headerText Headline text.
* @param {boolean} [props.isLoading] Indicator of whether the form is loading.
* Default is `false`
@@ -35,6 +36,7 @@ import EventDatabaseApiFeedType from "./templates/event-database-feed-type";
function FeedSourceForm({
handleInput,
handleSubmit,
+ handleSaveNoClose,
headerText,
isLoading = false,
loadingMessage = "",
@@ -54,78 +56,90 @@ function FeedSourceForm({
isLoading={isLoading}
loadingMessage={loadingMessage}
/>
- {headerText}
-
-
-
-
+
+ {headerText}
+
+
+
+
+
- {feedSource?.feedType === "App\\Feed\\CalendarApiFeedType" && (
-
- )}
- {feedSource?.feedType === "App\\Feed\\EventDatabaseApiFeedType" && (
-
- )}
- {feedSource?.feedType === "App\\Feed\\NotifiedFeedType" && (
-
- )}
-
-
+ {feedSource?.feedType === "App\\Feed\\CalendarApiFeedType" && (
+
+ )}
+ {feedSource?.feedType ===
+ "App\\Feed\\EventDatabaseApiFeedType" && (
+
+ )}
+ {feedSource?.feedType === "App\\Feed\\NotifiedFeedType" && (
+
+ )}
+
+
+
+
+
-
+
>
);
@@ -140,6 +154,7 @@ FeedSourceForm.propTypes = {
}),
handleInput: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
+ handleSaveNoClose: PropTypes.func.isRequired,
handleSecretInput: PropTypes.func.isRequired,
onFeedTypeChange: PropTypes.func.isRequired,
headerText: PropTypes.string.isRequired,
diff --git a/src/components/feed-sources/feed-source-manager.jsx b/src/components/feed-sources/feed-source-manager.jsx
index 04fb2d9b..9be2ee9d 100644
--- a/src/components/feed-sources/feed-source-manager.jsx
+++ b/src/components/feed-sources/feed-source-manager.jsx
@@ -11,6 +11,7 @@ import {
displayError,
displaySuccess,
} from "../util/list/toast-component/display-toast";
+import idFromUrl from "../util/helpers/id-from-url";
/**
* The theme manager component.
@@ -47,10 +48,11 @@ function FeedSourceManager({
const [submitting, setSubmitting] = useState(false);
const [formStateObject, setFormStateObject] = useState({});
+ const [saveWithoutClose, setSaveWithoutClose] = useState(false);
const [
postV2FeedSources,
- { error: saveErrorPost, isSuccess: isSaveSuccessPost },
+ { error: saveErrorPost, isSuccess: isSaveSuccessPost, data },
] = usePostV2FeedSourcesMutation();
const [
@@ -160,7 +162,16 @@ function FeedSourceManager({
if (isSaveSuccessPost || isSaveSuccessPut) {
setSubmitting(false);
displaySuccess(t("success-messages.saved-feed-source"));
- navigate("/feed-sources/list");
+
+ if (saveWithoutClose) {
+ setSaveWithoutClose(false);
+
+ if (isSaveSuccessPost) {
+ navigate(`/feed-sources/edit/${idFromUrl(data["@id"])}`);
+ }
+ } else {
+ navigate(`/feed-sources/list`);
+ }
}
}, [isSaveSuccessPut, isSaveSuccessPost]);
@@ -170,6 +181,11 @@ function FeedSourceManager({
saveFeedSource();
};
+ const handleSaveNoClose = () => {
+ setSaveWithoutClose(true);
+ handleSubmit();
+ };
+
/** If the theme is saved with error, display the error message */
useEffect(() => {
if (saveErrorPut || saveErrorPost) {
@@ -187,6 +203,7 @@ function FeedSourceManager({
headerText={`${headerText}: ${formStateObject?.title}`}
handleInput={handleInput}
handleSubmit={handleSubmit}
+ handleSaveNoClose={handleSaveNoClose}
isLoading={isLoading || submitting}
loadingMessage={loadingMessage}
onFeedTypeChange={onFeedTypeChange}
diff --git a/src/components/feed-sources/feed-sources-list.jsx b/src/components/feed-sources/feed-sources-list.jsx
index aa852c00..514c4d59 100644
--- a/src/components/feed-sources/feed-sources-list.jsx
+++ b/src/components/feed-sources/feed-sources-list.jsx
@@ -129,7 +129,7 @@ function FeedSourcesList() {
}, [feedSourcesGetError]);
return (
- <>
+
)}
- >
+
);
}
diff --git a/src/components/groups/group-create.jsx b/src/components/groups/group-create.jsx
index 3c7b11b1..c3ef0372 100644
--- a/src/components/groups/group-create.jsx
+++ b/src/components/groups/group-create.jsx
@@ -71,14 +71,16 @@ function GroupCreate() {
};
return (
-
+
+
+
);
}
diff --git a/src/components/groups/group-edit.jsx b/src/components/groups/group-edit.jsx
index baaec894..7009f08f 100644
--- a/src/components/groups/group-edit.jsx
+++ b/src/components/groups/group-edit.jsx
@@ -1,7 +1,6 @@
import { React, useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
-
import {
useGetV2ScreenGroupsByIdQuery,
usePutV2ScreenGroupsByIdMutation,
@@ -103,7 +102,7 @@ function GroupEdit() {
};
return (
- <>
+
{formStateObject && (
)}
- >
+
);
}
diff --git a/src/components/groups/groups-list.jsx b/src/components/groups/groups-list.jsx
index 07f99a79..1031a790 100644
--- a/src/components/groups/groups-list.jsx
+++ b/src/components/groups/groups-list.jsx
@@ -125,7 +125,7 @@ function GroupsList() {
}, [groupsGetError]);
return (
- <>
+
- >
+
);
}
diff --git a/src/components/media/media-list.jsx b/src/components/media/media-list.jsx
index b0bdca65..9009158b 100644
--- a/src/components/media/media-list.jsx
+++ b/src/components/media/media-list.jsx
@@ -174,7 +174,7 @@ function MediaList({ fromModal = false, multiple = true }) {
};
return (
- <>
+
@@ -233,7 +233,7 @@ function MediaList({ fromModal = false, multiple = true }) {
currentPage={page}
onPageChange={updateUrlAndChangePage}
/>
- >
+
);
}
diff --git a/src/components/playlist/playlist-campaign-form.jsx b/src/components/playlist/playlist-campaign-form.jsx
index 69f9fd65..e194f15e 100644
--- a/src/components/playlist/playlist-campaign-form.jsx
+++ b/src/components/playlist/playlist-campaign-form.jsx
@@ -1,14 +1,21 @@
-import { React, useState } from "react";
-import { Button, Col, Form, Row } from "react-bootstrap";
+import { React, useContext, useState } from "react";
+import { Alert, Button, Col, Form, Row } from "react-bootstrap";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faExpand } from "@fortawesome/free-solid-svg-icons";
import ContentBody from "../util/content-body/content-body";
-import ContentFooter from "../util/content-footer/content-footer";
import FormInput from "../util/forms/form-input";
import FormInputArea from "../util/forms/form-input-area";
import SelectSlidesTable from "../util/multi-and-table/select-slides-table";
import LoadingComponent from "../util/loading-component/loading-component";
+import Preview from "../preview/preview";
+import idFromUrl from "../util/helpers/id-from-url";
+import StickyFooter from "../util/sticky-footer";
+import localStorageKeys from "../util/local-storage-keys";
+import Select from "../util/forms/select";
+import userContext from "../../context/user-context";
/**
* The shared form component.
@@ -24,11 +31,13 @@ import LoadingComponent from "../util/loading-component/loading-component";
* @param {boolean} props.isCampaign If it is a campaign form.
* @param {string} props.location Either playlist or campaign.
* @param {Array} props.children The children being passed from parent
+ * @param {Function} props.handleSaveNoClose Handles form submit with close.
* @returns {object} The form shared by campaigns and playlists.
*/
function PlaylistCampaignForm({
handleInput,
handleSubmit,
+ handleSaveNoClose,
headerText,
location,
children,
@@ -38,13 +47,38 @@ function PlaylistCampaignForm({
isCampaign = false,
playlist = null,
}) {
- const { t } = useTranslation("common");
+ const { t } = useTranslation("common", {
+ keyPrefix: "playlist-campaign-form",
+ });
+ const { config } = useContext(userContext);
+
+ const previewOrientationOptions = [
+ {
+ value: "horizontal",
+ title: t("preview-orientation-landscape"),
+ key: "horizontal",
+ },
+ {
+ value: "vertical",
+ title: t("preview-orientation-portrait"),
+ key: "vertical",
+ },
+ ];
+ const [previewOrientation, setPreviewOrientation] = useState(
+ previewOrientationOptions[0].value
+ );
const navigate = useNavigate();
const [publishedFromError, setPublishedFromError] = useState(false);
const [publishedToError, setPublishedToError] = useState(false);
+ const [displayPreview, setDisplayPreview] = useState(null);
+ const [previewOverlayVisible, setPreviewOverlayVisible] = useState(false);
- /** Check if published is set */
- const checkInputsHandleSubmit = () => {
+ /**
+ * Check if published is set
+ *
+ * @param {boolean | null} noRedirect - Save without redirect.
+ */
+ const checkInputsHandleSubmit = (noRedirect) => {
setPublishedToError(false);
setPublishedFromError(false);
let submit = true;
@@ -58,97 +92,214 @@ function PlaylistCampaignForm({
}
if (submit) {
- handleSubmit();
+ if (noRedirect === true) {
+ handleSaveNoClose();
+ } else {
+ handleSubmit();
+ }
}
};
+ /** Toggle display preview. */
+ const toggleDisplayPreview = () => {
+ const newValue = !displayPreview;
+ localStorage.setItem(localStorageKeys.PREVIEW, newValue);
+ setDisplayPreview(newValue);
+ };
+
return (
<>
>
);
@@ -157,16 +308,16 @@ function PlaylistCampaignForm({
PlaylistCampaignForm.propTypes = {
playlist: PropTypes.shape({
description: PropTypes.string,
-
+ "@id": PropTypes.string.isRequired,
published: PropTypes.shape({
from: PropTypes.string,
to: PropTypes.string,
}),
-
title: PropTypes.string,
}),
handleInput: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
+ handleSaveNoClose: PropTypes.func.isRequired,
headerText: PropTypes.string.isRequired,
slideId: PropTypes.string,
isLoading: PropTypes.bool,
diff --git a/src/components/playlist/playlist-campaign-list.jsx b/src/components/playlist/playlist-campaign-list.jsx
index ccf9d476..5649750a 100644
--- a/src/components/playlist/playlist-campaign-list.jsx
+++ b/src/components/playlist/playlist-campaign-list.jsx
@@ -142,7 +142,7 @@ function PlaylistCampaignList({ location }) {
}, [playlistsGetError]);
return (
- <>
+
)}
- >
+
);
}
diff --git a/src/components/playlist/playlist-campaign-manager.jsx b/src/components/playlist/playlist-campaign-manager.jsx
index f6dfca6d..566de7de 100644
--- a/src/components/playlist/playlist-campaign-manager.jsx
+++ b/src/components/playlist/playlist-campaign-manager.jsx
@@ -56,6 +56,7 @@ function PlaylistCampaignManager({
const [loadingMessage, setLoadingMessage] = useState("");
const [highlightSharedSection, setHighlightSharedSection] = useState(false);
const [savingRelations, setSavingRelations] = useState(false);
+ const [saveWithoutClose, setSaveWithoutClose] = useState(false);
const isCampaign = location === "campaign";
const [
@@ -243,7 +244,17 @@ function PlaylistCampaignManager({
.then(() => bindSlides(playlistId, selectedSlides))
.then(() => displaySuccess(t(`${location}.success-messages.saved`)))
.finally(() => {
- navigate(`/${location}/list`);
+ if (saveWithoutClose) {
+ setSaveWithoutClose(false);
+
+ if (isSaveSuccessPost) {
+ navigate(`/${location}/edit/${idFromUrl(data["@id"])}`);
+ }
+ } else {
+ navigate(`/${location}/list`);
+ }
+
+ setSavingRelations(false);
});
}
}, [isSaveSuccessPut, isSaveSuccessPost]);
@@ -326,19 +337,23 @@ function PlaylistCampaignManager({
}
};
+ const handleSaveNoClose = () => {
+ setSaveWithoutClose(true);
+ handleSubmit();
+ };
+
return (
<>
{formStateObject && (
diff --git a/src/components/playlist/playlist-form.jsx b/src/components/playlist/playlist-form.jsx
index 2030a255..985bef8f 100644
--- a/src/components/playlist/playlist-form.jsx
+++ b/src/components/playlist/playlist-form.jsx
@@ -14,7 +14,7 @@ import TenantsDropdown from "../util/forms/multiselect-dropdown/tenants/tenants-
* @param {object} props - The props.
* @param {object} props.playlist The playlist object to modify in the form.
* @param {Function} props.handleInput Handles form input.
- * @param {boolean} props.highlightSharedSection - Hightlight section concerning
+ * @param {boolean} props.highlightSharedSection - Highlight section concerning
* shared info
* @returns {object} The playlist form.
*/
@@ -23,7 +23,7 @@ function PlaylistForm({
highlightSharedSection = false,
playlist = null,
}) {
- const { t } = useTranslation("common");
+ const { t } = useTranslation("common", { keyPrefix: "playlist-form" });
const context = useContext(UserContext);
const { data: tenants } = useGetV2TenantsQuery({
@@ -35,7 +35,7 @@ function PlaylistForm({
{playlist && tenants && (
<>
- {t("playlist-form.schedule-header")}
+ {t("schedule-header")}
@@ -47,7 +47,7 @@ function PlaylistForm({
id="shared-section"
highlightSection={highlightSharedSection}
>
- {t("playlist-form.share-playlist")}
+ {t("share-playlist")}
- {t("playlist-form.warning")}
+ {t("warning")}
>
@@ -68,6 +68,7 @@ function PlaylistForm({
PlaylistForm.propTypes = {
playlist: PropTypes.shape({
+ "@id": PropTypes.string,
schedules: PropTypes.arrayOf(
PropTypes.shape({
duration: PropTypes.number,
diff --git a/src/components/playlist/shared-playlists.jsx b/src/components/playlist/shared-playlists.jsx
index 0779ad4e..5947e46a 100644
--- a/src/components/playlist/shared-playlists.jsx
+++ b/src/components/playlist/shared-playlists.jsx
@@ -71,7 +71,7 @@ function SharedPlaylists() {
}, [playlistsGetError]);
return (
- <>
+
)}
- >
+
);
}
diff --git a/src/components/preview/preview.jsx b/src/components/preview/preview.jsx
new file mode 100644
index 00000000..10eba67c
--- /dev/null
+++ b/src/components/preview/preview.jsx
@@ -0,0 +1,88 @@
+import { React, JSX, useEffect, useState } from "react";
+import PropTypes from "prop-types";
+import { useTranslation } from "react-i18next";
+import LocalStorageKeys from "../util/local-storage-keys";
+import ConfigLoader from "../../config-loader";
+
+/**
+ * The preview component.
+ *
+ * @param {object} props The props.
+ * @param {string} props.id The id to preview.
+ * @param {string} props.mode The mode: screen, playlist, slide
+ * @param {number} props.width The width of the container.
+ * @param {number} props.height The height of the container.
+ * @param {number} props.simulatedWidth The width of the screen to simulate.
+ * @param {number} props.simulatedHeight The height of the screen to simulate.
+ * @returns {JSX.Element} The preview component
+ */
+function Preview({
+ id,
+ mode,
+ width = 960,
+ height = 540,
+ simulatedWidth = 1920,
+ simulatedHeight = 1080,
+}) {
+ const { t } = useTranslation("common", { keyPrefix: "preview" });
+ const [previewClientUrl, setPreviewClientUrl] = useState(null);
+
+ useEffect(() => {
+ ConfigLoader.loadConfig().then((config) => {
+ const base = config.previewClient ?? "";
+
+ const urlSearchParams = new URLSearchParams();
+ urlSearchParams.set("preview", mode);
+ urlSearchParams.set("preview-id", id);
+ urlSearchParams.set(
+ "preview-token",
+ localStorage.getItem(LocalStorageKeys.API_TOKEN)
+ );
+ const tenantEntry = localStorage.getItem(
+ LocalStorageKeys.SELECTED_TENANT
+ );
+ urlSearchParams.set("preview-tenant", JSON.parse(tenantEntry).tenantKey);
+
+ setPreviewClientUrl(`${base}?${urlSearchParams}`);
+ });
+ }, []);
+
+ const scale = width / simulatedWidth;
+
+ return (
+ <>
+ {typeof previewClientUrl === "string" && (
+
+
+
+ )}
+ >
+ );
+}
+
+Preview.propTypes = {
+ id: PropTypes.string.isRequired,
+ mode: PropTypes.string.isRequired,
+ width: PropTypes.number,
+ height: PropTypes.number,
+ simulatedWidth: PropTypes.number,
+ simulatedHeight: PropTypes.number,
+};
+
+export default Preview;
diff --git a/src/components/screen/screen-form.jsx b/src/components/screen/screen-form.jsx
index 0c702c3b..87d36a95 100644
--- a/src/components/screen/screen-form.jsx
+++ b/src/components/screen/screen-form.jsx
@@ -1,11 +1,12 @@
-import { React, useEffect, useState } from "react";
-import { Button, Form, Spinner, Alert } from "react-bootstrap";
+import { React, useContext, useEffect, useState } from "react";
+import { Button, Form, Spinner, Alert, Col, Row } from "react-bootstrap";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import PropTypes from "prop-types";
import { useDispatch } from "react-redux";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faExpand } from "@fortawesome/free-solid-svg-icons";
import ContentBody from "../util/content-body/content-body";
-import ContentFooter from "../util/content-footer/content-footer";
import FormInput from "../util/forms/form-input";
import FormInputArea from "../util/forms/form-input-area";
import SelectGroupsTable from "../util/multi-and-table/select-groups-table";
@@ -20,6 +21,10 @@ import {
import { displayError } from "../util/list/toast-component/display-toast";
import FormCheckbox from "../util/forms/form-checkbox";
import "./screen-form.scss";
+import Preview from "../preview/preview";
+import StickyFooter from "../util/sticky-footer";
+import Select from "../util/forms/select";
+import userContext from "../../context/user-context";
/**
* The screen form component.
@@ -48,6 +53,7 @@ function ScreenForm({
screen = null,
}) {
const { t } = useTranslation("common", { keyPrefix: "screen-form" });
+ const { config } = useContext(userContext);
const navigate = useNavigate();
const dispatch = useDispatch();
const [layoutError, setLayoutError] = useState(false);
@@ -59,11 +65,29 @@ function ScreenForm({
itemsPerPage: 20,
order: { createdAt: "desc" },
});
+ const [displayPreview, setDisplayPreview] = useState(null);
+ const [previewOverlayVisible, setPreviewOverlayVisible] = useState(false);
+ const previewOrientationOptions = [
+ {
+ value: "horizontal",
+ title: t("preview-orientation-landscape"),
+ key: "horizontal",
+ },
+ {
+ value: "vertical",
+ title: t("preview-orientation-portrait"),
+ key: "vertical",
+ },
+ ];
+ const [previewOrientation, setPreviewOrientation] = useState(
+ previewOrientationOptions[0].value
+ );
/** Check if published is set */
const checkInputsHandleSubmit = () => {
setLayoutError(false);
let submit = true;
+
if (!selectedLayout) {
displayError(t("remember-layout-error"));
setLayoutError(true);
@@ -171,7 +195,7 @@ function ScreenForm({
};
return (
- <>
+
{isLoading && (
@@ -179,178 +203,277 @@ function ScreenForm({
)}