Skip to content

Commit

Permalink
Implemented swipe widget
Browse files Browse the repository at this point in the history
  • Loading branch information
GermanBluefox committed Jul 16, 2024
1 parent 147274f commit 4e24309
Show file tree
Hide file tree
Showing 23 changed files with 690 additions and 234 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ npm run start
## Changelog
### **WORK IN PROGRESS**
* (bluefox) Corrected the jumping by object selection
* (bluefox) Implemented swipe widget

### 2.10.3 (2024-07-11)
* (bluefox) Converted the CanJSWidget to typescript
Expand Down Expand Up @@ -440,8 +441,8 @@ npm run start
### 2.9.13 (2024-01-08)
* (foxriver76) correctly detect IDs in bindings when they contain hash character
* (foxriver76) fix crash when multiple JquiState widgets selected
* (foxriver76) prevent showing widget in group after it is already cut out
* (foxriver76) prevent usage of widgets which are not in group for calculating rulers on group view
* (foxriver76) prevent showing widget in a group after it is already cut out
* (foxriver76) prevent usage of widgets which are not in a group for calculating rulers on group view

### 2.9.12 (2024-01-04)
* (foxriver76) optimized copy/paste/cut in groups
Expand All @@ -453,7 +454,7 @@ npm run start
* (foxriver76) remove accidentally added script file, which lead to crash

### 2.9.9 (2024-01-01)
* (foxriver76) allow to import views without attribute `activeWidgets`
* (foxriver76) allow importing views without attribute `activeWidgets`
* (foxriver76) make BasicBulb behave more like its old version
* (foxriver76) fixed issue that data of different widget is displayed in edit mode
* (foxriver76) fixed issue that every state update is used for visibility calculation
Expand Down
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 30 additions & 10 deletions packages/iobroker.vis-2/src/src/Toolbar/ViewsManager/Folder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ import { FaFolder as FolderClosedIcon, FaFolderOpen as FolderOpenedIcon } from '
import { Utils, I18n } from '@iobroker/adapter-react-v5';
import type { VisTheme } from '@iobroker/types-vis-2';
import commonStyles from '@/Utils/styles';
import { store } from '../../Store';
import { store } from '@/Store';

