diff --git a/.github/workflows/test-and-release.yml b/.github/workflows/test-and-release.yml index e99dac7b9..171ec0b03 100644 --- a/.github/workflows/test-and-release.yml +++ b/.github/workflows/test-and-release.yml @@ -40,6 +40,9 @@ jobs: - name: Build run: NODE_OPTIONS=--max_old_space_size=8192 npm run build + - name: Test TypeScript + run: npm run check-ts + - name: Run local tests run: npm run test-gui diff --git a/package.json b/package.json index 0d9cfcedf..b84205d8b 100644 --- a/package.json +++ b/package.json @@ -35,12 +35,13 @@ "@alcalzone/release-script-plugin-iobroker": "^3.7.0", "@alcalzone/release-script-plugin-license": "^3.7.0", "@iobroker/vis-2-widgets-testing": "^1.0.0", - "@tsconfig/node16": "^16.1.1", + "@tsconfig/node18": "^18.2.2", "@types/mocha": "^10.0.6", "chai": "^4.3.10", "gulp": "^4.0.2", "iobroker.web": "*", "mocha": "^10.3.0", + "typescript": "^5.4.2", "unzipper": "^0.10.14" }, "bugs": { @@ -57,6 +58,7 @@ "main.js" ], "scripts": { + "check-ts": "tsc --project src/tsconfig.json", "start": "cd src && npm run start", "test": "mocha ./test/*.engine.js --exit", "test-gui": "mocha ./test/*.gui.js --exit", diff --git a/src/src/Toolbar/WidgetImportDialog.tsx b/src/src/Toolbar/WidgetImportDialog.tsx index 122e4279e..e105f4a5b 100644 --- a/src/src/Toolbar/WidgetImportDialog.tsx +++ b/src/src/Toolbar/WidgetImportDialog.tsx @@ -14,6 +14,7 @@ import { import { useFocus } from '@/Utils'; import { store } from '@/Store'; import { + AnyWidgetId, GroupWidget, GroupWidgetId, Project, Widget, } from '@/types'; import CustomAceEditor from '../Components/CustomAceEditor'; @@ -59,7 +60,7 @@ const WidgetImportDialog = (props: WidgetImportDialogProps) => { newWidgets[newKey] = widget; if (widget.grouped && widget.groupid && newWidgets[widget.groupid]?.data?.members) { // find group - const pos = (newWidgets[widget.groupid] as GroupWidget).data.members.indexOf(widget._id as string); + const pos = (newWidgets[widget.groupid] as GroupWidget).data.members.indexOf(widget._id as AnyWidgetId); if (pos !== -1) { (newWidgets[widget.groupid] as GroupWidget).data.members[pos] = newKey; } @@ -73,7 +74,7 @@ const WidgetImportDialog = (props: WidgetImportDialogProps) => { if (!isGroup(newWidgets[wid]) && props.selectedGroup !== undefined) { newWidgets[wid].grouped = true; newWidgets[wid].groupid = props.selectedGroup; - (project[props.selectedView].widgets[props.selectedGroup] as GroupWidget).data.members.push(wid); + (project[props.selectedView].widgets[props.selectedGroup] as GroupWidget).data.members.push(wid as AnyWidgetId); } }); diff --git a/src/src/Vis/Widgets/Basic/BasicBar.tsx b/src/src/Vis/Widgets/Basic/BasicBar.tsx index 26fb73fd5..a480eba3e 100644 --- a/src/src/Vis/Widgets/Basic/BasicBar.tsx +++ b/src/src/Vis/Widgets/Basic/BasicBar.tsx @@ -65,7 +65,7 @@ export default class BasicBar extends VisRxWidget { width: 200, height: 130, }, - }; + } as const; } /** diff --git a/src/src/Vis/Widgets/Basic/BasicFilterDropdown.tsx b/src/src/Vis/Widgets/Basic/BasicFilterDropdown.tsx index f88190044..24572cc1f 100644 --- a/src/src/Vis/Widgets/Basic/BasicFilterDropdown.tsx +++ b/src/src/Vis/Widgets/Basic/BasicFilterDropdown.tsx @@ -25,11 +25,13 @@ import { Edit } from '@mui/icons-material'; import { I18n, Icon } from '@iobroker/adapter-react-v5'; -import { GetRxDataFromWidget, RxRenderWidgetProps, RxWidgetInfo } from '@/types'; +import { + GetRxDataFromWidget, RxRenderWidgetProps, RxWidgetInfo, WidgetData, +} from '@/types'; import VisRxWidget from '@/Vis/visRxWidget'; -import { WidgetData } from '@/types'; +import { RxWidgetInfoAttributesField, RxWidgetInfoCustomComponentProperties, RxWidgetInfoCustomComponentContext } from '@/allInOneTypes'; +import { VisWidgetCommand } from '@/Vis/visBaseWidget'; import FiltersEditorDialog from './FiltersEditorDialog'; -import { RxWidgetInfoAttributesField, RxWidgetInfoCustomComponentProperties, RxWidgetInfoCustomComponentContext } from "@/allInOneTypes"; // eslint-disable-next-line no-use-before-define type RxData = GetRxDataFromWidget @@ -281,7 +283,7 @@ class BasicFilterDropdown extends VisRxWidget { } } - onCommand(command: string): void { + onCommand(command: VisWidgetCommand): void { if (command === 'changeFilter') { // analyse filter this.forceUpdate(); diff --git a/src/src/Vis/Widgets/Basic/BasicIFrame.tsx b/src/src/Vis/Widgets/Basic/BasicIFrame.tsx index 259723e58..0fbde5dff 100644 --- a/src/src/Vis/Widgets/Basic/BasicIFrame.tsx +++ b/src/src/Vis/Widgets/Basic/BasicIFrame.tsx @@ -4,21 +4,9 @@ import { GetRxDataFromWidget, RxRenderWidgetProps } from '@/types'; import VisRxWidget from '@/Vis/visRxWidget'; // eslint-disable-next-line no-use-before-define -// type RxData = GetRxDataFromWidget - -interface BasicIFrameData { - src: string; - refreshInterval: number; - refreshWithNoQuery: boolean; - noSandbox: boolean; - refreshOnViewChange: boolean; - refreshOnWakeUp: boolean; - scrollX: boolean; - scrollY: boolean; - seamless: boolean; -} +type RxData = GetRxDataFromWidget -export default class BasicIFrame extends VisRxWidget { +export default class BasicIFrame extends VisRxWidget { private refreshInterval: ReturnType | null = null; private readonly frameRef: React.RefObject; @@ -96,7 +84,7 @@ export default class BasicIFrame extends VisRxWidget { width: 600, height: 320, }, - }; + } as const; } async componentDidMount(): Promise { diff --git a/src/src/Vis/Widgets/Basic/BasicImage.tsx b/src/src/Vis/Widgets/Basic/BasicImage.tsx index d5fef574f..08aa59a61 100644 --- a/src/src/Vis/Widgets/Basic/BasicImage.tsx +++ b/src/src/Vis/Widgets/Basic/BasicImage.tsx @@ -4,19 +4,9 @@ import { GetRxDataFromWidget, RxRenderWidgetProps } from '@/types'; import VisRxWidget from '@/Vis/visRxWidget'; // eslint-disable-next-line no-use-before-define -// type RxData = GetRxDataFromWidget - -interface BasicImageData { - src: string; - stretch: boolean; - refreshInterval: number; - refreshOnWakeUp: boolean; - refreshOnViewChange: boolean; - refreshWithNoQuery: boolean; - allowUserInteractions: boolean; -} +type RxData = GetRxDataFromWidget -export default class BasicImage extends VisRxWidget { +export default class BasicImage extends VisRxWidget { private refreshInterval: ReturnType | null = null; private readonly imageRef: React.RefObject; @@ -85,7 +75,7 @@ export default class BasicImage extends VisRxWidget { width: 200, height: 130, }, - }; + } as const; } async componentDidMount(): Promise { diff --git a/src/src/Vis/Widgets/Basic/BasicImage8.tsx b/src/src/Vis/Widgets/Basic/BasicImage8.tsx index bbbb1f009..1435150bb 100644 --- a/src/src/Vis/Widgets/Basic/BasicImage8.tsx +++ b/src/src/Vis/Widgets/Basic/BasicImage8.tsx @@ -56,7 +56,7 @@ export default class BasicImage8 extends VisRxWidget { }, ], }], - }; + } as const; } /** diff --git a/src/src/Vis/Widgets/Basic/BasicRedNumber.tsx b/src/src/Vis/Widgets/Basic/BasicRedNumber.tsx index 24eee00a4..cd1face0f 100644 --- a/src/src/Vis/Widgets/Basic/BasicRedNumber.tsx +++ b/src/src/Vis/Widgets/Basic/BasicRedNumber.tsx @@ -85,7 +85,7 @@ export default class BasicRedNumber extends VisRxWidget { width: 52, height: 30, }, - }; + } as const; } /** @@ -123,7 +123,7 @@ export default class BasicRedNumber extends VisRxWidget { top: 0, left: 0, zIndex: 0, - color: this.state.rxData.backgroundColor || 'red', + color: this.state.rxData.background || 'red', }} />
{ borderColor: this.state.rxData.borderColor || 'white', borderWidth: 3, borderStyle: 'solid', - backgroundColor: this.state.rxData.backgroundColor || 'red', + backgroundColor: this.state.rxData.background || 'red', minWidth: 21, textAlign: 'center', color: props.style.color || 'white', diff --git a/src/src/Vis/Widgets/Basic/BasicScreenResolution.tsx b/src/src/Vis/Widgets/Basic/BasicScreenResolution.tsx index 822bdf4dd..5dffeaa6c 100644 --- a/src/src/Vis/Widgets/Basic/BasicScreenResolution.tsx +++ b/src/src/Vis/Widgets/Basic/BasicScreenResolution.tsx @@ -3,10 +3,9 @@ import React from 'react'; import { I18n } from '@iobroker/adapter-react-v5'; import { - RxRenderWidgetProps, VisLegacy, - RxWidgetState, RxWidgetProps, GetRxDataFromWidget, + RxRenderWidgetProps, VisLegacy, RxWidgetProps, GetRxDataFromWidget, } from '@/types'; -import VisRxWidget from '@/Vis/visRxWidget'; +import VisRxWidget, { VisRxWidgetState } from '@/Vis/visRxWidget'; declare global { interface Window { @@ -14,7 +13,7 @@ declare global { } } -interface BasicScreenResolutionState extends RxWidgetState { +interface BasicScreenResolutionState extends VisRxWidgetState { width: number; height: number; defaultView: string; @@ -30,9 +29,11 @@ export default class BasicScreenResolution extends VisRxWidget { @@ -122,7 +123,6 @@ export default class BasicScreenResolution extends VisRxWidget -interface BasicSpeechToTextState { +interface BasicSpeechToTextState extends VisBaseWidgetState { /** Current shown module text */ text: string; /** Current shown image */ diff --git a/src/src/Vis/Widgets/Basic/BasicSvgShape.tsx b/src/src/Vis/Widgets/Basic/BasicSvgShape.tsx index acc76872e..de090f783 100644 --- a/src/src/Vis/Widgets/Basic/BasicSvgShape.tsx +++ b/src/src/Vis/Widgets/Basic/BasicSvgShape.tsx @@ -90,7 +90,7 @@ export default class BasicSvgShape extends VisRxWidget { width: 100, height: 100, }, - }; + } as const; } /** diff --git a/src/src/Vis/visBaseWidget.tsx b/src/src/Vis/visBaseWidget.tsx index c18bbeb38..4b92a39b3 100644 --- a/src/src/Vis/visBaseWidget.tsx +++ b/src/src/Vis/visBaseWidget.tsx @@ -107,7 +107,7 @@ export interface VisBaseWidgetState { widgetHint?: 'light' | 'dark' | 'hide'; hideHelper?: boolean; isHidden?: boolean; - gap?: number, + gap?: number; draggable?: boolean; showRelativeMoveMenu?: boolean; } @@ -148,7 +148,7 @@ interface VisBaseWidget { renderLastChange(style: unknown): React.ReactNode; } -class VisBaseWidget extends React.Component { +class VisBaseWidget = VisBaseWidgetState> extends React.Component { static FORBIDDEN_CHARS = /[^._\-/ :!#$%&()+=@^{}|~]+/g; // from https://github.com/ioBroker/ioBroker.js-controller/blob/master/packages/common/lib/common/tools.js /** We do not store the SVG Element in the state because it is cyclic */ @@ -219,7 +219,7 @@ class VisBaseWidget extends React.Component, TState extends Record = Record> extends VisBaseWidget { - visHidden?: boolean; - adapter?: string; - version?: string; - url?: string; - custom?: any; - state: VisRxWidgetState & TState & { rxData: TRxData }; -} - -class VisRxWidget, TState extends Record = Record> extends VisBaseWidget{ +class VisRxWidget, TState extends Partial = VisRxWidgetState> extends VisBaseWidget { static POSSIBLE_MUI_STYLES = POSSIBLE_MUI_STYLES; static i18nPrefix: string | undefined; @@ -432,7 +420,6 @@ class VisRxWidget, TState extends Record, TState extends Record !this.linkContext.IDs.includes(id)); - // @ts-expect-error check later if types wrong or call wrong if (unsubscribe.length) { + // @ts-expect-error check later if types wrong or call wrong await context.socket.unsubscribeState(unsubscribe, this.onStateChangedBind); } const subscribe = this.linkContext.IDs.filter(id => !oldIDs.includes(id)); - // @ts-expect-error check later if types wrong or call wrong if (subscribe.length) { + // @ts-expect-error check later if types wrong or call wrong await context.socket.subscribeState(subscribe, this.onStateChangedBind); } @@ -1028,7 +1015,7 @@ class VisRxWidget, TState extends Record { + getWidgetInfo(): Readonly { throw new Error('not implemented'); } } diff --git a/src/src/types.d.ts b/src/src/types.d.ts index c8732d5ce..5dfbee242 100644 --- a/src/src/types.d.ts +++ b/src/src/types.d.ts @@ -187,7 +187,7 @@ export interface ViewSettings { 'line-height'?: string; 'letter-spacing'?: string; 'word-spacing'?: string; - } + }; useAsDefault?: boolean; alwaysRender?: boolean; @@ -414,8 +414,8 @@ interface VisBindingOperationArgument { } interface VisBindingOperation { - op: VisBindingOperationType, - arg?: VisBindingOperationArgument[] | string | number | string[], + op: VisBindingOperationType; + arg?: VisBindingOperationArgument[] | string | number | string[]; formula?: string; } @@ -425,8 +425,8 @@ interface VisBinding { /** ioBroker state ID */ systemOid: StateID; /** Part of the string, like {id.ack} */ - token: string, - operations?: VisBindingOperation[], + token: string; + operations?: VisBindingOperation[]; format: string; isSeconds: boolean; } @@ -457,7 +457,7 @@ export interface CustomPaletteProperties { selectedView: string; themeType: 'dark' | 'light'; helpers: { - deviceIcons: Record + deviceIcons: Record; detectDevices: (socket: Connection) => Promise; getObjectIcon: (obj: ioBroker.Object, id?: string, imagePrefix?: string) => React.JSX.Element; allObjects: (socket: Connection) => Promise>; @@ -467,12 +467,11 @@ export interface CustomPaletteProperties { }; } - interface RxWidgetInfoAttributes { /** Name of the attributes section */ readonly name: string; /** Fields of this attribute section */ - readonly fields: RxWidgetInfoAttributesField[]; + fields: readonly RxWidgetInfoAttributesField[]; /** I18n Label */ readonly label?: string; readonly indexFrom?: number; @@ -503,7 +502,7 @@ interface RxWidgetInfo { readonly visWidgetColor?: string; /** Groups of attributes */ - readonly visAttrs: RxWidgetInfoAttributes[]; + visAttrs: (readonly RxWidgetInfoAttributes[]); /** Default style for widget */ readonly visDefaultStyle?: React.CSSProperties; /** Position in the widget set */ @@ -614,4 +613,4 @@ type GetRxDataFromVisAttrs> = { } /** Infers the RxData from a given Widget */ -type GetRxDataFromWidget Record }> = GetRxDataFromVisAttrs> +type GetRxDataFromWidget Record }> = GetRxDataFromVisAttrs> diff --git a/src/tsconfig.json b/src/tsconfig.json index c8e0b1e42..0c1702b64 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,9 +1,10 @@ { "extends": "../tsconfig.json", "compilerOptions": { + "module": "ES2020", "moduleResolution": "Bundler", "baseUrl": ".", - "checkJs": true, + "checkJs": false, "noEmit": false, "outDir": "./build", "sourceMap": true, diff --git a/tsconfig.json b/tsconfig.json index 358f6a885..c3ce17910 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ // Root tsconfig to set the settings and power editor support for all TS files { - "extends": "@tsconfig/node16/tsconfig.json", + "extends": "@tsconfig/node18/tsconfig.json", "compilerOptions": { // do not compile anything, this file is just to configure type checking "noEmit": true,