feat(kue705FO): scrolling für lange Modulnamen (48 Zeichen) + Version-Gate/Env-Override
- Unterstützt bis zu 48 Zeichen im Modulnamen; bei Überlänge automatische Laufschrift - Marquee via react-fast-marquee (SSR-sicher per next/dynamic) - Overflow-Erkennung + Tooltip mit vollem Namen - Version-Gate: aktiviert ab V4.30
This commit is contained in:
@@ -6,6 +6,6 @@ NEXT_PUBLIC_USE_MOCK_BACKEND_LOOP_START=false
|
|||||||
NEXT_PUBLIC_EXPORT_STATIC=false
|
NEXT_PUBLIC_EXPORT_STATIC=false
|
||||||
NEXT_PUBLIC_USE_CGI=false
|
NEXT_PUBLIC_USE_CGI=false
|
||||||
# App-Versionsnummer
|
# App-Versionsnummer
|
||||||
NEXT_PUBLIC_APP_VERSION=1.6.844
|
NEXT_PUBLIC_APP_VERSION=1.6.845
|
||||||
NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter)
|
NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter)
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ NEXT_PUBLIC_CPL_API_PATH=/CPL
|
|||||||
NEXT_PUBLIC_EXPORT_STATIC=true
|
NEXT_PUBLIC_EXPORT_STATIC=true
|
||||||
NEXT_PUBLIC_USE_CGI=true
|
NEXT_PUBLIC_USE_CGI=true
|
||||||
# App-Versionsnummer
|
# App-Versionsnummer
|
||||||
NEXT_PUBLIC_APP_VERSION=1.6.844
|
NEXT_PUBLIC_APP_VERSION=1.6.845
|
||||||
NEXT_PUBLIC_CPL_MODE=production
|
NEXT_PUBLIC_CPL_MODE=production
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
## [1.6.845] – 2025-09-05
|
||||||
|
|
||||||
|
- feat: prepare KÜ 8 for scrolling text
|
||||||
|
|
||||||
|
---
|
||||||
## [1.6.844] – 2025-09-05
|
## [1.6.844] – 2025-09-05
|
||||||
|
|
||||||
- test: woodpecker
|
- test: woodpecker
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
"use client"; // components/modules/kue705FO/Kue705FO.tsx
|
"use client"; // components/modules/kue705FO/Kue705FO.tsx
|
||||||
import React, { useState, useMemo } from "react";
|
import React, { useState, useMemo, useEffect, useRef } from "react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const Marquee: any = dynamic(() => import("react-fast-marquee"), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import KueModal from "./modals/SettingsModalWrapper";
|
import KueModal from "./modals/SettingsModalWrapper";
|
||||||
// import FallSensors from "../../fall-detection-sensors/FallSensors";
|
// import FallSensors from "../../fall-detection-sensors/FallSensors";
|
||||||
@@ -54,6 +59,19 @@ const Kue705FO: React.FC<Kue705FOProps> = ({
|
|||||||
// Admin authentication hook for security - using showModal as true for continuous auth check
|
// Admin authentication hook for security - using showModal as true for continuous auth check
|
||||||
const { isAdminLoggedIn } = useAdminAuth(true);
|
const { isAdminLoggedIn } = useAdminAuth(true);
|
||||||
|
|
||||||
|
// Modulname (max 48 Zeichen) vorbereiten
|
||||||
|
const moduleNameRaw = useMemo(
|
||||||
|
() => kueName?.[slotIndex] || `Modul ${slotIndex + 1}`,
|
||||||
|
[kueName, slotIndex]
|
||||||
|
);
|
||||||
|
const moduleName48 = useMemo(
|
||||||
|
() =>
|
||||||
|
typeof moduleNameRaw === "string"
|
||||||
|
? moduleNameRaw.slice(0, 48)
|
||||||
|
: String(moduleNameRaw),
|
||||||
|
[moduleNameRaw]
|
||||||
|
);
|
||||||
|
|
||||||
const [activeButton, setActiveButton] = useState<"Schleife" | "TDR" | "ISO">(
|
const [activeButton, setActiveButton] = useState<"Schleife" | "TDR" | "ISO">(
|
||||||
"Schleife"
|
"Schleife"
|
||||||
);
|
);
|
||||||
@@ -206,6 +224,53 @@ const Kue705FO: React.FC<Kue705FOProps> = ({
|
|||||||
);
|
);
|
||||||
const { setCurrentModulName } = useModulName(slotIndex, modulName);
|
const { setCurrentModulName } = useModulName(slotIndex, modulName);
|
||||||
//---------------------------------
|
//---------------------------------
|
||||||
|
// Version-gate für Laufschrift: erst ab V4.30 aktiv
|
||||||
|
const parseVersion = (v?: string): [number, number, number] => {
|
||||||
|
if (!v) return [0, 0, 0];
|
||||||
|
const m = String(v).match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
|
||||||
|
if (!m) return [0, 0, 0];
|
||||||
|
const major = parseInt(m[1] || "0", 10) || 0;
|
||||||
|
const minor = parseInt(m[2] || "0", 10) || 0;
|
||||||
|
const patch = parseInt(m[3] || "0", 10) || 0;
|
||||||
|
return [major, minor, patch];
|
||||||
|
};
|
||||||
|
const gte = (a: [number, number, number], b: [number, number, number]) => {
|
||||||
|
if (a[0] !== b[0]) return a[0] > b[0];
|
||||||
|
if (a[1] !== b[1]) return a[1] > b[1];
|
||||||
|
return a[2] >= b[2];
|
||||||
|
};
|
||||||
|
const marqueeOverride =
|
||||||
|
process.env.NEXT_PUBLIC_ENABLE_KUE_MARQUEE === "1" ||
|
||||||
|
process.env.NEXT_PUBLIC_ENABLE_KUE_MARQUEE === "true";
|
||||||
|
const scrollFeatureEnabled = useMemo(
|
||||||
|
() => marqueeOverride || gte(parseVersion(kueVersion), [4, 30, 0]),
|
||||||
|
[kueVersion, marqueeOverride]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Überlängen-Erkennung für Laufschrift
|
||||||
|
const nameContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const measureTextRef = useRef<HTMLSpanElement | null>(null);
|
||||||
|
const [shouldScroll, setShouldScroll] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const measure = () => {
|
||||||
|
if (!scrollFeatureEnabled) {
|
||||||
|
setShouldScroll(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const container = nameContainerRef.current;
|
||||||
|
const text = measureTextRef.current;
|
||||||
|
if (!container || !text) {
|
||||||
|
setShouldScroll(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const needs = text.scrollWidth > container.clientWidth + 2;
|
||||||
|
setShouldScroll(needs);
|
||||||
|
};
|
||||||
|
measure();
|
||||||
|
window.addEventListener("resize", measure);
|
||||||
|
return () => window.removeEventListener("resize", measure);
|
||||||
|
}, [moduleName48, scrollFeatureEnabled]);
|
||||||
//---------------------------------
|
//---------------------------------
|
||||||
const tdmChartData = useSelector(
|
const tdmChartData = useSelector(
|
||||||
(state: RootState) => state.tdmChartSlice.data
|
(state: RootState) => state.tdmChartSlice.data
|
||||||
@@ -360,8 +425,38 @@ const Kue705FO: React.FC<Kue705FOProps> = ({
|
|||||||
<div className="absolute top-0 left-[4.688rem] w-[0.188rem] h-full bg-white z-0"></div>
|
<div className="absolute top-0 left-[4.688rem] w-[0.188rem] h-full bg-white z-0"></div>
|
||||||
<div className="absolute top-[2.5rem] left-[4.688rem] w-[2.5rem] h-[0.188rem] bg-white z-0"></div>
|
<div className="absolute top-[2.5rem] left-[4.688rem] w-[2.5rem] h-[0.188rem] bg-white z-0"></div>
|
||||||
|
|
||||||
<div className="absolute bottom-[1.25rem] left-0 right-0 text-black text-[0.625rem] bg-gray-300 p-[0.063rem] text-center">
|
{/* Hidden measuring span for overflow detection (kept measurable) */}
|
||||||
{kueName?.[slotIndex] || `Modul ${slotIndex + 1}`}
|
<span
|
||||||
|
ref={measureTextRef}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: -9999,
|
||||||
|
top: -9999,
|
||||||
|
visibility: "hidden",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{moduleName48}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div
|
||||||
|
ref={nameContainerRef}
|
||||||
|
className="absolute bottom-[1.25rem] left-0 right-0 text-black text-[0.625rem] bg-gray-300 p-[0.063rem] overflow-hidden"
|
||||||
|
>
|
||||||
|
{shouldScroll && scrollFeatureEnabled ? (
|
||||||
|
<Marquee pauseOnHover gradient={false} speed={40}>
|
||||||
|
<span className="pr-8 whitespace-nowrap" title={moduleName48}>
|
||||||
|
{moduleName48}
|
||||||
|
</span>
|
||||||
|
</Marquee>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
className="block text-center whitespace-nowrap"
|
||||||
|
title={moduleName48}
|
||||||
|
>
|
||||||
|
{moduleName48}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute bottom-[0.063rem] right-[0.063rem] text-black text-[0.5rem]">
|
<div className="absolute bottom-[0.063rem] right-[0.063rem] text-black text-[0.5rem]">
|
||||||
|
|||||||
@@ -94,9 +94,9 @@ in Rot, wenn Schleifenfehler ansteht
|
|||||||
|
|
||||||
# Folgende Erweiterung / Neuerungen:
|
# Folgende Erweiterung / Neuerungen:
|
||||||
|
|
||||||
[ ] TODO: Messverlauf bei Systemwerten (Temperatur und Spannungen) mit Datumsauswahl
|
[x] TODO: Messverlauf bei Systemwerten (Temperatur und Spannungen) mit Datumsauswahl
|
||||||
|
|
||||||
[ ] TODO: Formatierung der Kabelüberwachungswerten in den visuellen Einschüben (Isowert mit Komma und 2 Nachkommastellen; RSL mit Komma und 3 Noachkommastellen) Nachkommastellen immer anzeigen und mit Nullen auffüllen.
|
[x] TODO: Formatierung der Kabelüberwachungswerten in den visuellen Einschüben (Isowert mit Komma und 2 Nachkommastellen; RSL mit Komma und 3 Noachkommastellen) Nachkommastellen immer anzeigen und mit Nullen auffüllen.
|
||||||
|
|
||||||
[ ] TODO: Admin User nach einer Zeit von einer Stunde löschen (Cookie oder Local Storrage)
|
[ ] TODO: Admin User nach einer Zeit von einer Stunde löschen (Cookie oder Local Storrage)
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ var win_kueLoopInterval = [
|
|||||||
//---------------------------------------------------
|
//---------------------------------------------------
|
||||||
//KÜ Modul Version soll /100 und davor V angezeigt werden z.B. 4.19V
|
//KÜ Modul Version soll /100 und davor V angezeigt werden z.B. 4.19V
|
||||||
var win_kueVersion = [
|
var win_kueVersion = [
|
||||||
420, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419,
|
420, 419, 419, 419, 419, 419, 419, 430, 419, 419, 419, 419, 419, 419, 419,
|
||||||
419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419,
|
419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419, 419,
|
||||||
419, 419,
|
419, 419,
|
||||||
];
|
];
|
||||||
|
|||||||
15
package-lock.json
generated
15
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.844",
|
"version": "1.6.845",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.844",
|
"version": "1.6.845",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/roboto": "^5.1.0",
|
"@fontsource/roboto": "^5.1.0",
|
||||||
"@headlessui/react": "^2.2.4",
|
"@headlessui/react": "^2.2.4",
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
"react-datepicker": "^8.0.0",
|
"react-datepicker": "^8.0.0",
|
||||||
"react-day-picker": "^9.6.7",
|
"react-day-picker": "^9.6.7",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-fast-marquee": "^1.6.0",
|
||||||
"react-modal": "^3.16.1",
|
"react-modal": "^3.16.1",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.1.2",
|
||||||
"react-spinners": "^0.14.1",
|
"react-spinners": "^0.14.1",
|
||||||
@@ -9161,6 +9162,16 @@
|
|||||||
"react": "^18.3.1"
|
"react": "^18.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-fast-marquee": {
|
||||||
|
"version": "1.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-fast-marquee/-/react-fast-marquee-1.6.5.tgz",
|
||||||
|
"integrity": "sha512-swDnPqrT2XISAih0o74zQVE2wQJFMvkx+9VZXYYNSLb/CUcAzU9pNj637Ar2+hyRw6b4tP6xh4GQZip2ZCpQpg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16.8.0 || ^18.0.0",
|
||||||
|
"react-dom": ">= 16.8.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-fit": {
|
"node_modules/react-fit": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-fit/-/react-fit-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-fit/-/react-fit-2.0.1.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cpl-v4",
|
"name": "cpl-v4",
|
||||||
"version": "1.6.844",
|
"version": "1.6.845",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3000",
|
"dev": "next dev -p 3000",
|
||||||
@@ -60,6 +60,7 @@
|
|||||||
"next": "^14.2.23",
|
"next": "^14.2.23",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
"react-fast-marquee": "^1.6.0",
|
||||||
"react-chartjs-2": "^5.3.0",
|
"react-chartjs-2": "^5.3.0",
|
||||||
"react-date-picker": "^11.0.0",
|
"react-date-picker": "^11.0.0",
|
||||||
"react-datepicker": "^8.0.0",
|
"react-datepicker": "^8.0.0",
|
||||||
|
|||||||
17
types/react-fast-marquee.d.ts
vendored
Normal file
17
types/react-fast-marquee.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
declare module "react-fast-marquee" {
|
||||||
|
import * as React from "react";
|
||||||
|
interface MarqueeProps {
|
||||||
|
speed?: number;
|
||||||
|
pauseOnHover?: boolean;
|
||||||
|
gradient?: boolean;
|
||||||
|
gradientColor?: [number, number, number];
|
||||||
|
gradientWidth?: number | string;
|
||||||
|
direction?: "left" | "right";
|
||||||
|
play?: boolean;
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
const Marquee: React.FC<MarqueeProps>;
|
||||||
|
export default Marquee;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user