Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bring back type inference #400

Merged
merged 6 commits into from
Mar 12, 2024
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
3 changes: 3 additions & 0 deletions .github/workflows/test-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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",
Expand Down
5 changes: 3 additions & 2 deletions src/src/Toolbar/WidgetImportDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}
Expand All @@ -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);
}
});

Expand Down
2 changes: 1 addition & 1 deletion src/src/Vis/Widgets/Basic/BasicBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export default class BasicBar extends VisRxWidget<RxData> {
width: 200,
height: 130,
},
};
} as const;
}

/**
Expand Down
10 changes: 6 additions & 4 deletions src/src/Vis/Widgets/Basic/BasicFilterDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof BasicFilterDropdown>
Expand Down Expand Up @@ -281,7 +283,7 @@ class BasicFilterDropdown extends VisRxWidget<RxData> {
}
}

onCommand(command: string): void {
onCommand(command: VisWidgetCommand): void {
if (command === 'changeFilter') {
// analyse filter
this.forceUpdate();
Expand Down
18 changes: 3 additions & 15 deletions src/src/Vis/Widgets/Basic/BasicIFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof BasicIFrame>

interface BasicIFrameData {
src: string;
refreshInterval: number;
refreshWithNoQuery: boolean;
noSandbox: boolean;
refreshOnViewChange: boolean;
refreshOnWakeUp: boolean;
scrollX: boolean;
scrollY: boolean;
seamless: boolean;
}
type RxData = GetRxDataFromWidget<typeof BasicIFrame>

export default class BasicIFrame extends VisRxWidget<BasicIFrameData> {
export default class BasicIFrame extends VisRxWidget<RxData> {
private refreshInterval: ReturnType<typeof setInterval> | null = null;

private readonly frameRef: React.RefObject<HTMLIFrameElement>;
Expand Down Expand Up @@ -96,7 +84,7 @@ export default class BasicIFrame extends VisRxWidget<BasicIFrameData> {
width: 600,
height: 320,
},
};
} as const;
}

async componentDidMount(): Promise<void> {
Expand Down
16 changes: 3 additions & 13 deletions src/src/Vis/Widgets/Basic/BasicImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof BasicImage>

interface BasicImageData {
src: string;
stretch: boolean;
refreshInterval: number;
refreshOnWakeUp: boolean;
refreshOnViewChange: boolean;
refreshWithNoQuery: boolean;
allowUserInteractions: boolean;
}
type RxData = GetRxDataFromWidget<typeof BasicImage>

export default class BasicImage extends VisRxWidget<BasicImageData> {
export default class BasicImage extends VisRxWidget<RxData> {
private refreshInterval: ReturnType<typeof setInterval> | null = null;

private readonly imageRef: React.RefObject<HTMLImageElement>;
Expand Down Expand Up @@ -85,7 +75,7 @@ export default class BasicImage extends VisRxWidget<BasicImageData> {
width: 200,
height: 130,
},
};
} as const;
}

async componentDidMount(): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion src/src/Vis/Widgets/Basic/BasicImage8.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default class BasicImage8 extends VisRxWidget<RxData> {
},
],
}],
};
} as const;
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/src/Vis/Widgets/Basic/BasicRedNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
width: 52,
height: 30,
},
};
} as const;
}

/**
Expand Down Expand Up @@ -123,7 +123,7 @@
top: 0,
left: 0,
zIndex: 0,
color: this.state.rxData.backgroundColor || 'red',
color: this.state.rxData.background || 'red',
}}
/>
<div
Expand All @@ -150,11 +150,11 @@

const style: React.CSSProperties = {
padding: 3,
borderRadius: this.state.rxData.borderRadius !== undefined && this.state.rxData.borderRadius !== null ? parseFloat(this.state.rxData.borderRadius) : 16,

Check failure on line 153 in src/src/Vis/Widgets/Basic/BasicRedNumber.tsx

View workflow job for this annotation

GitHub Actions / adapter-tests-gui

Argument of type 'number' is not assignable to parameter of type 'string'.

Check failure on line 153 in src/src/Vis/Widgets/Basic/BasicRedNumber.tsx

View workflow job for this annotation

GitHub Actions / adapter-tests-gui

Argument of type 'number' is not assignable to parameter of type 'string'.
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',
Expand Down
18 changes: 9 additions & 9 deletions src/src/Vis/Widgets/Basic/BasicScreenResolution.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@
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 {
vis: VisLegacy;
}
}

interface BasicScreenResolutionState extends RxWidgetState {
interface BasicScreenResolutionState extends VisRxWidgetState {
width: number;
height: number;
defaultView: string;
Expand All @@ -24,15 +23,17 @@
// eslint-disable-next-line no-use-before-define
type RxData = GetRxDataFromWidget<typeof BasicScreenResolution>

export default class BasicScreenResolution extends VisRxWidget<RxData, BasicScreenResolutionState> {

Check failure on line 26 in src/src/Vis/Widgets/Basic/BasicScreenResolution.tsx

View workflow job for this annotation

GitHub Actions / adapter-tests-gui

Class 'BasicScreenResolution' incorrectly extends base class 'VisRxWidget<RxData, BasicScreenResolutionState>'.

Check failure on line 26 in src/src/Vis/Widgets/Basic/BasicScreenResolution.tsx

View workflow job for this annotation

GitHub Actions / adapter-tests-gui

Class 'BasicScreenResolution' incorrectly extends base class 'VisRxWidget<RxData, BasicScreenResolutionState>'.
private essentialData: string;

constructor(props: RxWidgetProps) {
// @ts-expect-error refactor types to extend from parent types
super(props);
const state = this.state;
state.width = document.documentElement.clientWidth;
state.height = document.documentElement.clientHeight;
this.state = {
...this.state,
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
};
this.essentialData = JSON.stringify(this.buildEssentialProjectData());
}

Expand Down Expand Up @@ -63,7 +64,7 @@
width: 170,
height: 75,
},
};
} as const;
}

async componentDidMount(): Promise<void> {
Expand Down Expand Up @@ -122,7 +123,6 @@
defaultView = window.vis.findNearestResolution();
}
this.setState({
// @ts-expect-error unknown error
width,
height,
defaultView,
Expand Down
3 changes: 2 additions & 1 deletion src/src/Vis/Widgets/Basic/BasicSpeechToText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import type {
import { I18n } from '@iobroker/adapter-react-v5';
// @ts-expect-error fix import
import type * as SpeechRecognition from 'dom-speech-recognition';
import type { VisBaseWidgetState } from '@/Vis/visBaseWidget';

// eslint-disable-next-line no-use-before-define
type RxData = GetRxDataFromWidget<typeof BasicSpeechToText>

interface BasicSpeechToTextState {
interface BasicSpeechToTextState extends VisBaseWidgetState {
/** Current shown module text */
text: string;
/** Current shown image */
Expand Down
2 changes: 1 addition & 1 deletion src/src/Vis/Widgets/Basic/BasicSvgShape.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default class BasicSvgShape extends VisRxWidget<RxData> {
width: 100,
height: 100,
},
};
} as const;
}

/**
Expand Down
10 changes: 5 additions & 5 deletions src/src/Vis/visBaseWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export interface VisBaseWidgetState {
widgetHint?: 'light' | 'dark' | 'hide';
hideHelper?: boolean;
isHidden?: boolean;
gap?: number,
gap?: number;
draggable?: boolean;
showRelativeMoveMenu?: boolean;
}
Expand Down Expand Up @@ -148,7 +148,7 @@ interface VisBaseWidget {
renderLastChange(style: unknown): React.ReactNode;
}

class VisBaseWidget extends React.Component<VisBaseWidgetProps, VisBaseWidgetState> {
class VisBaseWidget<TState extends Partial<VisBaseWidgetState> = VisBaseWidgetState> extends React.Component<VisBaseWidgetProps, TState & VisBaseWidgetState> {
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 */
Expand Down Expand Up @@ -219,7 +219,7 @@ class VisBaseWidget extends React.Component<VisBaseWidgetProps, VisBaseWidgetSta
),
hideHelper: false,
gap: style.position === 'relative' ? (isVarFinite(props.context.views[props.view].settings?.rowGap) ? parseFloat(props.context.views[props.view].settings?.rowGap as string) : 0) : 0,
};
} as TState & VisBaseWidgetState;

this.onCommandBound = this.onCommand.bind(this);
}
Expand Down Expand Up @@ -399,8 +399,8 @@ class VisBaseWidget extends React.Component<VisBaseWidgetProps, VisBaseWidgetSta
}

// take actual (old) style and data
let styleStr: string = state.style?._originalData ? state.style._originalData : JSON.stringify(state.style);
let dataStr: string = state.data?._originalData ? state.data._originalData : JSON.stringify(state.data);
const styleStr: string = state.style?._originalData ? state.style._originalData : JSON.stringify(state.style);
const dataStr: string = state.data?._originalData ? state.data._originalData : JSON.stringify(state.data);

const isHidden = VisBaseWidget.isWidgetFilteredOutStatic(
props.viewsActiveFilter,
Expand Down
25 changes: 6 additions & 19 deletions src/src/Vis/visRxWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@
'word-spacing',
];

interface VisRxWidgetProps extends VisBaseWidgetProps {

}
type VisRxWidgetProps = VisBaseWidgetProps

interface RxData {
_originalData: any;
Expand Down Expand Up @@ -111,24 +109,14 @@
selectedWidget: AnyWidgetId;
}

interface VisRxWidgetState extends VisBaseWidgetState {
export interface VisRxWidgetState extends VisBaseWidgetState {
rxData: RxData;
values: VisRxWidgetStateValues;
visible: boolean;
disabled?: boolean;
}

/** TODO: this overload can be removed as soon as VisBaseWidget is written correctly in TS */
interface VisRxWidget<TRxData extends Record<string, any>, TState extends Record<string, any> = Record<string, never>> extends VisBaseWidget {
visHidden?: boolean;
adapter?: string;
version?: string;
url?: string;
custom?: any;
state: VisRxWidgetState & TState & { rxData: TRxData };
}

class VisRxWidget<TRxData extends Record<string, any>, TState extends Record<string, any> = Record<string, never>> extends VisBaseWidget<TState>{
class VisRxWidget<TRxData extends Record<string, any>, TState extends Partial<VisRxWidgetState> = VisRxWidgetState> extends VisBaseWidget<VisRxWidgetState & TState & { rxData: TRxData }> {
static POSSIBLE_MUI_STYLES = POSSIBLE_MUI_STYLES;

static i18nPrefix: string | undefined;
Expand Down Expand Up @@ -173,7 +161,7 @@
// collect all attributes (only types)
if (Array.isArray(options.visAttrs)) {
options.visAttrs.forEach(group =>
group.fields && group.fields.forEach(item => {

Check failure on line 164 in src/src/Vis/visRxWidget.tsx

View workflow job for this annotation

GitHub Actions / adapter-tests-gui

Parameter 'item' implicitly has an 'any' type.

Check failure on line 164 in src/src/Vis/visRxWidget.tsx

View workflow job for this annotation

GitHub Actions / adapter-tests-gui

Parameter 'item' implicitly has an 'any' type.
widgetAttrInfo[item.name] = { type: item.type };
}));
}
Expand Down Expand Up @@ -303,7 +291,7 @@
* @param doNotApplyState if state should not be set
*/
onStateChanged(id?: string | null, state?: typeof this.state | null, doNotApplyState?: boolean) {
this.newState = this.newState || {

Check failure on line 294 in src/src/Vis/visRxWidget.tsx

View workflow job for this annotation

GitHub Actions / adapter-tests-gui

Type 'Partial<VisRxWidgetState & { rxData: TRxData; }> | { values: (VisRxWidgetState & TState & { rxData: TRxData; } & VisBaseWidgetState)["values"]; rxData: WidgetDataState | GroupDataState; rxStyle: WidgetStyleState; editMode: boolean; applyBindings: false; }' is not assignable to type 'Partial<VisRxWidgetState & { rxData: TRxData; }> | null | undefined'.

Check failure on line 294 in src/src/Vis/visRxWidget.tsx

View workflow job for this annotation

GitHub Actions / adapter-tests-gui

Type 'Partial<VisRxWidgetState & { rxData: TRxData; }> | { values: (VisRxWidgetState & TState & { rxData: TRxData; } & VisBaseWidgetState)["values"]; rxData: WidgetDataState | GroupDataState; rxStyle: WidgetStyleState; editMode: boolean; applyBindings: false; }' is not assignable to type 'Partial<VisRxWidgetState & { rxData: TRxData; }> | null | undefined'.
values: deepClone(this.state.values || {}),
rxData: { ...this.state.data } as WidgetDataState | GroupDataState,
rxStyle: { ...this.state.style } as WidgetStyleState,
Expand Down Expand Up @@ -432,7 +420,6 @@
}

async componentWillUnmount() {
// @ts-expect-error check later if types wrong or call wrong
if (this.linkContext.IDs.length) {
await this.props.context.socket.unsubscribeState(this.linkContext.IDs, this.onStateChangedBind);
}
Expand Down Expand Up @@ -510,14 +497,14 @@

// subscribe on some new IDs and remove old IDs
const unsubscribe = oldIDs.filter(id => !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);
}

Expand Down Expand Up @@ -1028,7 +1015,7 @@
* Get information about specific widget, needs to be implemented by widget class
*/
// eslint-disable-next-line class-methods-use-this
getWidgetInfo(): Record<string, any> {
getWidgetInfo(): Readonly<RxWidgetInfo> {
throw new Error('not implemented');
}
}
Expand Down
Loading
Loading