Skip to content

Commit

Permalink
Unavailable overlay for sidebar forms and temp panels on nav
Browse files Browse the repository at this point in the history
  • Loading branch information
fungairino committed Apr 26, 2024
1 parent 90f8b48 commit b109943
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 6 deletions.
29 changes: 26 additions & 3 deletions src/sidebar/ConnectedSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React, { useMemo } from "react";
import React, { useCallback, useMemo } from "react";
import {
addListener,
removeListener,
Expand All @@ -40,14 +40,20 @@ import DefaultPanel from "@/sidebar/DefaultPanel";
import { MOD_LAUNCHER } from "@/store/sidebar/constants";
import { ensureExtensionPointsInstalled } from "@/contentScript/messenger/api";
import { getReservedSidebarEntries } from "@/contentScript/messenger/strict/api";
import { getConnectedTarget } from "@/sidebar/connectedTarget";
import {
getConnectedTabIdMv3,
getConnectedTarget,
} from "@/sidebar/connectedTarget";
import useAsyncEffect from "use-async-effect";
import activateLinkClickHandler from "@/activation/activateLinkClickHandler";
import addFormPanel from "@/store/sidebar/thunks/addFormPanel";
import addTemporaryPanel from "@/store/sidebar/thunks/addTemporaryPanel";
import removeTemporaryPanel from "@/store/sidebar/thunks/removeTemporaryPanel";
import { type AsyncDispatch } from "@/sidebar/store";
import useEventListener from "@/hooks/useEventListener";
import { WebNavigation } from "webextension-polyfill";
import OnBeforeNavigateDetailsType = WebNavigation.OnBeforeNavigateDetailsType;
import { isMV3 } from "@/mv3/api";

/**
* Listeners to update the Sidebar's Redux state upon receiving messages from the contentScript.
Expand Down Expand Up @@ -98,7 +104,21 @@ const ConnectedSidebar: React.VFC = () => {
const listener = useConnectedListener();
const sidebarIsEmpty = useSelector(selectIsSidebarEmpty);

// `useAsyncEffect` will run once on component mount since listener and formsRef don't change on renders.
const navigationListener = useCallback(
(details: OnBeforeNavigateDetailsType) => {
const { frameId, tabId } = details;
if (isMV3()) {
const connectedTabId = getConnectedTabIdMv3();
if (tabId === connectedTabId && frameId === 0) {
console.log("navigationListener:connectedTabId", connectedTabId);
dispatch(sidebarSlice.actions.markTemporaryPanelsAsUnavailable());
}
}
},
[dispatch],
);

// `useAsyncEffect` will run once on component mount since listeners and formsRef don't change on renders.
// We could instead consider moving the initial panel logic to SidebarApp.tsx and pass the entries as the
// initial state to the sidebarSlice reducer.
useAsyncEffect(async () => {
Expand Down Expand Up @@ -132,10 +152,13 @@ const ConnectedSidebar: React.VFC = () => {
// To avoid races with panel registration, listen after reserving the initial panels.
addListener(listener);

browser.webNavigation.onBeforeNavigate.addListener(navigationListener);

Check failure on line 155 in src/sidebar/ConnectedSidebar.tsx

View workflow job for this annotation

GitHub Actions / test

SidebarApp › renders not connected

TypeError: Cannot read properties of undefined (reading 'addListener') at addListener (src/sidebar/ConnectedSidebar.tsx:155:44)

Check failure on line 155 in src/sidebar/ConnectedSidebar.tsx

View workflow job for this annotation

GitHub Actions / test

SidebarApp › renders connected partner view

TypeError: Cannot read properties of undefined (reading 'addListener') at addListener (src/sidebar/ConnectedSidebar.tsx:155:44)

Check failure on line 155 in src/sidebar/ConnectedSidebar.tsx

View workflow job for this annotation

GitHub Actions / test

SidebarApp › renders

TypeError: Cannot read properties of undefined (reading 'addListener') at addListener (src/sidebar/ConnectedSidebar.tsx:155:44)

Check failure on line 155 in src/sidebar/ConnectedSidebar.tsx

View workflow job for this annotation

GitHub Actions / test

SidebarBody › it renders with anonymous user

TypeError: Cannot read properties of undefined (reading 'addListener') at addListener (src/sidebar/ConnectedSidebar.tsx:155:44)

Check failure on line 155 in src/sidebar/ConnectedSidebar.tsx

View workflow job for this annotation

GitHub Actions / test

SidebarBody › it renders with authenticated user

TypeError: Cannot read properties of undefined (reading 'addListener') at addListener (src/sidebar/ConnectedSidebar.tsx:155:44)

return () => {
// NOTE: we don't need to cancel any outstanding forms on unmount because the FormTransformer is set up to watch
// for PANEL_HIDING_EVENT. (and the only time this SidebarApp would unmount is if the sidebar was closing)
removeListener(listener);
browser.webNavigation.onBeforeNavigate.removeListener(navigationListener);
};
// Excluding showModLauncher from deps. The flags detect shouldn't change after initial mount. And if they somehow do,
// we don't want to attempt to change mod launcher panel visibility after initial mount.
Expand Down
1 change: 1 addition & 0 deletions src/sidebar/Tabs.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

.tabContainer {
flex-wrap: nowrap;
position: relative;
}

.tabWrapper {
Expand Down
2 changes: 2 additions & 0 deletions src/sidebar/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import { useHideEmptySidebar } from "@/sidebar/useHideEmptySidebar";
import removeTemporaryPanel from "@/store/sidebar/thunks/removeTemporaryPanel";
import { type AsyncDispatch } from "@/sidebar/store";
import useOnMountOnly from "@/hooks/useOnMountOnly";
import UnavailableOverlay from "@/sidebar/UnavailableOverlay";

const ActivateModPanel = lazy(
async () =>
Expand Down Expand Up @@ -350,6 +351,7 @@ const Tabs: React.FC = () => {
});
}}
>
{form.isUnavailable && <UnavailableOverlay />}
<FormBody form={form} />
</ErrorBoundary>
</Tab.Pane>
Expand Down
48 changes: 48 additions & 0 deletions src/sidebar/UnavailableOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*

Check failure on line 1 in src/sidebar/UnavailableOverlay.tsx

View workflow job for this annotation

GitHub Actions / strictNullChecks

strictNullChecks

src/sidebar/UnavailableOverlay.tsx was not found in tsconfig.strictNullChecks.json
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import React from "react";
import styles from "./unavailableOverlay.module.scss";
import cx from "classnames";
import { Button, Modal } from "react-bootstrap";

const UnavailableOverlay = () => (
<div className={styles["unavailable-overlay"]}>
<div
className={cx("modal show position-static w-auto h-auto")}
style={{ display: "block", marginTop: "10vh" }}
>
<Modal.Dialog
size={"sm"}
className={cx(styles["modal-dialog"], "shadow text-center")}
>
<Modal.Header className={styles["modal-header"]}>
<strong>Panel not available on this page</strong>
</Modal.Header>

<Modal.Body className={styles["modal-body"]}>
<p>The browser navigated away from the page</p>
<Button variant="primary" className={styles["modal-button"]}>
Close
</Button>
</Modal.Body>
</Modal.Dialog>
</div>
</div>
);

export default UnavailableOverlay;
2 changes: 1 addition & 1 deletion src/sidebar/connectedTarget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { once } from "lodash";
import { type TopLevelFrame, getTopLevelFrame } from "webext-messenger";
import { getTabUrl } from "webext-tools";

function getConnectedTabIdMv3(): number {
export function getConnectedTabIdMv3(): number {
expectContext("sidebar");
const tabId = new URLSearchParams(window.location.search).get("tabId");
assertNotNullish(
Expand Down
57 changes: 57 additions & 0 deletions src/sidebar/unavailableOverlay.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*!
* Copyright (C) 2024 PixieBrix, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

.unavailable-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.5);
display: flex;
justify-content: center;
align-items: flex-start;
backdrop-filter: blur(2.5px);
pointer-events: all;
}

.modal-dialog {
margin: 23px;
padding: 16px;
border-radius: 12px;
background: white;
border: 1px solid #cfcbd6;
> div {
border: 0;
}
}

.modal-header {
display: block;
padding-bottom: 0;
background: white;
border: 0;
}

.modal-body {
background: white;
border: 0;
}

.modal-button {
border-radius: 4px;
}
9 changes: 9 additions & 0 deletions src/store/sidebar/sidebarSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,15 @@ const sidebarSlice = createSlice({

fixActiveTabOnRemove(state, entry);
},
markTemporaryPanelsAsUnavailable(state) {
for (const form of state.forms) {
form.isUnavailable = true;
}

for (const temporaryPanel of state.temporaryPanels) {
temporaryPanel.isUnavailable = true;
}
},
updateTemporaryPanel(
state,
action: PayloadAction<{ panel: TemporaryPanelEntry }>,
Expand Down
7 changes: 6 additions & 1 deletion src/testUtils/factories/sidebarEntryFactories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@

import { define, type FactoryConfig } from "cooky-cutter";
import {
type ModActivationPanelEntry,
type EntryType,
type FormPanelEntry,
type ModActivationPanelEntry,
type PanelEntry,
type SidebarEntry,
type StaticPanelEntry,
Expand All @@ -38,11 +38,13 @@ const activateModPanelEntryFactory = define<ModActivationPanelEntry>({
},
],
heading: (n: number) => `Activate Mods Test ${n}`,
isUnavailable: false,
});
const staticPanelEntryFactory = define<StaticPanelEntry>({
type: "staticPanel",
heading: (n: number) => `Static Panel ${n}`,
key: (n: number) => `static-panel-${n}`,
isUnavailable: false,
});
const formDefinitionFactory = define<FormDefinition>({
schema: () => ({
Expand All @@ -60,6 +62,7 @@ export const formEntryFactory = define<FormPanelEntry>({
validateRegistryId(`@test/form-panel-recipe-test-${n}`),
nonce: uuidSequence,
form: formDefinitionFactory,
isUnavailable: false,
});
const temporaryPanelEntryFactory = define<TemporaryPanelEntry>({
type: "temporaryPanel",
Expand All @@ -68,6 +71,7 @@ const temporaryPanelEntryFactory = define<TemporaryPanelEntry>({
heading: (n: number) => `Temporary Panel Test ${n}`,
payload: null,
nonce: uuidSequence,
isUnavailable: false,
});
const panelEntryFactory = define<PanelEntry>({
type: "panel",
Expand All @@ -78,6 +82,7 @@ const panelEntryFactory = define<PanelEntry>({
payload: null,
extensionPointId: (n: number) =>
validateRegistryId(`@test/panel-extension-point-test-${n}`),
isUnavailable: false,
});

export function sidebarEntryFactory<T = PanelEntry>(
Expand Down
6 changes: 6 additions & 0 deletions src/types/sidebarTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ type BasePanelEntry = {
* The panel type.
*/
type: EntryType;

/**
* Determines if the panel cannot be displayed for the current tab. Used
* to show an overlay over the panel to indicate it is unavailable.
*/
isUnavailable?: boolean;
};

/**
Expand Down
2 changes: 1 addition & 1 deletion webpack.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ const createConfig = (env, options) =>
}),

// Only notifies when watching. `zsh-notify` is suggested for the `build` script
options.watch &&
!isProd(options) &&
process.env.DEV_NOTIFY !== "false" &&
new WebpackBuildNotifierPlugin({
title: "PB Extension",
Expand Down

0 comments on commit b109943

Please sign in to comment.