The Welcome Home Screen: Return Button (#9089)
* Implement button to return to user onboarding screen * Add analytics events * Increase stability of lazy loading test
This commit is contained in:
parent
8db7766a40
commit
9eaf48b176
13 changed files with 241 additions and 28 deletions
|
@ -45,9 +45,12 @@ import { shouldShowComponent } from "../../customisations/helpers/UIComponents";
|
|||
import { UIComponent } from "../../settings/UIFeature";
|
||||
import { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
import PosthogTrackers from "../../PosthogTrackers";
|
||||
import PageType from "../../PageTypes";
|
||||
import { UserOnboardingButton } from "../views/user-onboarding/UserOnboardingButton";
|
||||
|
||||
interface IProps {
|
||||
isMinimized: boolean;
|
||||
pageType: PageType;
|
||||
resizeNotifier: ResizeNotifier;
|
||||
}
|
||||
|
||||
|
@ -390,6 +393,10 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
onVisibilityChange={this.refreshStickyHeaders}
|
||||
/>
|
||||
) }
|
||||
<UserOnboardingButton
|
||||
selected={this.props.pageType === PageType.HomePage}
|
||||
minimized={this.props.isMinimized}
|
||||
/>
|
||||
<div className="mx_LeftPanel_roomListWrapper">
|
||||
<div
|
||||
className={roomListClasses}
|
||||
|
|
|
@ -689,6 +689,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
data-collapsed={this.props.collapseLhs ? true : undefined}
|
||||
>
|
||||
<LeftPanel
|
||||
pageType={this.props.page_type as PageTypes}
|
||||
isMinimized={this.props.collapseLhs || false}
|
||||
resizeNotifier={this.props.resizeNotifier}
|
||||
/>
|
||||
|
|
|
@ -46,6 +46,7 @@ interface IState {
|
|||
export default class PreferencesUserSettingsTab extends React.Component<IProps, IState> {
|
||||
private static ROOM_LIST_SETTINGS = [
|
||||
'breadcrumbs',
|
||||
"FTUE.userOnboardingButton",
|
||||
];
|
||||
|
||||
private static SPACES_SETTINGS = [
|
||||
|
|
102
src/components/views/user-onboarding/UserOnboardingButton.tsx
Normal file
102
src/components/views/user-onboarding/UserOnboardingButton.tsx
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import classNames from "classnames";
|
||||
import React, { useCallback } from "react";
|
||||
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { useSettingValue } from "../../../hooks/useSettings";
|
||||
import { useUserOnboardingContext } from "../../../hooks/useUserOnboardingContext";
|
||||
import { useUserOnboardingTasks } from "../../../hooks/useUserOnboardingTasks";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { UseCase } from "../../../settings/enums/UseCase";
|
||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
|
||||
import ProgressBar from "../../views/elements/ProgressBar";
|
||||
import Heading from "../../views/typography/Heading";
|
||||
import { showUserOnboardingPage } from "./UserOnboardingPage";
|
||||
|
||||
function toPercentage(progress: number): string {
|
||||
return (progress * 100).toFixed(0);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
selected: boolean;
|
||||
minimized: boolean;
|
||||
}
|
||||
|
||||
export function UserOnboardingButton({ selected, minimized }: Props) {
|
||||
const context = useUserOnboardingContext();
|
||||
const [completedTasks, waitingTasks] = useUserOnboardingTasks(context);
|
||||
|
||||
const completed = completedTasks.length;
|
||||
const waiting = waitingTasks.length;
|
||||
const total = completed + waiting;
|
||||
|
||||
let progress = 1;
|
||||
if (context && waiting) {
|
||||
progress = completed / total;
|
||||
}
|
||||
|
||||
const onDismiss = useCallback((ev: ButtonEvent) => {
|
||||
PosthogTrackers.trackInteraction("WebRoomListUserOnboardingIgnoreButton", ev);
|
||||
SettingsStore.setValue("FTUE.userOnboardingButton", null, SettingLevel.ACCOUNT, false);
|
||||
}, []);
|
||||
|
||||
const onClick = useCallback((ev: ButtonEvent) => {
|
||||
PosthogTrackers.trackInteraction("WebRoomListUserOnboardingButton", ev);
|
||||
defaultDispatcher.fire(Action.ViewHomePage);
|
||||
}, []);
|
||||
|
||||
const useCase = useSettingValue<UseCase | null>("FTUE.useCaseSelection");
|
||||
const visible = useSettingValue<boolean>("FTUE.userOnboardingButton");
|
||||
if (!visible || minimized || !showUserOnboardingPage(useCase)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AccessibleButton
|
||||
className={classNames("mx_UserOnboardingButton", {
|
||||
"mx_UserOnboardingButton_selected": selected,
|
||||
"mx_UserOnboardingButton_minimized": minimized,
|
||||
"mx_UserOnboardingButton_completed": !waiting || !context,
|
||||
})}
|
||||
onClick={onClick}>
|
||||
{ !minimized && (
|
||||
<>
|
||||
<div className="mx_UserOnboardingButton_content">
|
||||
<Heading size="h4" className="mx_Heading_h4">
|
||||
{ _t("Welcome") }
|
||||
</Heading>
|
||||
{ context && !completed && (
|
||||
<div className="mx_UserOnboardingButton_percentage">
|
||||
{ toPercentage(progress) }%
|
||||
</div>
|
||||
) }
|
||||
<AccessibleButton
|
||||
className="mx_UserOnboardingButton_close"
|
||||
onClick={onDismiss}
|
||||
/>
|
||||
</div>
|
||||
<ProgressBar value={completed} max={total} animated />
|
||||
</>
|
||||
) }
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
|
@ -19,6 +19,7 @@ import * as React from "react";
|
|||
|
||||
import { useInitialSyncComplete } from "../../../hooks/useIsInitialSyncComplete";
|
||||
import { useSettingValue } from "../../../hooks/useSettings";
|
||||
import { useUserOnboardingContext } from "../../../hooks/useUserOnboardingContext";
|
||||
import { useUserOnboardingTasks } from "../../../hooks/useUserOnboardingTasks";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
|
@ -47,7 +48,8 @@ export function UserOnboardingPage({ justRegistered = false }: Props) {
|
|||
const pageUrl = getHomePageUrl(config);
|
||||
|
||||
const useCase = useSettingValue<UseCase | null>("FTUE.useCaseSelection");
|
||||
const [completedTasks, waitingTasks] = useUserOnboardingTasks();
|
||||
const context = useUserOnboardingContext();
|
||||
const [completedTasks, waitingTasks] = useUserOnboardingTasks(context);
|
||||
|
||||
const initialSyncComplete = useInitialSyncComplete();
|
||||
const [showList, setShowList] = useState<boolean>(false);
|
||||
|
|
|
@ -14,8 +14,9 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { ClientEvent, IMyDevice, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import DMRoomMap from "../utils/DMRoomMap";
|
||||
|
@ -33,18 +34,23 @@ export function useUserOnboardingContext(): UserOnboardingContext | null {
|
|||
|
||||
const cli = MatrixClientPeg.get();
|
||||
const handler = useCallback(async () => {
|
||||
const profile = await cli.getProfileInfo(cli.getUserId());
|
||||
try {
|
||||
const profile = await cli.getProfileInfo(cli.getUserId());
|
||||
|
||||
const myDevice = cli.getDeviceId();
|
||||
const devices = await cli.getDevices();
|
||||
const myDevice = cli.getDeviceId();
|
||||
const devices = await cli.getDevices();
|
||||
|
||||
const dmRooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals() ?? {};
|
||||
setContext({
|
||||
avatar: profile?.avatar_url ?? null,
|
||||
myDevice,
|
||||
devices: devices.devices,
|
||||
dmRooms: dmRooms,
|
||||
});
|
||||
const dmRooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals() ?? {};
|
||||
setContext({
|
||||
avatar: profile?.avatar_url ?? null,
|
||||
myDevice,
|
||||
devices: devices.devices,
|
||||
dmRooms: dmRooms,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.warn("Could not load context for user onboarding task list: ", e);
|
||||
setContext(null);
|
||||
}
|
||||
}, [cli]);
|
||||
|
||||
useEventEmitter(cli, ClientEvent.AccountData, handler);
|
||||
|
|
|
@ -27,7 +27,7 @@ import { Notifier } from "../Notifier";
|
|||
import PosthogTrackers from "../PosthogTrackers";
|
||||
import { UseCase } from "../settings/enums/UseCase";
|
||||
import { useSettingValue } from "./useSettings";
|
||||
import { UserOnboardingContext, useUserOnboardingContext } from "./useUserOnboardingContext";
|
||||
import { UserOnboardingContext } from "./useUserOnboardingContext";
|
||||
|
||||
export interface UserOnboardingTask {
|
||||
id: string;
|
||||
|
@ -140,13 +140,12 @@ const tasks: InternalUserOnboardingTask[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export function useUserOnboardingTasks(): [UserOnboardingTask[], UserOnboardingTask[]] {
|
||||
export function useUserOnboardingTasks(context: UserOnboardingContext): [UserOnboardingTask[], UserOnboardingTask[]] {
|
||||
const useCase = useSettingValue<UseCase | null>("FTUE.useCaseSelection") ?? UseCase.Skip;
|
||||
const relevantTasks = useMemo(
|
||||
() => tasks.filter(it => !it.relevant || it.relevant.includes(useCase)),
|
||||
[useCase],
|
||||
);
|
||||
const onboardingInfo = useUserOnboardingContext();
|
||||
const completedTasks = relevantTasks.filter(it => onboardingInfo && it.completed(onboardingInfo));
|
||||
const completedTasks = relevantTasks.filter(it => context && it.completed(context));
|
||||
return [completedTasks, relevantTasks.filter(it => !completedTasks.includes(it))];
|
||||
}
|
||||
|
|
|
@ -951,6 +951,7 @@
|
|||
"Order rooms by name": "Order rooms by name",
|
||||
"Show rooms with unread notifications first": "Show rooms with unread notifications first",
|
||||
"Show shortcuts to recently viewed rooms above the room list": "Show shortcuts to recently viewed rooms above the room list",
|
||||
"Show shortcut to welcome checklist above the room list": "Show shortcut to welcome checklist above the room list",
|
||||
"Show hidden events in timeline": "Show hidden events in timeline",
|
||||
"Low bandwidth mode (requires compatible homeserver)": "Low bandwidth mode (requires compatible homeserver)",
|
||||
"Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)",
|
||||
|
@ -1147,6 +1148,7 @@
|
|||
"Anchor": "Anchor",
|
||||
"Headphones": "Headphones",
|
||||
"Folder": "Folder",
|
||||
"Welcome": "Welcome",
|
||||
"How are you finding Element so far?": "How are you finding Element so far?",
|
||||
"We’d appreciate any feedback on how you’re finding Element.": "We’d appreciate any feedback on how you’re finding Element.",
|
||||
"Feedback": "Feedback",
|
||||
|
|
|
@ -804,6 +804,11 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
|||
default: true,
|
||||
controller: new IncompatibleController("feature_breadcrumbs_v2", true),
|
||||
},
|
||||
"FTUE.userOnboardingButton": {
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
displayName: _td("Show shortcut to welcome checklist above the room list"),
|
||||
default: true,
|
||||
},
|
||||
"showHiddenEventsInTimeline": {
|
||||
displayName: _td("Show hidden events in timeline"),
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue