diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/app/PopupComponent.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/app/PopupComponent.kt new file mode 100644 index 00000000..b1b44842 --- /dev/null +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/app/PopupComponent.kt @@ -0,0 +1,72 @@ +package hu.bme.sch.cmsch.component.app + +import hu.bme.sch.cmsch.component.ComponentBase +import hu.bme.sch.cmsch.model.RoleType +import hu.bme.sch.cmsch.service.ControlPermissions +import hu.bme.sch.cmsch.setting.BooleanSettingRef +import hu.bme.sch.cmsch.setting.ComponentSettingService +import hu.bme.sch.cmsch.setting.MinRoleSettingRef +import hu.bme.sch.cmsch.setting.NumberSettingRef +import hu.bme.sch.cmsch.setting.SettingGroup +import hu.bme.sch.cmsch.setting.SettingType +import hu.bme.sch.cmsch.setting.StringSettingRef +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean +import org.springframework.context.annotation.Conditional +import org.springframework.core.env.Environment +import org.springframework.stereotype.Service + +@Service +@ConditionalOnBean(ApplicationComponent::class) +class PopupComponent( + componentSettingService: ComponentSettingService, + env: Environment +) : ComponentBase( + componentSettingService, + "popup", + "/", + "Popup", + ControlPermissions.PERMISSION_CONTROL_APP, + listOf(), + env +) { + + final override val menuDisplayName = null + + final override var minRole by MinRoleSettingRef(MinRoleSettingRef.ALL_ROLES, minRoleToEdit = RoleType.NOBODY, + fieldName = "Jogosultságok", description = "Melyik roleokkal nyitható meg az oldal") + + /// ------------------------------------------------------------------------------------------------------------------- + + val jumpscareGroup by SettingGroup(fieldName = "Jumpscare beállítások") + + val popupEnabled by BooleanSettingRef(false, fieldName = "Felugró média engedélyezése", description = "Engedélyezi a felugró média megjelenítését az oldalon") + + val popupUrls by StringSettingRef( + fieldName = "Felugró média", type = SettingType.LONG_TEXT, + description = "A felugró médiák elérési útjai, vesszővel elválasztva" + ) + + val popupBackground by StringSettingRef( + defaultValue = "#000000", type = SettingType.COLOR, + fieldName = "Felugró média háttérszíne", + description = "A felugró média háttérszíne hexadecimális formátumban (pl. #000000)" + ) + + val popupOpacity by NumberSettingRef( + defaultValue = 80, + fieldName = "Felugró média átlátszósága", + description = "A felugró média átlátszósága 0 és 100 között (0 = teljesen átlátszó, 100 = teljesen átlátszatlan)" + ) + + val intervalLowBound by NumberSettingRef( + defaultValue = 60, + fieldName = "Felugró média alsó intervallum", + description = "Legutóbbi megjelenésétől számítva legalább ennyi másodpercnek kell eltelnie a következő felugró média megjelenéséig", + ) + + val intervalHighBound by NumberSettingRef( + defaultValue = 60, + fieldName = "Felugró média felső intervallum", + description = "Legutóbbi megjelenésétől számítva legfeljebb ennyi másodpercnek kell eltelnie a következő felugró média megjelenéséig" + ) +} \ No newline at end of file diff --git a/backend/src/main/kotlin/hu/bme/sch/cmsch/component/app/PopupComponentController.kt b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/app/PopupComponentController.kt new file mode 100644 index 00000000..5ce75596 --- /dev/null +++ b/backend/src/main/kotlin/hu/bme/sch/cmsch/component/app/PopupComponentController.kt @@ -0,0 +1,33 @@ +package hu.bme.sch.cmsch.component.app + +import hu.bme.sch.cmsch.component.ComponentApiBase +import hu.bme.sch.cmsch.service.AdminMenuService +import hu.bme.sch.cmsch.service.AuditLogService +import hu.bme.sch.cmsch.service.ControlPermissions +import hu.bme.sch.cmsch.service.StorageService +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.RequestMapping + + +@Controller +@RequestMapping("/admin/control/component/popup") +class PopupComponentController( + adminMenuService: AdminMenuService, + component: PopupComponent, + menuService: MenuService, + auditLogService: AuditLogService, + storageService: StorageService +): ComponentApiBase( + adminMenuService, + PopupComponent::class.java, + component, + ControlPermissions.PERMISSION_CONTROL_APP, + componentMenuName = "Popup beállítások", + componentMenuIcon = "flash_on", + insertComponentCategory = false, + componentCategory = ApplicationComponent.STYLING_CATEGORY, + componentMenuPriority = 6, + menuService = menuService, + auditLogService = auditLogService, + storageService = storageService +) \ No newline at end of file diff --git a/frontend/src/api/contexts/config/types.ts b/frontend/src/api/contexts/config/types.ts index c2f3c793..3f0bb299 100644 --- a/frontend/src/api/contexts/config/types.ts +++ b/frontend/src/api/contexts/config/types.ts @@ -43,6 +43,7 @@ export interface Components { communities?: Communities footer?: Footer support?: Support + popup?: Popup } export interface App { @@ -449,3 +450,12 @@ export interface Communities { searchEnabledResort: boolean tinderEnabled: boolean } + +export interface Popup { + popupEnabled: boolean + popupUrls: string + popupBackground: string + popupOpacity: number + intervalLowBound: number + intervalHighBound: number +} diff --git a/frontend/src/common-components/layout/CmschLayout.tsx b/frontend/src/common-components/layout/CmschLayout.tsx index b80d2fd0..2ef1f7e4 100644 --- a/frontend/src/common-components/layout/CmschLayout.tsx +++ b/frontend/src/common-components/layout/CmschLayout.tsx @@ -9,6 +9,7 @@ import { EnableNotifications } from '../EnableNotifications' import { Footer } from '../footer/Footer' import { Navbar } from '../navigation/Navbar' import { Warning } from '../Warning' +import { Popup } from './Popup' import { ScrollToTop } from './ScrollToTop' export const CmschLayout = ({ children }: PropsWithChildren) => { @@ -25,17 +26,19 @@ export const CmschLayout = ({ children }: PropsWithChildren) => { } return ( -
- - -
- - - - {children} - -
-
+ +
+ + +
+ + + + {children} + +
+
+
) } diff --git a/frontend/src/common-components/layout/Popup.tsx b/frontend/src/common-components/layout/Popup.tsx new file mode 100644 index 00000000..6e95259a --- /dev/null +++ b/frontend/src/common-components/layout/Popup.tsx @@ -0,0 +1,61 @@ +import { useConfigContext } from '@/api/contexts/config/ConfigContext.tsx' +import { type PropsWithChildren, useEffect, useMemo, useState } from 'react' + +export const Popup = ({ children }: PropsWithChildren) => { + const component = useConfigContext()?.components?.popup + const popupUrls = useMemo( + () => + component?.popupUrls + .split(',') + .map((url) => url.trim()) + .filter((url) => url.length > 0) ?? [], + [component?.popupUrls] + ) + const popupConfigKey = [ + component?.popupEnabled, + component?.intervalLowBound, + component?.intervalHighBound, + component?.popupBackground, + component?.popupOpacity, + popupUrls.join(',') + ].join('|') + const [popup, setPopup] = useState<{ url: string; configKey: string }>() + //const [timerKey, setTimerKey] = useState(0) + + useEffect(() => { + if (!component?.popupEnabled || popupUrls.length === 0) return + + const lowBound = Math.max(0, component.intervalLowBound) + const highBound = Math.max(lowBound, component.intervalHighBound) + const intervalSeconds = Math.random() * (highBound - lowBound) + lowBound + const timer = window.setTimeout(() => { + const randomUrl = popupUrls[Math.floor(Math.random() * popupUrls.length)] + if (randomUrl) setPopup({ url: randomUrl, configKey: popupConfigKey }) + }, intervalSeconds * 1000) + + return () => window.clearTimeout(timer) + }, [component?.intervalHighBound, component?.intervalLowBound, component?.popupEnabled, popupConfigKey, popupUrls /*, timerKey*/]) + + const dismissPopup = () => { + setPopup(undefined) + //setTimerKey((key) => key + 1) + } + + return ( + <> + {children} + {popup?.configKey === popupConfigKey && component?.popupEnabled && ( +
+
+ +
+ )} + + ) +}