Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 23 additions & 22 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,32 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

* Gathered all repositories in one Symfony application.
* Changed to vite 7 and rolldown.
* Added ADRs 008 and 009.
* Cleaned up Github Actions workflows.
* Updated PHP dependencies.
* Added Playwright github action.
* Changed how templates are imported.
* Removed propTypes.
* Upgraded redux-toolkit and how api slices are generated.
* Fixed redux-toolkit cache handling.
* Added Taskfile
* Added update command.
* Added (Client) online-check to public.
* Updated developer documentation.
* Removed admin/access-config.json fetch
* Aligned with v. 2.5.2.
* Removed themes.
* Added command to migrate config.json files.
- Gathered all repositories in one Symfony application.
- Changed to vite 7 and rolldown.
- Added ADRs 008 and 009.
- Cleaned up Github Actions workflows.
- Updated PHP dependencies.
- Added Playwright github action.
- Changed how templates are imported.
- Removed propTypes.
- Upgraded redux-toolkit and how api slices are generated.
- Fixed redux-toolkit cache handling.
- Added Taskfile
- Added update command.
- Added (Client) online-check to public.
- Updated developer documentation.
- Removed admin/access-config.json fetch
- Aligned with v. 2.5.2.
- Removed themes.
- Added command to migrate config.json files.
- Fix data fetching bug

### NB! Prior to 3.x the project was split into separate repositories

Therefore, changelogs were maintained for each repo. The v2 changelogs have been moved to the `docs/v2-changelogs/`
folder.

* API: [docs/v2-changelogs/api.md](docs/v2-changelogs/api.md)
* Admin: [docs/v2-changelogs/admin.md](docs/v2-changelogs/admin.md)
* Template: [docs/v2-changelogs/template.md](docs/v2-changelogs/template.md)
* Client: [docs/v2-changelogs/client.md](docs/v2-changelogs/client.md)
- API: [docs/v2-changelogs/api.md](docs/v2-changelogs/api.md)
- Admin: [docs/v2-changelogs/admin.md](docs/v2-changelogs/admin.md)
- Template: [docs/v2-changelogs/template.md](docs/v2-changelogs/template.md)
- Client: [docs/v2-changelogs/client.md](docs/v2-changelogs/client.md)
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,16 @@ function PlaylistDragAndDrop({
keyPrefix: "playlist-drag-and-drop",
});
const [searchText, setSearchText] = useState();
const [page, setPage] = useState(1);
const [onlySharedPlaylists, setOnlySharedPlaylists] = useState(false);

const {
data: {
"hydra:member": playlists = null,
"hydra:totalItems": totalItems = 0,
} = {},
} = useGetV2PlaylistsQuery({
isCampaign: false,
title: searchText,
itemsPerPage: 30,
order: { createdAt: "desc" },
sharedWithMe: onlySharedPlaylists,
});
const { data: { "hydra:member": playlists = null } = {} } =
useGetV2PlaylistsQuery({
isCampaign: false,
title: searchText,
itemsPerPage: 30,
order: { createdAt: "desc" },
sharedWithMe: onlySharedPlaylists,
});