const styles: Record<string, any> = {
viewManageBlock: (theme: VisTheme) => theme.classes.viewManageBlock,
viewManageButtonActions: (theme: VisTheme) => theme.classes.viewManageButtonActions,
folderName: {
marginLeft: 8,
fontWeight: 'bold',
cursor: 'pointer',
},
icon: (theme: VisTheme) => ({
cursor: 'grab',
Expand Down Expand Up @@ -143,7 +144,13 @@ const Folder: React.FC<FolderProps> = props => {
return <Box
component="div"
ref={drop}
sx={Utils.getStyle(props.theme, styles.root, styles.viewManageBlock, props.isDragging && !canDrop && styles.noDrop, props.isDragging && canDrop && styles.rootCanDrop)}
sx={Utils.getStyle(
props.theme,
styles.root,
styles.viewManageBlock,
props.isDragging && !canDrop && styles.noDrop,
props.isDragging && canDrop && styles.rootCanDrop,
)}
>
<Box
component="div"
Expand All @@ -153,32 +160,45 @@ const Folder: React.FC<FolderProps> = props => {
>
{props.foldersCollapsed.includes(props.folder.id)
? <FolderClosedIcon
fontSize="small"
fontSize="large"
onClick={() => {
const foldersCollapsed = JSON.parse(JSON.stringify(props.foldersCollapsed));
const foldersCollapsed: string[] = JSON.parse(JSON.stringify(props.foldersCollapsed));
foldersCollapsed.splice(foldersCollapsed.indexOf(props.folder.id), 1);
props.setFoldersCollapsed(foldersCollapsed);
window.localStorage.setItem('ViewsManager.foldersCollapsed', JSON.stringify(foldersCollapsed));
}}
/>
: <FolderOpenedIcon
fontSize="small"
fontSize="large"
onClick={() => {
const foldersCollapsed = JSON.parse(JSON.stringify(props.foldersCollapsed));
const foldersCollapsed: string[] = JSON.parse(JSON.stringify(props.foldersCollapsed));
foldersCollapsed.push(props.folder.id);
props.setFoldersCollapsed(foldersCollapsed);
window.localStorage.setItem('ViewsManager.foldersCollapsed', JSON.stringify(foldersCollapsed));
}}
/>}
</Box>
<span style={styles.folderName}>{props.folder.name}</span>
<span
style={styles.folderName}
onClick={() => {
const foldersCollapsed: string[] = JSON.parse(JSON.stringify(props.foldersCollapsed));
const index = foldersCollapsed.indexOf(props.folder.id);
if (index !== -1) {
foldersCollapsed.splice(index, 1);
} else {
foldersCollapsed.push(props.folder.id);
}
props.setFoldersCollapsed(foldersCollapsed);
window.localStorage.setItem('ViewsManager.foldersCollapsed', JSON.stringify(foldersCollapsed));
}}
>
{props.folder.name}
</span>
<Box component="span" sx={styles.viewManageButtonActions}>
{props.editMode ? <Tooltip title={I18n.t('Add view')} componentsProps={{ popper: { sx: commonStyles.tooltip } }}>
<IconButton
size="small"
onClick={() => {
props.showDialog('add', null, props.folder.id);
}}
onClick={() => props.showDialog('add', null, props.folder.id)}
>
<AddIcon />
</IconButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,13 +184,15 @@ const ViewsManager: React.FC<ViewsManagerProps> = props => {
isDragging={isDragging}
folder={folder}
theme={props.theme}
editMode={props.editMode}
setFolderDialog={setFolderDialog}
setFolderDialogName={setFolderDialogName}
setFolderDialogId={setFolderDialogId}
setFolderDialogParentId={setFolderDialogParentId}
moveFolder={moveFolder}
foldersCollapsed={foldersCollapsed}
setFoldersCollapsed={setFoldersCollapsed}
showDialog={props.showDialog}
/>
</div>
{foldersCollapsed.includes(folder.id) ? null : <div style={{ paddingLeft: 10 }}>
Expand All @@ -200,7 +202,12 @@ const ViewsManager: React.FC<ViewsManagerProps> = props => {
</div>);
};

return <IODialog open={props.open} onClose={props.onClose} title="Manage views" closeTitle="Close">
return <IODialog
open={props.open}
onClose={props.onClose}
title="Manage views"
closeTitle="Close"
>
<div style={styles.dialog}>
<DndProvider backend={isTouchDevice() ? TouchBackend : HTML5Backend}>
<DndPreview />
Expand Down
169 changes: 7 additions & 162 deletions packages/iobroker.vis-2/src/src/Vis/Widgets/Basic/BasicImage.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,10 @@
import React from 'react';
import BasicImageGeneric, { type RxDataBasicImageGeneric } from './BasicImageGeneric';

import { Icon } from '@iobroker/adapter-react-v5';
import type { GetRxDataFromWidget, RxRenderWidgetProps } from '@iobroker/types-vis-2';
import VisRxWidget from '@/Vis/visRxWidget';

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

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

private readonly imageRef: React.RefObject<HTMLImageElement>;

private hashInstalled = false;

private onWakeUpInstalled = false;

private startedInterval = 0;

constructor(props: RxRenderWidgetProps) {
// @ts-expect-error refactor types to extend from parent types
super(props);
this.imageRef = React.createRef<HTMLImageElement>();
}
interface RxData extends RxDataBasicImageGeneric {
src: string;
}

export default class BasicImage extends BasicImageGeneric<RxData> {
/**
* Returns the widget info which is rendered in the edit mode
*/
Expand Down Expand Up @@ -79,20 +60,6 @@ export default class BasicImage extends VisRxWidget<RxData> {
} as const;
}

async componentDidMount(): Promise<void> {
super.componentDidMount();
this.onPropertyUpdate();
}

async componentWillUnmount(): Promise<void> {
super.componentWillUnmount();
this.refreshInterval && clearInterval(this.refreshInterval);
if (this.hashInstalled) {
this.hashInstalled = false;
window.removeEventListener('hashchange', this.onHashChange);
}
}

/**
* Enables calling widget info on the class instance itself
*/
Expand All @@ -101,129 +68,7 @@ export default class BasicImage extends VisRxWidget<RxData> {
return BasicImage.getWidgetInfo();
}

onHashChange = () => {
if (this.props.context.activeView === this.props.view) {
this.refreshImage();
}
};

refreshImage() {
if (this.imageRef.current) {
if (this.state.rxData.refreshWithNoQuery === true) {
this.imageRef.current.src = this.state.rxData.src;
} else {
this.imageRef.current.src = `${this.state.rxData.src}${this.state.rxData.src.includes('?') ? '&' : '?'}_refts=${Date.now()}`;
}
}
}

static isHidden(el: HTMLElement) {
return el.offsetParent === null;
}

static getParents(el: HTMLImageElement): HTMLElement[] {
const els = [];
let pel = el as HTMLElement;
do {
pel = pel.parentNode as HTMLElement;
els.unshift(el);
} while (pel);

return els;
}

onPropertyUpdate() {
const src = this.state.rxData.src || '';
const refreshInterval = Number(this.state.rxData.refreshInterval) || 0;
const refreshOnViewChange = this.state.rxData.refreshOnViewChange === true;
const refreshOnWakeUp = this.state.rxData.refreshOnWakeUp === true;

if (src) {
if (refreshOnViewChange) {
// install on view changed handler
if (!this.hashInstalled) {
this.hashInstalled = true;
window.addEventListener('hashchange', this.onHashChange);
}
} else if (this.hashInstalled) {
this.hashInstalled = false;
window.removeEventListener('hashchange', this.onHashChange);
}

if (refreshInterval > 0) {
if (this.startedInterval !== refreshInterval) {
this.startedInterval = refreshInterval;
this.refreshInterval && clearInterval(this.refreshInterval);
this.refreshInterval = null;
}
// install refresh handler
this.refreshInterval = this.refreshInterval || setInterval(() => {
if (this.imageRef.current && !BasicImage.isHidden(this.imageRef.current as HTMLElement)) {
const parents = BasicImage.getParents(this.imageRef.current).filter(el => BasicImage.isHidden(el));
if (!parents.length || parents[0].tagName === 'BODY' || parents[0].id === 'materialdesign-vuetify-container') {
this.refreshImage();
}
}
}, refreshInterval);
} else if (this.refreshInterval) {
this.startedInterval = 0;
clearInterval(this.refreshInterval);
this.refreshInterval = null;
}

if (refreshOnWakeUp) {
// install on wake-up handler
if (!this.onWakeUpInstalled) {
this.onWakeUpInstalled = true;
window.vis.onWakeUp(this.onHashChange, this.props.id);
}
} else if (this.onWakeUpInstalled) {
this.onWakeUpInstalled = false;
window.vis.onWakeUp(null, this.props.id);
}
}
}

onRxDataChanged() {
this.onPropertyUpdate();
}

/**
* Renders the widget
*
* @param props props passed to the parent classes render method
*/
renderWidgetBody(props: RxRenderWidgetProps): React.JSX.Element | null {
super.renderWidgetBody(props);

if (this.props.editMode) {
props.overlayClassNames.push('vis-editmode-helper');
}

const style: React.CSSProperties = {
padding: 0,
margin: 0,
border: 0,
width: '100%',
height: 'auto',
};
if (this.state.rxData.stretch) {
style.height = '100%';
}
if (!this.state.rxData.allowUserInteractions) {
style.touchAction = 'none';
style.userSelect = 'none';
}

const src = this.state.rxData.src || '';

return src ? <div className="vis-widget-body" style={{ overflow: 'hidden' }}>
<Icon
style={style}
ref={this.imageRef}
src={this.state.rxData.src}
alt={this.props.id}
/>
</div> : null;
getImage(): string {
return this.state.rxData.src || '';
}
}
Loading

0 comments on commit 4e24309

Please sign in to comment.