Iterate with new buttons and resize locking
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
d36fafd0c6
commit
a6c81a903c
8 changed files with 173 additions and 89 deletions
|
@ -109,20 +109,55 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSummaryCard_appsGroup {
|
.mx_RoomSummaryCard_appsGroup {
|
||||||
.mx_RoomSummaryCard_widgetRow {
|
.mx_RoomSummaryCard_Button {
|
||||||
margin: 0;
|
// this button is special so we have to override some of the original styling
|
||||||
display: flex;
|
// as we will be applying it in its children
|
||||||
|
padding: 0;
|
||||||
|
height: auto;
|
||||||
|
color: $tertiary-fg-color;
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_icon_app {
|
||||||
|
padding: 8px 48px 8px 12px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.mx_BaseAvatar_image {
|
||||||
|
vertical-align: top;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_RoomSummaryCard_app_pinToggle,
|
.mx_RoomSummaryCard_app_pinToggle,
|
||||||
.mx_RoomSummaryCard_app_options {
|
.mx_RoomSummaryCard_app_options {
|
||||||
position: relative;
|
position: absolute;
|
||||||
height: 16px;
|
top: 0;
|
||||||
width: 16px;
|
height: 100%; // to give bigger interactive zone
|
||||||
padding: 8px;
|
width: 24px;
|
||||||
border-radius: 8px;
|
padding: 10px 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 24px; // prevent flexbox crushing
|
||||||
|
|
||||||
|
.mx_AccessibleTooltipButton_container {
|
||||||
|
// TODO
|
||||||
|
position: absolute;
|
||||||
|
top: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(141, 151, 165, 0.1);
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
top: 6px; // equal to top-margin of parent
|
||||||
|
left: 0;
|
||||||
|
border-radius: 12px;
|
||||||
|
background-color: rgba(141, 151, 165, 0.1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
|
@ -138,36 +173,40 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSummaryCard_app_pinToggle {
|
.mx_RoomSummaryCard_app_pinToggle {
|
||||||
|
right: 24px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
mask-image: url('$(res)/img/element-icons/room/pin-upright.svg');
|
mask-image: url('$(res)/img/element-icons/room/pin-upright.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_RoomSummaryCard_app_pinned {
|
|
||||||
&::before {
|
|
||||||
background-color: $accent-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomSummaryCard_app_options {
|
.mx_RoomSummaryCard_app_options {
|
||||||
|
right: 48px;
|
||||||
|
display: none;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
mask-image: url('$(res)/img/element-icons/room/ellipsis.svg');
|
mask-image: url('$(res)/img/element-icons/room/ellipsis.svg');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomSummaryCard_Button {
|
&.mx_RoomSummaryCard_Button_pinned {
|
||||||
padding: 6px 24px 6px 12px;
|
&::after {
|
||||||
color: $tertiary-fg-color;
|
opacity: 0.2;
|
||||||
flex: 1;
|
}
|
||||||
|
|
||||||
span {
|
.mx_RoomSummaryCard_app_pinToggle::before {
|
||||||
color: $primary-fg-color;
|
background-color: $accent-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_BaseAvatar_image {
|
&:hover {
|
||||||
vertical-align: top;
|
.mx_RoomSummaryCard_icon_app {
|
||||||
margin-right: 12px;
|
padding-right: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_app_options {
|
||||||
|
display: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
|
@ -175,7 +214,8 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
top: 6px; // re-align based on the height change
|
top: 8px; // re-align based on the height change
|
||||||
|
pointer-events: none; // pass through to the real button
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -276,7 +276,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private checkWidgets = (room) => {
|
private checkWidgets = (room) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
hasPinnedWidgets: WidgetStore.instance.getApps(room, true).length > 0,
|
hasPinnedWidgets: WidgetStore.instance.getPinnedApps(room.roomId).length > 0,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
|
||||||
tooltip?: React.ReactNode;
|
tooltip?: React.ReactNode;
|
||||||
tooltipClassName?: string;
|
tooltipClassName?: string;
|
||||||
forceHide?: boolean;
|
forceHide?: boolean;
|
||||||
|
yOffset?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -63,12 +64,13 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const {title, tooltip, children, tooltipClassName, forceHide, ...props} = this.props;
|
const {title, tooltip, children, tooltipClassName, forceHide, yOffset, ...props} = this.props;
|
||||||
|
|
||||||
const tip = this.state.hover ? <Tooltip
|
const tip = this.state.hover ? <Tooltip
|
||||||
className="mx_AccessibleTooltipButton_container"
|
className="mx_AccessibleTooltipButton_container"
|
||||||
tooltipClassName={classNames("mx_AccessibleTooltipButton_tooltip", tooltipClassName)}
|
tooltipClassName={classNames("mx_AccessibleTooltipButton_tooltip", tooltipClassName)}
|
||||||
label={tooltip || title}
|
label={tooltip || title}
|
||||||
|
yOffset={yOffset}
|
||||||
/> : <div />;
|
/> : <div />;
|
||||||
return (
|
return (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
|
|
@ -36,6 +36,7 @@ interface IProps {
|
||||||
// the react element to put into the tooltip
|
// the react element to put into the tooltip
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
forceOnRight?: boolean;
|
forceOnRight?: boolean;
|
||||||
|
yOffset?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Tooltip extends React.Component<IProps> {
|
export default class Tooltip extends React.Component<IProps> {
|
||||||
|
@ -46,6 +47,7 @@ export default class Tooltip extends React.Component<IProps> {
|
||||||
|
|
||||||
public static readonly defaultProps = {
|
public static readonly defaultProps = {
|
||||||
visible: true,
|
visible: true,
|
||||||
|
yOffset: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a wrapper for the tooltip outside the parent and attach it to the body element
|
// Create a wrapper for the tooltip outside the parent and attach it to the body element
|
||||||
|
@ -82,9 +84,9 @@ export default class Tooltip extends React.Component<IProps> {
|
||||||
offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT);
|
offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
style.top = (parentBox.top - 2) + window.pageYOffset + offset;
|
style.top = (parentBox.top - 2 + this.props.yOffset) + window.pageYOffset + offset;
|
||||||
if (!this.props.forceOnRight && parentBox.right > window.innerWidth / 2) {
|
if (!this.props.forceOnRight && parentBox.right > window.innerWidth / 2) {
|
||||||
style.right = window.innerWidth - parentBox.right - window.pageXOffset - 8;
|
style.right = window.innerWidth - parentBox.right - window.pageXOffset - 16;
|
||||||
} else {
|
} else {
|
||||||
style.left = parentBox.right + window.pageXOffset + 6;
|
style.left = parentBox.right + window.pageXOffset + 6;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ import { E2EStatus } from "../../../utils/ShieldUtils";
|
||||||
import RoomContext from "../../../contexts/RoomContext";
|
import RoomContext from "../../../contexts/RoomContext";
|
||||||
import {UIFeature} from "../../../settings/UIFeature";
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
import {ContextMenuButton} from "../../../accessibility/context_menu/ContextMenuButton";
|
import {ContextMenuButton} from "../../../accessibility/context_menu/ContextMenuButton";
|
||||||
import {ChevronFace, useContextMenu} from "../../structures/ContextMenu";
|
import {ChevronFace, ContextMenuTooltipButton, useContextMenu} from "../../structures/ContextMenu";
|
||||||
import WidgetContextMenu from "../context_menus/WidgetContextMenu";
|
import WidgetContextMenu from "../context_menus/WidgetContextMenu";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
@ -70,11 +70,11 @@ const Button: React.FC<IButtonProps> = ({ children, className, onClick }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useWidgets = (room: Room) => {
|
export const useWidgets = (room: Room) => {
|
||||||
const [apps, setApps] = useState<IApp[]>(WidgetStore.instance.getApps(room));
|
const [apps, setApps] = useState<IApp[]>(WidgetStore.instance.getApps(room.roomId));
|
||||||
|
|
||||||
const updateApps = useCallback(() => {
|
const updateApps = useCallback(() => {
|
||||||
// Copy the array so that we always trigger a re-render, as some updates mutate the array of apps/settings
|
// Copy the array so that we always trigger a re-render, as some updates mutate the array of apps/settings
|
||||||
setApps([...WidgetStore.instance.getApps(room)]);
|
setApps([...WidgetStore.instance.getApps(room.roomId)]);
|
||||||
}, [room]);
|
}, [room]);
|
||||||
|
|
||||||
useEffect(updateApps, [room]);
|
useEffect(updateApps, [room]);
|
||||||
|
@ -130,34 +130,39 @@ const AppRow: React.FC<IAppRowProps> = ({ app }) => {
|
||||||
pinTitle = isPinned ? _t("Unpin") : _t("Pin");
|
pinTitle = isPinned ? _t("Unpin") : _t("Pin");
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_RoomSummaryCard_widgetRow" ref={handle}>
|
const classes = classNames("mx_BaseCard_Button mx_RoomSummaryCard_Button", {
|
||||||
|
mx_RoomSummaryCard_Button_pinned: isPinned,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <div className={classes} ref={handle}>
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_BaseCard_Button mx_RoomSummaryCard_Button mx_RoomSummaryCard_icon_app"
|
className="mx_RoomSummaryCard_icon_app"
|
||||||
onClick={onOpenWidgetClick}
|
onClick={onOpenWidgetClick}
|
||||||
// only show a tooltip if the widget is pinned
|
// only show a tooltip if the widget is pinned
|
||||||
title={isPinned ? _t("Unpin a widget to view it in this panel") : ""}
|
title={isPinned ? _t("Unpin a widget to view it in this panel") : ""}
|
||||||
forceHide={!isPinned}
|
forceHide={!isPinned}
|
||||||
disabled={isPinned}
|
disabled={isPinned}
|
||||||
|
yOffset={-48}
|
||||||
>
|
>
|
||||||
<WidgetAvatar app={app} />
|
<WidgetAvatar app={app} />
|
||||||
<span>{name}</span>
|
<span>{name}</span>
|
||||||
{ subtitle }
|
{ subtitle }
|
||||||
</AccessibleTooltipButton>
|
</AccessibleTooltipButton>
|
||||||
|
|
||||||
<AccessibleTooltipButton
|
<ContextMenuTooltipButton
|
||||||
className={classNames("mx_RoomSummaryCard_app_pinToggle", {
|
|
||||||
mx_RoomSummaryCard_app_pinned: isPinned,
|
|
||||||
})}
|
|
||||||
onClick={togglePin}
|
|
||||||
title={pinTitle}
|
|
||||||
disabled={cannotPin}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ContextMenuButton
|
|
||||||
className="mx_RoomSummaryCard_app_options"
|
className="mx_RoomSummaryCard_app_options"
|
||||||
isExpanded={menuDisplayed}
|
isExpanded={menuDisplayed}
|
||||||
onClick={openMenu}
|
onClick={openMenu}
|
||||||
label={_t("Options")}
|
title={_t("Options")}
|
||||||
|
yOffset={-24}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AccessibleTooltipButton
|
||||||
|
className="mx_RoomSummaryCard_app_pinToggle"
|
||||||
|
onClick={togglePin}
|
||||||
|
title={pinTitle}
|
||||||
|
disabled={cannotPin}
|
||||||
|
yOffset={-24}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{ contextMenu }
|
{ contextMenu }
|
||||||
|
|
|
@ -93,8 +93,9 @@ export default class AppsDrawer extends React.Component {
|
||||||
onResizeStop: () => {
|
onResizeStop: () => {
|
||||||
this._resizeContainer.classList.remove("mx_AppsDrawer_resizing");
|
this._resizeContainer.classList.remove("mx_AppsDrawer_resizing");
|
||||||
// persist to localStorage
|
// persist to localStorage
|
||||||
|
console.log("@@ _saveResizerPreferences");
|
||||||
localStorage.setItem(this._getStorageKey(), JSON.stringify([
|
localStorage.setItem(this._getStorageKey(), JSON.stringify([
|
||||||
this._getIdString(),
|
this._getAppsHash(this.state.apps),
|
||||||
...this.state.apps.slice(1).map((_, i) => this.resizer.forHandleAt(i).size),
|
...this.state.apps.slice(1).map((_, i) => this.resizer.forHandleAt(i).size),
|
||||||
]));
|
]));
|
||||||
},
|
},
|
||||||
|
@ -121,26 +122,39 @@ export default class AppsDrawer extends React.Component {
|
||||||
|
|
||||||
_getStorageKey = () => `mx_apps_drawer-${this.props.room.roomId}`;
|
_getStorageKey = () => `mx_apps_drawer-${this.props.room.roomId}`;
|
||||||
|
|
||||||
_getIdString = () => this.state.apps.map(app => app.id).join("~");
|
_getAppsHash = (apps) => apps.map(app => app.id).join("~");
|
||||||
|
|
||||||
_loadResizerPreferences = () => { // TODO call this when changing pinned apps
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
if (this._getAppsHash(this.state.apps) !== this._getAppsHash(prevState.apps)) {
|
||||||
|
this._loadResizerPreferences();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadResizerPreferences = () => {
|
||||||
console.log("@@ _loadResizerPreferences");
|
console.log("@@ _loadResizerPreferences");
|
||||||
try {
|
try {
|
||||||
const [idString, ...sizes] = JSON.parse(localStorage.getItem(this._getStorageKey()));
|
const [idString, ...sizes] = JSON.parse(localStorage.getItem(this._getStorageKey()));
|
||||||
// format: [idString: string, ...percentages: string];
|
// format: [idString: string, ...percentages: string];
|
||||||
if (this._getIdString() !== idString) return;
|
// TODO determine the exact behaviour we want for layout changing when pinning/unpinning
|
||||||
sizes.forEach((size, i) => {
|
if (this._getAppsHash() === idString || true) {
|
||||||
const distributor = this.resizer.forHandleAt(i);
|
sizes.forEach((size, i) => {
|
||||||
distributor.size = size;
|
const distributor = this.resizer.forHandleAt(i);
|
||||||
distributor.finish();
|
if (distributor) {
|
||||||
});
|
distributor.size = size;
|
||||||
|
distributor.finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
// this is expected
|
||||||
this.state.apps.slice(1).forEach((_, i) => {
|
}
|
||||||
const distributor = this.resizer.forHandleAt(i);
|
|
||||||
distributor.item.clearSize();
|
if (this.state.apps) {
|
||||||
distributor.finish();
|
console.log("@@ full relaxation");
|
||||||
});
|
const distributors = this.resizer.getDistributors();
|
||||||
|
distributors.forEach(d => d.item.clearSize());
|
||||||
|
distributors.forEach(d => d.finish());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -162,12 +176,12 @@ export default class AppsDrawer extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_getApps = () => WidgetStore.instance.getApps(this.props.room, true);
|
_getApps = () => WidgetStore.instance.getPinnedApps(this.props.room.roomId);
|
||||||
|
|
||||||
_updateApps = () => {
|
_updateApps = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
apps: this._getApps(),
|
apps: this._getApps(),
|
||||||
}, this._loadResizerPreferences);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_launchManageIntegrations() {
|
_launchManageIntegrations() {
|
||||||
|
|
|
@ -163,16 +163,20 @@ export default class Resizer<C extends IConfig = IConfig> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private onResize = throttle(() => {
|
private onResize = throttle(() => {
|
||||||
const distributors = this.getResizeHandles().map(handle => {
|
const distributors = this.getDistributors();
|
||||||
const {distributor} = this.createSizerAndDistributor(<HTMLDivElement>handle);
|
|
||||||
return distributor;
|
|
||||||
});
|
|
||||||
|
|
||||||
// relax all items if they had any overconstrained flexboxes
|
// relax all items if they had any overconstrained flexboxes
|
||||||
distributors.forEach(d => d.start());
|
distributors.forEach(d => d.start());
|
||||||
distributors.forEach(d => d.finish());
|
distributors.forEach(d => d.finish());
|
||||||
}, 100, {trailing: true, leading: true});
|
}, 100, {trailing: true, leading: true});
|
||||||
|
|
||||||
|
public getDistributors = () => {
|
||||||
|
return this.getResizeHandles().map(handle => {
|
||||||
|
const {distributor} = this.createSizerAndDistributor(<HTMLDivElement>handle);
|
||||||
|
return distributor;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private createSizerAndDistributor(
|
private createSizerAndDistributor(
|
||||||
resizeHandle: HTMLDivElement,
|
resizeHandle: HTMLDivElement,
|
||||||
): {sizer: Sizer, distributor: FixedDistributor<any>} {
|
): {sizer: Sizer, distributor: FixedDistributor<any>} {
|
||||||
|
@ -186,6 +190,7 @@ export default class Resizer<C extends IConfig = IConfig> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getResizeHandles() {
|
private getResizeHandles() {
|
||||||
|
if (!this.container.children) return [];
|
||||||
return Array.from(this.container.children).filter(el => {
|
return Array.from(this.container.children).filter(el => {
|
||||||
return this.isResizeHandle(<HTMLElement>el);
|
return this.isResizeHandle(<HTMLElement>el);
|
||||||
}) as HTMLElement[];
|
}) as HTMLElement[];
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
|
||||||
import defaultDispatcher from "../dispatcher/dispatcher";
|
import defaultDispatcher from "../dispatcher/dispatcher";
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
import WidgetEchoStore from "../stores/WidgetEchoStore";
|
import WidgetEchoStore from "../stores/WidgetEchoStore";
|
||||||
|
import RoomViewStore from "../stores/RoomViewStore";
|
||||||
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
|
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
|
||||||
import WidgetUtils from "../utils/WidgetUtils";
|
import WidgetUtils from "../utils/WidgetUtils";
|
||||||
import {SettingLevel} from "../settings/SettingLevel";
|
import {SettingLevel} from "../settings/SettingLevel";
|
||||||
|
@ -48,11 +49,6 @@ interface IRoomWidgets {
|
||||||
|
|
||||||
export const MAX_PINNED = 3;
|
export const MAX_PINNED = 3;
|
||||||
|
|
||||||
// TODO change order to be order that they were pinned
|
|
||||||
// TODO HARD cap at 3, truncating if needed
|
|
||||||
// TODO call finish more proactively to lock things in
|
|
||||||
// TODO auto-open the appsDrawer for the room when widgets get pinned
|
|
||||||
|
|
||||||
// TODO consolidate WidgetEchoStore into this
|
// TODO consolidate WidgetEchoStore into this
|
||||||
// TODO consolidate ActiveWidgetStore into this
|
// TODO consolidate ActiveWidgetStore into this
|
||||||
export default class WidgetStore extends AsyncStoreWithClient<IState> {
|
export default class WidgetStore extends AsyncStoreWithClient<IState> {
|
||||||
|
@ -75,7 +71,7 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
|
||||||
private initRoom(roomId: string) {
|
private initRoom(roomId: string) {
|
||||||
if (!this.roomMap.has(roomId)) {
|
if (!this.roomMap.has(roomId)) {
|
||||||
this.roomMap.set(roomId, {
|
this.roomMap.set(roomId, {
|
||||||
pinned: {},
|
pinned: {}, // ordered
|
||||||
widgets: [],
|
widgets: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -163,25 +159,24 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
|
||||||
|
|
||||||
public isPinned(widgetId: string) {
|
public isPinned(widgetId: string) {
|
||||||
const roomId = this.getRoomId(widgetId);
|
const roomId = this.getRoomId(widgetId);
|
||||||
const roomInfo = this.getRoom(roomId);
|
return !!this.getPinnedApps(roomId).find(w => w.id === widgetId);
|
||||||
|
|
||||||
let pinned = roomInfo && roomInfo.pinned[widgetId];
|
|
||||||
// Jitsi widgets should be pinned by default
|
|
||||||
const widget = this.widgetMap.get(widgetId);
|
|
||||||
if (pinned === undefined && WidgetType.JITSI.matches(widget?.type)) pinned = true;
|
|
||||||
return pinned;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public canPin(widgetId: string) {
|
public canPin(widgetId: string) {
|
||||||
const roomId = this.getRoomId(widgetId);
|
const roomId = this.getRoomId(widgetId);
|
||||||
const roomInfo = this.getRoom(roomId);
|
return this.getPinnedApps(roomId).length < MAX_PINNED;
|
||||||
return roomInfo && Object.keys(roomInfo.pinned).filter(k => {
|
|
||||||
return roomInfo.pinned[k] && roomInfo.widgets.some(app => app.id === k);
|
|
||||||
}).length < MAX_PINNED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public pinWidget(widgetId: string) {
|
public pinWidget(widgetId: string) {
|
||||||
this.setPinned(widgetId, true);
|
this.setPinned(widgetId, true);
|
||||||
|
|
||||||
|
// Show the apps drawer upon the user pinning a widget
|
||||||
|
if (RoomViewStore.getRoomId() === this.getRoomId(widgetId)) {
|
||||||
|
defaultDispatcher.dispatch({
|
||||||
|
action: "appsDrawer",
|
||||||
|
show: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public unpinWidget(widgetId: string) {
|
public unpinWidget(widgetId: string) {
|
||||||
|
@ -192,6 +187,10 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
|
||||||
const roomId = this.getRoomId(widgetId);
|
const roomId = this.getRoomId(widgetId);
|
||||||
const roomInfo = this.getRoom(roomId);
|
const roomInfo = this.getRoom(roomId);
|
||||||
if (!roomInfo) return;
|
if (!roomInfo) return;
|
||||||
|
if (roomInfo.pinned[widgetId] === false && value) {
|
||||||
|
// delete this before write to maintain the correct object insertion order
|
||||||
|
delete roomInfo.pinned[widgetId];
|
||||||
|
}
|
||||||
roomInfo.pinned[widgetId] = value;
|
roomInfo.pinned[widgetId] = value;
|
||||||
|
|
||||||
// Clean up the pinned record
|
// Clean up the pinned record
|
||||||
|
@ -206,13 +205,30 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
|
||||||
this.emit(UPDATE_EVENT);
|
this.emit(UPDATE_EVENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getApps(room: Room, pinned?: boolean): IApp[] {
|
public getPinnedApps(roomId): IApp[] {
|
||||||
const roomInfo = this.getRoom(room.roomId);
|
// returns the apps in the order they were pinned with, up to the maximum
|
||||||
|
const roomInfo = this.getRoom(roomId);
|
||||||
if (!roomInfo) return [];
|
if (!roomInfo) return [];
|
||||||
if (pinned) {
|
|
||||||
return roomInfo.widgets.filter(app => this.isPinned(app.id));
|
// Show Jitsi widgets even if the user already had the maximum pinned, instead of their latest pinned,
|
||||||
|
// except if the user already explicitly unpinned the Jitsi widget
|
||||||
|
const priorityWidget = roomInfo.widgets.find(widget => {
|
||||||
|
return roomInfo.pinned[widget.id] === undefined && WidgetType.JITSI.matches(widget.type);
|
||||||
|
});
|
||||||
|
|
||||||
|
const order = Object.keys(roomInfo.pinned).filter(k => roomInfo.pinned[k]);
|
||||||
|
let apps = order.map(wId => this.widgetMap.get(wId)).filter(Boolean);
|
||||||
|
apps = apps.slice(0, priorityWidget ? MAX_PINNED - 1 : MAX_PINNED);
|
||||||
|
if (priorityWidget) {
|
||||||
|
apps.push(priorityWidget);
|
||||||
}
|
}
|
||||||
return roomInfo.widgets;
|
|
||||||
|
return apps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getApps(roomId: string): IApp[] {
|
||||||
|
const roomInfo = this.getRoom(roomId);
|
||||||
|
return roomInfo?.widgets || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public doesRoomHaveConference(room: Room): boolean {
|
public doesRoomHaveConference(room: Room): boolean {
|
||||||
|
|
Loading…
Reference in a new issue