/**
* Fetches data for the multi component
Expand Down Expand Up @@ -94,9 +89,6 @@ function PlaylistDragAndDrop({
onDropped={handleChange}
name={name}
data={selectedPlaylists}
callback={() => setPage(page + 1)}
label={t("more-playlists")}
totalItems={totalItems}
/>
)}
{selectedPlaylists?.length > 0 && (
Expand Down
6 changes: 4 additions & 2 deletions assets/admin/components/playlist/campaign-form.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useTranslation } from "react-i18next";
import idFromUrl from "../util/helpers/id-from-url";
import { useGetV2CampaignsByIdScreenGroupsQuery } from "../../../shared/redux/enhanced-api.ts";
import { enhancedApi } from "../../../shared/redux/enhanced-api.ts";
import ContentBody from "../util/content-body/content-body";
import SelectScreensTable from "../util/multi-and-table/select-screens-table";
import SelectGroupsTable from "../util/multi-and-table/select-groups-table";
Expand Down Expand Up @@ -34,7 +34,9 @@ function CampaignForm({ campaign = null, handleInput }) {
mappingId="screenGroup"
handleChange={handleInput}
name="groups"
getSelectedMethod={useGetV2CampaignsByIdScreenGroupsQuery}
getSelectedMethod={
enhancedApi.endpoints.getV2CampaignsByIdScreenGroups.initiate
}
id={idFromUrl(campaign["@id"])}
/>
</ContentBody>
Expand Down
6 changes: 4 additions & 2 deletions assets/admin/components/screen/screen-form.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import MultiSelectComponent from "../util/forms/multiselect-dropdown/multi-dropd
import idFromUrl from "../util/helpers/id-from-url";
import {
useGetV2LayoutsQuery,
useGetV2ScreensByIdScreenGroupsQuery,
enhancedApi,
} from "../../../shared/redux/enhanced-api.ts";
import FormCheckbox from "../util/forms/form-checkbox";
import Preview from "../preview/preview";
Expand Down Expand Up @@ -196,7 +196,9 @@ function ScreenForm({
handleChange={handleInput}
name="inScreenGroups"
id={groupId}
getSelectedMethod={useGetV2ScreensByIdScreenGroupsQuery}
getSelectedMethod={
enhancedApi.endpoints.getV2ScreensByIdScreenGroups.initiate
}
/>
</ContentBody>
<ContentBody>
Expand Down
198 changes: 92 additions & 106 deletions assets/admin/components/screen/util/grid-generation-and-select.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { useState, useEffect } from "react";
import { Tabs, Tab, Alert } from "react-bootstrap";
import Grid from "./grid";
import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux";
import idFromUrl from "../../util/helpers/id-from-url";
import PlaylistDragAndDrop from "../../playlist-drag-and-drop/playlist-drag-and-drop";
import { enhancedApi } from "../../../../shared/redux/enhanced-api.ts";
import useFetchDataHook from "../../util/fetch-data-hook.js";
import mapToIds from "../../util/helpers/map-to-ids.js";
import "./grid.scss";

/**
Expand All @@ -27,11 +28,18 @@ function GridGenerationAndSelect({
regions = [],
}) {
const { t } = useTranslation("common");
const dispatch = useDispatch();
const [selectedRegion, setSelectedRegion] = useState(
regions.length > 0 ? regions[0]["@id"] : "",
);
const [selectedPlaylists, setSelectedPlaylists] = useState([]);
const { data: playlistsAndRegions } = useFetchDataHook(
enhancedApi.endpoints.getV2ScreensByIdRegionsAndRegionIdPlaylists.initiate,
mapToIds(regions), // returns and array with ids to fetch for all ids
{
id: screenId, // screen id is the id
},
"regionId", // The key for the list of ids
);

/**
* @param {object} props The props
Expand All @@ -40,93 +48,72 @@ function GridGenerationAndSelect({
* @returns {Array} Mapped data
*/
function mapData({ value: inputPlaylists, id }) {
// Map to add region id to incoming data.
const localTarget = inputPlaylists.map((playlist) => {
return {
region: idFromUrl(id),
...playlist,
};
});
// A copy, to be able to remove items.
let selectedPlaylistsCopy = [...selectedPlaylists];

// The following is used to determine if something has been removed from a list.
const regionPlaylists = selectedPlaylists
.filter(({ region }) => region === id)
.map(({ region }) => region);

const selectedWithoutRegion = [];

// Checks if an element has been removed from the list
if (inputPlaylists.length < regionPlaylists.length) {
selectedPlaylists.forEach((playlist) => {
if (!regionPlaylists.includes(playlist.region)) {
selectedWithoutRegion.push(playlist);
}
});
// If a playlist is removed from a list, all the playlists in that region will be removed.
selectedPlaylistsCopy = selectedWithoutRegion;
// Region id form id url
const region = idFromUrl(id);

// Add the region id to each inputted playlist
const playlistsWithRegion = inputPlaylists.map((playlist) => ({
region,
...playlist,
}));

// Get the playlists that belong the same region from the selected playlists
const existingRegionPlaylists = selectedPlaylists.filter(
(playlist) => playlist.region === region,
);

// Check if any playlists from the existing region playlists are missing from
// The inputted playlists if so, they are removed from the list
const removedPlaylists = existingRegionPlaylists.some(
({ "@id": existingId }) =>
!inputPlaylists.find(
({ "@id": incomingId }) => incomingId === existingId,
),
);

// Start with the existing selected playlists
let updatedRegionPlaylists = [...selectedPlaylists];

// If any playlists were removed, filter out all playlists for this region
if (removedPlaylists) {
updatedRegionPlaylists = selectedPlaylists.filter(
(playlist) => playlist.region !== region,
);
}

// Removes duplicates.
const localSelectedPlaylists = [
...localTarget,
...selectedPlaylistsCopy,
// Merge the updated region playlists with the input playlists,
// and remove any duplicate region and id combinations
const mappedData = [
...playlistsWithRegion,
...updatedRegionPlaylists,
].filter(
(playlist, index, self) =>
index ===
self.findIndex(
(secondPlaylist) =>
secondPlaylist["@id"] === playlist["@id"] &&
secondPlaylist.region === playlist.region,
({ region, "@id": playlistId }) =>
playlistId === playlist["@id"] && region === playlist.region,
),
);

return localSelectedPlaylists;
return mappedData;
}

// On received data, map to fit the components
// We need region id to figure out which dropdown they should be placed in
// and weight (order) for sorting.
useEffect(() => {
if (regions.length > 0) {
const promises = [];
regions.forEach(({ "@id": id }) => {
promises.push(
dispatch(
enhancedApi.endpoints.getV2ScreensByIdRegionsAndRegionIdPlaylists.initiate(
{
id: screenId,
regionId: idFromUrl(id),
page: 1,
itemsPerPage: 50,
},
),
),
);
});

Promise.allSettled(promises).then((results) => {
let playlists = [];
results.forEach(
({
value: {
originalArgs: { regionId },
data: { "hydra:member": promisedPlaylists = null } = {},
},
}) => {
playlists = [
...playlists,
...promisedPlaylists.map(({ playlist, weight }) => ({
...playlist,
weight,
region: regionId,
})),
];
},
);
playlists = playlists.sort((a, b) => a.weight - b.weight);
setSelectedPlaylists(playlists);
});
if (playlistsAndRegions && playlistsAndRegions.length > 0) {
const playlists = playlistsAndRegions
.map(({ originalArgs: { regionId }, playlist, weight }) => ({
...playlist,
weight,
region: regionId,
}))
.sort((a, b) => a.weight - b.weight);

setSelectedPlaylists(playlists);
}
}, [regions]);
}, [playlistsAndRegions]);

useEffect(() => {
handleInput({ target: { value: selectedPlaylists, id: "playlists" } });
Expand Down Expand Up @@ -156,6 +143,7 @@ function GridGenerationAndSelect({
);
};

// If there are no regions, the components should not spend time rendering.
if (regions?.length === 0) return null;

return (
Expand All @@ -171,37 +159,35 @@ function GridGenerationAndSelect({
</div>
</div>
<div className="col-md-12">
<>
<h3 className="h5">{t("screen-form.screen-region-playlists")}</h3>
<Tabs
defaultActiveKey={regions[0]["@id"]}
id="tabs"
onSelect={setSelectedRegion}
className="mb-3"
>
{regions.map(({ title, "@id": id, type }) => (
<Tab eventKey={id} key={id} title={title}>
<PlaylistDragAndDrop
id="playlist_drag_and_drop"
handleChange={handleChange}
removeFromList={removeFromList}
name={id}
regionIdForInitializeCallback={id}
screenId={screenId}
regionId={idFromUrl(id)}
selectedPlaylists={selectedPlaylists.filter(
({ region }) => region === idFromUrl(id),
)}
/>
{type === "touch-buttons" && (
<Alert key="screen-form-touch-buttons" variant="info">
{t("screen-form.touch-region-helptext")}
</Alert>
<h3 className="h5">{t("screen-form.screen-region-playlists")}</h3>
<Tabs
defaultActiveKey={regions[0]["@id"]}
id="tabs"
onSelect={setSelectedRegion}
className="mb-3"
>
{regions.map(({ title, "@id": id, type }) => (
<Tab eventKey={id} key={id} title={title}>
<PlaylistDragAndDrop
id="playlist_drag_and_drop"
handleChange={handleChange}
removeFromList={removeFromList}
name={id}
regionIdForInitializeCallback={id}
screenId={screenId}
regionId={idFromUrl(id)}
selectedPlaylists={selectedPlaylists.filter(
({ region }) => region === idFromUrl(id),
)}
</Tab>
))}
</Tabs>
</>
/>
{type === "touch-buttons" && (
<Alert key="screen-form-touch-buttons" variant="info">
{t("screen-form.touch-region-helptext")}
</Alert>
)}
</Tab>
))}
</Tabs>
</div>
</>
);
Expand Down
Loading