diff --git a/.docker/vhost.conf b/.docker/vhost.conf
index 49dbc642..e48754e5 100644
--- a/.docker/vhost.conf
+++ b/.docker/vhost.conf
@@ -7,6 +7,10 @@ server {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://node:3000;
+
+ add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
+ add_header Pragma "no-cache";
+ add_header Expires "0";
}
location /admin/ws {
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd16e8bf..4ee588cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+## [2.1.1] - 2024-10-23
+
+- [#266](https://github.com/os2display/display-admin-client/pull/266)
+ - Fixed search from local storage.
+- [#265](https://github.com/os2display/display-admin-client/pull/265)
+ - Add no-cache directive
+- [#263](https://github.com/os2display/display-admin-client/pull/263)
+ - Added prefix to local storage keys.
+- [#262](https://github.com/os2display/display-admin-client/pull/262)
+ - Add multi select styling for `invalid` state
+ - Add possibility of sending error via props to multiselect component
+ - Add validation checking if layout is selected on screen before save
+ - Add validation checking if template is selected on slide before save
+- [#260](https://github.com/os2display/display-admin-client/pull/260)
+ - Bug in multiselect, fixed by removing duplicates by key both `@id`and `id`
+- [#265](https://github.com/os2display/display-admin-client/pull/265)
+ - Bug in multiselect, fixed by removing duplicates by key both `@id`and `id`
+- [#259](https://github.com/os2display/display-admin-client/pull/259)
+ - Add saving of playlists/groups with screen (as opposed to _after_)
+ - Clean up `screen-manager.jsx`
+ - Change bootstrap column class from `col-md-8` -> `col-md-12`
+ - update api.generated.ts to match [related pr](https://github.com/os2display/display-api-service/pull/213)
+ - Add @rtk-incubator/rtk-query-codegen-openapi to package.json in `src/redux/api`
+ - Sort playlists based on weight in drag/drop component
+
## [2.1.0] - 2024-10-23
- [#258](https://github.com/os2display/display-admin-client/pull/258)
diff --git a/e2e/slides.spec.js b/e2e/slides.spec.js
index 7895caeb..5507c01c 100644
--- a/e2e/slides.spec.js
+++ b/e2e/slides.spec.js
@@ -145,11 +145,7 @@ test.describe("Create slide page works", () => {
page.locator(".Toastify").locator(".Toastify__toast--error")
).toBeVisible();
await expect(
- page
- .locator(".Toastify")
- .locator(".Toastify__toast--error")
- .getByText(/An error occurred/)
- .first()
+ page.locator(".Toastify").locator(".Toastify__toast--error").first()
).toBeVisible();
await expect(page).toHaveURL(/slide\/create/);
});
diff --git a/infrastructure/itkdev/etc/confd/templates/default.conf.tmpl b/infrastructure/itkdev/etc/confd/templates/default.conf.tmpl
index 658ee1ed..27f75763 100644
--- a/infrastructure/itkdev/etc/confd/templates/default.conf.tmpl
+++ b/infrastructure/itkdev/etc/confd/templates/default.conf.tmpl
@@ -4,15 +4,21 @@ server {
root /var/www/html;
index index.html index.htm;
+ add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
+ add_header Pragma "no-cache";
+ add_header Expires "0";
+
# Any route containing a file extension (e.g. /devicesfile.js)
location ~* ^{{ getenv "APP_ADMIN_CLIENT_PATH" "" }}/(.+\..+)$ {
rewrite ^{{ getenv "APP_ADMIN_CLIENT_PATH" "/" }}(.*) /$1 break;
+
try_files $uri =404;
}
location ~* ^{{ getenv "APP_ADMIN_CLIENT_PATH" "/" }} {
rewrite ^{{ getenv "APP_ADMIN_CLIENT_PATH" "/" }}(.*) /$1 break;
autoindex off;
+
try_files $uri /index.html;
}
diff --git a/infrastructure/os2display/etc/confd/templates/default.conf.tmpl b/infrastructure/os2display/etc/confd/templates/default.conf.tmpl
index 658ee1ed..27f75763 100644
--- a/infrastructure/os2display/etc/confd/templates/default.conf.tmpl
+++ b/infrastructure/os2display/etc/confd/templates/default.conf.tmpl
@@ -4,15 +4,21 @@ server {
root /var/www/html;
index index.html index.htm;
+ add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
+ add_header Pragma "no-cache";
+ add_header Expires "0";
+
# Any route containing a file extension (e.g. /devicesfile.js)
location ~* ^{{ getenv "APP_ADMIN_CLIENT_PATH" "" }}/(.+\..+)$ {
rewrite ^{{ getenv "APP_ADMIN_CLIENT_PATH" "/" }}(.*) /$1 break;
+
try_files $uri =404;
}
location ~* ^{{ getenv "APP_ADMIN_CLIENT_PATH" "/" }} {
rewrite ^{{ getenv "APP_ADMIN_CLIENT_PATH" "/" }}(.*) /$1 break;
autoindex off;
+
try_files $uri /index.html;
}
diff --git a/src/app.scss b/src/app.scss
index 7ba15e71..4114c6df 100644
--- a/src/app.scss
+++ b/src/app.scss
@@ -57,13 +57,8 @@ body,
padding: 5em;
z-index: 1021;
- .spinner-container {
- display: flex;
- position: fixed;
-
- .loading-spinner {
- margin-right: 1em;
- }
+ .loading-spinner {
+ margin-right: 0.6em;
}
}
diff --git a/src/components/playlist-drag-and-drop/playlist-drag-and-drop.jsx b/src/components/playlist-drag-and-drop/playlist-drag-and-drop.jsx
index e0cc038a..755a50c6 100644
--- a/src/components/playlist-drag-and-drop/playlist-drag-and-drop.jsx
+++ b/src/components/playlist-drag-and-drop/playlist-drag-and-drop.jsx
@@ -20,9 +20,17 @@ import ScreenGanttChart from "../screen/util/screen-gantt-chart";
* @param {string} props.name - The id of the form element
* @param {string} props.screenId - The screen id for get request
* @param {string} props.regionId - The region id for get request
+ * @param {string} props.regionIdForInitializeCallback - The region id to add
+ * regions to formstateobject.
* @returns {object} A drag and drop component
*/
-function PlaylistDragAndDrop({ handleChange, name, screenId, regionId }) {
+function PlaylistDragAndDrop({
+ handleChange,
+ name,
+ screenId,
+ regionId,
+ regionIdForInitializeCallback,
+}) {
const { t } = useTranslation("common", {
keyPrefix: "playlist-drag-and-drop",
});
@@ -49,16 +57,35 @@ function PlaylistDragAndDrop({ handleChange, name, screenId, regionId }) {
sharedWithMe: onlySharedPlaylists,
});
+ /**
+ * @param regionsAndPlaylists This method initializes playlists, so the
+ * initial formstate object in screen manager is not empty
+ */
+ function callbackToinitializePlaylists(regionsAndPlaylists) {
+ handleChange({
+ target: {
+ id: regionIdForInitializeCallback,
+ value: regionsAndPlaylists["hydra:member"].map(
+ ({ playlist }) => playlist
+ ),
+ },
+ });
+ }
+
/** Set loaded data into form state. */
useEffect(() => {
if (selectedPlaylistsByRegion) {
setTotalItems(selectedPlaylistsByRegion["hydra:totalItems"]);
const newPlaylists = selectedPlaylistsByRegion["hydra:member"].map(
- ({ playlist }) => {
- return playlist;
- }
+ ({ playlist, weight }) => ({ ...playlist, weight })
+ );
+
+ const selected = [...selectedData, ...newPlaylists].sort(
+ (a, b) => a.weight - b.weight
);
- setSelectedData([...selectedData, ...newPlaylists]);
+
+ setSelectedData(selected);
+ callbackToinitializePlaylists(selectedPlaylistsByRegion);
}
}, [selectedPlaylistsByRegion]);
@@ -157,6 +184,7 @@ function PlaylistDragAndDrop({ handleChange, name, screenId, regionId }) {
PlaylistDragAndDrop.propTypes = {
name: PropTypes.string.isRequired,
screenId: PropTypes.string.isRequired,
+ regionIdForInitializeCallback: PropTypes.string.isRequired,
regionId: PropTypes.string.isRequired,
handleChange: PropTypes.func.isRequired,
};
diff --git a/src/components/screen/screen-form.jsx b/src/components/screen/screen-form.jsx
index a6e68491..0c702c3b 100644
--- a/src/components/screen/screen-form.jsx
+++ b/src/components/screen/screen-form.jsx
@@ -50,6 +50,7 @@ function ScreenForm({
const { t } = useTranslation("common", { keyPrefix: "screen-form" });
const navigate = useNavigate();
const dispatch = useDispatch();
+ const [layoutError, setLayoutError] = useState(false);
const [selectedLayout, setSelectedLayout] = useState();
const [layoutOptions, setLayoutOptions] = useState();
const [bindKey, setBindKey] = useState("");
@@ -59,6 +60,21 @@ function ScreenForm({
order: { createdAt: "desc" },
});
+ /** Check if published is set */
+ const checkInputsHandleSubmit = () => {
+ setLayoutError(false);
+ let submit = true;
+ if (!selectedLayout) {
+ displayError(t("remember-layout-error"));
+ setLayoutError(true);
+ submit = false;
+ }
+
+ if (submit) {
+ handleSubmit();
+ }
+ };
+
useEffect(() => {
if (layouts) {
setLayoutOptions(layouts["hydra:member"]);
@@ -72,6 +88,11 @@ function ScreenForm({
);
if (localSelectedLayout) {
setSelectedLayout(localSelectedLayout);
+ // Initialize regions in the formstate object of screenmanager. used to save "empty" playlists, in the situation
+ // we are deleting all playlists from a screen region
+ handleInput({
+ target: { id: "regions", value: localSelectedLayout.regions },
+ });
}
}
}, [screen.layout, layoutOptions]);
@@ -84,6 +105,7 @@ function ScreenForm({
*/
const handleAdd = ({ target }) => {
const { value, id } = target;
+
setSelectedLayout(value);
handleInput({
target: { id, value: value.map((item) => item["@id"]).shift() },
@@ -250,7 +272,7 @@ function ScreenForm({
noSelectedString={t("nothing-selected-resolution")}
handleSelection={handleInput}
options={resolutionOptions}
- selected={screen.resolution || ""}
+ selected={screen.resolution || []}
name="resolution"
singleSelect
/>
@@ -259,7 +281,7 @@ function ScreenForm({
noSelectedString={t("nothing-selected-orientation")}
handleSelection={handleInput}
options={orientationOptions}
- selected={screen.orientation || ""}
+ selected={screen.orientation || []}
name="orientation"
singleSelect
/>
@@ -277,6 +299,7 @@ function ScreenForm({
helpText={t("search-to-se-possible-selections")}
selected={selectedLayout ? [selectedLayout] : []}
name="layout"
+ error={layoutError}
singleSelect
/>
@@ -321,7 +344,7 @@ function ScreenForm({
type="button"
id="save_screen"
size="lg"
- onClick={handleSubmit}
+ onClick={checkInputsHandleSubmit}
>
{t("save-button")}
@@ -340,7 +363,11 @@ ScreenForm.propTypes = {
enableColorSchemeChange: PropTypes.bool,
layout: PropTypes.string,
location: PropTypes.string,
- regions: PropTypes.arrayOf(PropTypes.string),
+ regions: PropTypes.arrayOf(
+ PropTypes.shape({
+ "@id": PropTypes.string,
+ })
+ ),
screenUser: PropTypes.string,
size: PropTypes.string,
title: PropTypes.string,
diff --git a/src/components/screen/screen-manager.jsx b/src/components/screen/screen-manager.jsx
index ba552bb8..6983fb01 100644
--- a/src/components/screen/screen-manager.jsx
+++ b/src/components/screen/screen-manager.jsx
@@ -6,8 +6,6 @@ import { useNavigate } from "react-router-dom";
import {
usePostV2ScreensMutation,
usePutV2ScreensByIdMutation,
- usePutV2ScreensByIdScreenGroupsMutation,
- usePutPlaylistScreenRegionItemMutation,
} from "../../redux/api/api.generated.ts";
import ScreenForm from "./screen-form";
import {
@@ -38,22 +36,18 @@ function ScreenManager({
}) {
const { t } = useTranslation("common", { keyPrefix: "screen-manager" });
const navigate = useNavigate();
- const [orientationOptions] = useState([
+ const orientationOptions = [
{ title: "Vertikal", "@id": "vertical" },
{ title: "Horisontal", "@id": "horizontal" },
- ]);
- const [resolutionOptions] = useState([
+ ];
+ const resolutionOptions = [
{ title: "4K", "@id": "4K" },
{ title: "HD", "@id": "HD" },
- ]);
+ ];
const headerText =
saveMethod === "PUT" ? t("edit-screen-header") : t("create-screen-header");
const [loadingMessage, setLoadingMessage] = useState("");
const [savingScreen, setSavingScreen] = useState(false);
- const [savingGroups, setSavingGroups] = useState(false);
- const [savingPlaylists, setSavingPlaylists] = useState(false);
- const [groupsToAdd, setGroupsToAdd] = useState();
- const [playlistsToAdd, setPlaylistsToAdd] = useState([]);
// Initialize to empty screen object.
const [formStateObject, setFormStateObject] = useState(null);
@@ -64,63 +58,9 @@ function ScreenManager({
// Handler for creating screen.
const [
PostV2Screens,
- { data: postData, error: saveErrorPost, isSuccess: isSaveSuccessPost },
+ { error: saveErrorPost, isSuccess: isSaveSuccessPost },
] = usePostV2ScreensMutation();
- // @TODO: Handle errors.
- const [
- putPlaylistScreenRegionItem,
- { error: savePlaylistError, isSuccess: isSavePlaylistSuccess },
- ] = usePutPlaylistScreenRegionItemMutation();
-
- const [
- PutV2ScreensByIdScreenGroups,
- { error: saveErrorGroups, isSuccess: isSaveSuccessGroups },
- ] = usePutV2ScreensByIdScreenGroupsMutation();
-
- /** When the screen is saved, the groups will be saved. */
- useEffect(() => {
- if ((isSaveSuccessPut || isSaveSuccessPost) && groupsToAdd) {
- setLoadingMessage(t("loading-messages.saving-groups"));
- PutV2ScreensByIdScreenGroups({
- id: id || idFromUrl(postData["@id"]),
- body: JSON.stringify(groupsToAdd),
- });
- }
- }, [isSaveSuccessPost, isSaveSuccessPut]);
-
- // Playlists are saved successfully, display a message
- useEffect(() => {
- if (isSavePlaylistSuccess && playlistsToAdd.length === 0) {
- setSavingPlaylists(false);
- displaySuccess(t("success-messages.saved-playlists"));
- }
- }, [isSavePlaylistSuccess]);
-
- // Groups are saved successfully, display a message
- useEffect(() => {
- if (isSaveSuccessGroups) {
- setSavingGroups(false);
- displaySuccess(t("success-messages.saved-groups"));
- }
- }, [isSaveSuccessGroups]);
-
- // Playlists are not saved successfully, display an error message
- useEffect(() => {
- if (savePlaylistError) {
- setSavingPlaylists(false);
- displayError(t("error-messages.save-playlists-error"), savePlaylistError);
- }
- }, [savePlaylistError]);
-
- // Groups are not saved successfully, display an error message
- useEffect(() => {
- if (saveErrorGroups) {
- setSavingGroups(false);
- displayError(t("error-messages.save-groups-error"), saveErrorGroups);
- }
- }, [saveErrorGroups]);
-
/** If the screen is saved, display the success message */
useEffect(() => {
if (isSaveSuccessPost || isSaveSuccessPut) {
@@ -166,102 +106,123 @@ function ScreenManager({
const localFormStateObject = JSON.parse(JSON.stringify(initialState));
if (localFormStateObject.orientation) {
localFormStateObject.orientation = orientationOptions.filter(
- ({ id: localOrientationId }) =>
- localOrientationId === localFormStateObject.orientation
+ (orientation) =>
+ orientation["@id"] === localFormStateObject.orientation
);
}
if (localFormStateObject.resolution) {
localFormStateObject.resolution = resolutionOptions.filter(
- ({ id: localResolutioId }) =>
- localResolutioId === localFormStateObject.resolution
+ (resolution) => resolution["@id"] === localFormStateObject.resolution
);
}
+
setFormStateObject(localFormStateObject);
}
}, [initialState]);
- /** Adds playlists to regions. */
- useEffect(() => {
- if (
- (isSaveSuccessPost || isSaveSuccessPut) &&
- playlistsToAdd &&
- playlistsToAdd.length > 0
- ) {
- setLoadingMessage(t("loading-messages.saving-playlists"));
- const playlistToAdd = playlistsToAdd.splice(0, 1).shift();
- putPlaylistScreenRegionItem({
- body: JSON.stringify(playlistToAdd?.list),
- id: playlistToAdd.screenId || idFromUrl(postData["@id"]),
- regionId: playlistToAdd.regionId,
+ /**
+ * Map group ids for submitting.
+ *
+ * @returns {Array | null} A mapped array with group ids or null
+ */
+ function mapGroups() {
+ if (formStateObject.inScreenGroups) {
+ return formStateObject.inScreenGroups.map((group) => {
+ return idFromUrl(group);
});
}
- }, [isSavePlaylistSuccess, isSaveSuccessPut, isSaveSuccessPost]);
-
- /** Set playlists to save, if any */
- function savePlaylists() {
- const toSave = [];
- const formStateObjectPlaylists = formStateObject.playlists?.map(
- (playlist) => {
- return {
- id: idFromUrl(playlist["@id"]),
- regionId: idFromUrl(playlist.region),
- };
- }
- );
- if (formStateObjectPlaylists) {
- // Unique regions that will have a playlist connected.
- const regions = [
- ...new Set(
- formStateObjectPlaylists.map((playlists) => playlists.regionId)
- ),
- ];
+ return [];
+ }
- // Filter playlists by region
- regions.forEach((element) => {
- const filteredPlaylists = formStateObjectPlaylists
- .map((localPlaylists, index) => {
- if (element === localPlaylists.regionId) {
- return { playlist: localPlaylists.id, weight: index };
- }
- return undefined;
- })
- .filter((anyValue) => typeof anyValue !== "undefined");
+ /**
+ * Creates an array of playlist ids and weight filtered by region id or null
+ *
+ * @param regionId RegionId for filtering
+ * @returns {Array | null} A mapped array with playlist ids and weight
+ * filtered by region id or null
+ */
+ function getPlaylistsByRegionId(regionId) {
+ const { playlists } = formStateObject;
- // Collect playlists with according ids for saving
- toSave.push({
- list: filteredPlaylists,
- regionId: element,
- screenId: id,
- });
+ return playlists
+ .filter(({ region }) => idFromUrl(region) === idFromUrl(regionId))
+ .map((playlist, index) => {
+ return { id: idFromUrl(playlist["@id"]), weight: index };
});
+ }
- if (formStateObject.playlists?.length === 0) {
- formStateObject.regions.forEach((element) => {
- toSave.push({
- list: [],
- regionId: idFromUrl(element, 1),
- screenId: id,
- });
- });
- }
-
- // Set playlists to save
- setPlaylistsToAdd(toSave);
- setSavingPlaylists(true);
+ /**
+ * @param {string} id The item to remove.
+ * @param {Array} array The array to remove from.
+ */
+ function removeFromArray(id, array) {
+ if (array.indexOf(id) >= 0) {
+ array.splice(array.indexOf(id), 1);
}
}
- /** Set groups to save, if any */
- function saveGroups() {
- if (Array.isArray(formStateObject.inScreenGroups)) {
- setSavingGroups(true);
- setGroupsToAdd(
- formStateObject.inScreenGroups.map((group) => {
- return idFromUrl(group);
+ /**
+ * Map playlists with regions and weight for submitting.
+ *
+ * @returns {Array | null} A mapped array with playlist, regions and weight or null
+ */
+ function mapPlaylistsWithRegion() {
+ const returnArray = [];
+ const { playlists, regions } = formStateObject;
+ const regionIds = regions.map((r) => r["@id"]);
+
+ // The playlists all have a regionId, the following creates a unique list of relevant regions If there are not
+ // playlists, then an empty playlist is to be saved per region
+ let playlistRegions = [];
+ if (playlists?.length > 0) {
+ playlistRegions = [...new Set(playlists.map(({ region }) => region))];
+ }
+
+ // Then the playlists are mapped by region Looping through the regions that have a playlist connected...
+ playlistRegions.forEach((regionId) => {
+ // remove region id from list of regionids to finally end up with an array of region ids with empty playlist
+ // arrays connected
+ removeFromArray(regionId, regionIds);
+
+ // Add regionsId and connected playlists to the returnarray
+ returnArray.push({
+ playlists: getPlaylistsByRegionId(regionId),
+ regionId: idFromUrl(regionId),
+ });
+ });
+
+ // The remaining regions are added with empty playlist arrays.
+ if (regionIds.length > 0) {
+ regionIds.forEach((regionId) =>
+ returnArray.push({
+ playlists: [],
+ regionId: idFromUrl(regionId),
})
);
}
+
+ return returnArray;
+ }
+
+ /**
+ * Gets orientation for submitting
+ *
+ * @returns {string} Orientation or empty string
+ */
+ function getOrientation() {
+ const { orientation } = formStateObject;
+ return orientation ? orientation[0]["@id"] : "";
+ }
+
+ /**
+ * Gets resolution for submitting
+ *
+ * @returns {string} Resolution or empty string
+ */
+ function getResolution() {
+ const { resolution } = formStateObject;
+ return resolution && resolution.length > 0 ? resolution[0]["@id"] : "";
}
/** Handles submit. */
@@ -269,62 +230,50 @@ function ScreenManager({
setSavingScreen(true);
setLoadingMessage(t("loading-messages.saving-screen"));
const localFormStateObject = JSON.parse(JSON.stringify(formStateObject));
- const resolution =
- localFormStateObject.resolution &&
- localFormStateObject.resolution.length > 0
- ? localFormStateObject.resolution[0].id
- : "";
+ const {
+ title,
+ description,
+ size,
+ modifiedBy,
+ createdBy,
+ layout,
+ location,
+ enableColorSchemeChange,
+ } = localFormStateObject;
+
const saveData = {
screenScreenInput: JSON.stringify({
- title: localFormStateObject.title,
- description: localFormStateObject.description,
- size: localFormStateObject.size,
- modifiedBy: localFormStateObject.modifiedBy,
- createdBy: localFormStateObject.createdBy,
- layout: localFormStateObject.layout,
- location: localFormStateObject.location,
- resolution,
- orientation: localFormStateObject.orientation
- ? localFormStateObject.orientation[0].id
- : "",
- enableColorSchemeChange: localFormStateObject.enableColorSchemeChange,
+ title,
+ description,
+ size,
+ modifiedBy,
+ createdBy,
+ layout,
+ location,
+ enableColorSchemeChange,
+ resolution: getResolution(),
+ groups: mapGroups(),
+ orientation: getOrientation(),
+ regions: mapPlaylistsWithRegion(),
}),
};
+ setLoadingMessage(t("loading-messages.saving-screen"));
+
if (saveMethod === "POST") {
- setLoadingMessage(t("loading-messages.saving-screen"));
PostV2Screens(saveData);
} else if (saveMethod === "PUT") {
- setLoadingMessage(t("loading-messages.saving-screen"));
- const putData = { ...saveData, id };
-
- PutV2Screens(putData);
- } else {
- throw new Error("Unsupported save method");
+ PutV2Screens({ ...saveData, id });
}
-
- saveGroups();
- savePlaylists();
};
/** Handle submitting is done. */
useEffect(() => {
- if (
- (isSaveSuccessPut || isSaveSuccessPost) &&
- !savingPlaylists &&
- !savingGroups
- ) {
+ if (isSaveSuccessPut || isSaveSuccessPost) {
setSavingScreen(false);
navigate("/screen/list");
}
- }, [
- isSaveSuccessPut,
- isSaveSuccessPost,
- isSavePlaylistSuccess,
- isSaveSuccessGroups,
- savingGroups,
- savingPlaylists,
- ]);
+ }, [isSaveSuccessPut, isSaveSuccessPost]);
return (
<>
@@ -336,9 +285,7 @@ function ScreenManager({
headerText={headerText}
handleInput={handleInput}
handleSubmit={handleSubmit}
- isLoading={
- savingScreen || savingPlaylists || savingGroups || isLoading
- }
+ isLoading={savingScreen || isLoading}
loadingMessage={loadingMessage}
groupId={groupId}
/>
diff --git a/src/components/screen/util/grid-generation-and-select.jsx b/src/components/screen/util/grid-generation-and-select.jsx
index 0e212261..3af57aaf 100644
--- a/src/components/screen/util/grid-generation-and-select.jsx
+++ b/src/components/screen/util/grid-generation-and-select.jsx
@@ -113,7 +113,7 @@ function GridGenerationAndSelect({
-
+
{regions.length > 0 && (
<>
@@ -136,6 +136,7 @@ function GridGenerationAndSelect({
id="playlist_drag_and_drop"
handleChange={handleChange}
name={data["@id"]}
+ regionIdForInitializeCallback={data["@id"]}
screenId={screenId}
regionId={idFromUrl(data["@id"])}
/>
diff --git a/src/components/slide/slide-form.jsx b/src/components/slide/slide-form.jsx
index 2ae24f73..1755b490 100644
--- a/src/components/slide/slide-form.jsx
+++ b/src/components/slide/slide-form.jsx
@@ -72,6 +72,7 @@ function SlideForm({
const [searchTextTheme, setSearchTextTheme] = useState("");
const [selectedTemplates, setSelectedTemplates] = useState([]);
const [themesOptions, setThemesOptions] = useState();
+ const [templateError, setTemplateError] = useState(false);
// Load templates.
const { data: templates, isLoading: loadingTemplates } =
@@ -87,6 +88,21 @@ function SlideForm({
order: { createdAt: "desc" },
});
+ /** Check if published is set */
+ const checkInputsHandleSubmit = () => {
+ setTemplateError(false);
+ let submit = true;
+ if (!selectedTemplate) {
+ setTemplateError(true);
+ submit = false;
+ displayError(t("slide-form.remember-template-error"));
+ }
+
+ if (submit) {
+ handleSubmit();
+ }
+ };
+
/**
* For closing overlay on escape key.
*
@@ -227,6 +243,7 @@ function SlideForm({
handleSelection={selectTemplate}
options={templateOptions}
selected={selectedTemplates}
+ error={templateError}
name="templateInfo"
filterCallback={onFilterTemplate}
singleSelect
@@ -484,7 +501,7 @@ function SlideForm({