interface: sketch out tutorial state

This commit is contained in:
Liam Fitzgerald 2021-02-08 10:29:47 +10:00
parent 9d78271c4d
commit cca47cc3e7
No known key found for this signature in database
GPG Key ID: D390E12C61D1CFFB
3 changed files with 177 additions and 2 deletions

View File

@ -0,0 +1,120 @@
import { TutorialProgress, Associations } from "~/types";
import {
AlignX,
AlignY,
} from "~/logic/lib/relativePosition";
export const MODAL_WIDTH = 256;
export const MODAL_HEIGHT = 180;
export const MODAL_WIDTH_PX = `${MODAL_WIDTH}px`;
export const MODAL_HEIGHT_PX = `${MODAL_HEIGHT}px`;
interface StepDetail {
title: string;
description: string;
url: string;
alignX: AlignX | AlignX[];
alignY: AlignY | AlignY[];
offsetX: number;
offsetY: number;
}
export function hasTutorialGroup(props: { associations: Associations }) {
return '/ship/~hastuc-dibtux/beginner-island' in props.associations.groups;
}
export const progressDetails: Record<TutorialProgress, StepDetail> = {
hidden: {} as any,
done: {
title: "End",
description: "This tutorial is finished. Would you like to leave Beginner Island?",
url: "/",
alignX: "right",
alignY: "top",
offsetX: MODAL_WIDTH + 8,
offsetY: 0
},
start: {
title: "New group added",
description:
"We just added you to the Beginner island group to show you around. This group is public, but other groups can be private",
url: "/",
alignX: "right",
alignY: "top",
offsetX: MODAL_WIDTH + 8,
offsetY: 0
},
'group-desc': {
title: "What's a group",
description: "A group contains members and tends to be centered around a topic or multiple topics.",
url: "/~landscape/ship/~hastuc-dibtux/beginner-island",
alignX: "left",
alignY: "top",
offsetX: MODAL_WIDTH + 8,
offsetY: (MODAL_HEIGHT / 2) - 8,
},
channels: {
title: "Channels",
description: "Inside a group you have three types of Channels: Chat, Collection, or Notebook. Mix and match these depending on your group context!",
url: "/~landscape/ship/~hastuc-dibtux/beginner-island",
alignY: "top",
alignX: "right",
offsetX: MODAL_WIDTH + 8,
offsetY: -8,
},
chat: {
title: "Chat",
description: "Chat channels are for messaging within your group. Direct Messages are also supported, and are accessible from the “DMs” tile on the homescreen",
url: "/~landscape/ship/~hastuc-dibtux/beginner-island/resource/chat/ship/~hastuc-dibtux/chat-8401",
alignY: "top",
alignX: "right",
offsetX: 0,
offsetY: -32,
},
link: {
title: "Collection",
description: "A collection is where you can share and view links, images, and other media within your group. Every item in a Collection can have its own comment thread.",
url: "/~landscape/ship/~hastuc-dibtux/beginner-island/resource/link/ship/~hastuc-dibtux/link-4353",
alignY: "top",
alignX: "right",
offsetX: 0,
offsetY: -32,
},
publish: {
title: "Notebook",
description: "Notebooks are for creating long-form content within your group. Use markdown to create rich posts with headers, lists and images.",
url: "/~landscape/ship/~hastuc-dibtux/beginner-island/resource/publish/ship/~hastuc-dibtux/notebook-9148",
alignY: "top",
alignX: "right",
offsetX: 0,
offsetY: -32,
},
notifications: {
title: "Notifications",
description: "Subscribing to a channel will send you notifications when there are new updates. You will also receive a notification when someone mentions your name in a channel.",
url: "/~landscape/ship/~hastuc-dibtux/beginner-island/resource/publish/ship/~hastuc-dibtux/notebook-9148/settings#notifications",
alignY: "top",
alignX: "right",
offsetX: 0,
offsetY: -32,
},
profile: {
title: "Profile",
description: "Your profile is customizable and can be shared with other ships. Enter as much or as little information as youd like.",
url: `/~profile/~${window.ship}`,
alignY: "top",
alignX: "right",
offsetX: -300 + (MODAL_WIDTH / 2),
offsetY: -120 + (MODAL_HEIGHT / 2),
},
leap: {
title: "Leap",
description: "Leap allows you to go to a specific channel, message, collection, profile or group simply by typing in a command or selecting a shortcut from the dropdown menu.",
url: `/~profile/~${window.ship}`,
alignY: "top",
alignX: "left",
offsetX: 0,
offsetY: -32,
}
};

View File

@ -1,13 +1,21 @@
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
import f from 'lodash/fp';
import create, { State } from 'zustand'; import create, { State } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import produce from 'immer'; import produce from 'immer';
import { BackgroundConfig, RemoteContentPolicy } from "~/types/local-update"; import { BackgroundConfig, RemoteContentPolicy, TutorialProgress, tutorialProgress } from "~/types/local-update";
export interface LocalState extends State { export interface LocalState extends State {
hideAvatars: boolean; hideAvatars: boolean;
hideNicknames: boolean; hideNicknames: boolean;
remoteContentPolicy: RemoteContentPolicy; remoteContentPolicy: RemoteContentPolicy;
tutorialProgress: TutorialProgress;
tutorialRef: HTMLElement | null,
hideTutorial: () => void;
nextTutStep: () => void;
prevTutStep: () => void;
setTutorialRef: (el: HTMLElement | null) => void;
dark: boolean; dark: boolean;
background: BackgroundConfig; background: BackgroundConfig;
omniboxShown: boolean; omniboxShown: boolean;
@ -15,12 +23,35 @@ export interface LocalState extends State {
toggleOmnibox: () => void; toggleOmnibox: () => void;
set: (fn: (state: LocalState) => void) => void set: (fn: (state: LocalState) => void) => void
}; };
export const selectLocalState =
<K extends keyof LocalState>(keys: K[]) => f.pick<LocalState, K>(keys);
const useLocalState = create<LocalState>(persist((set, get) => ({ const useLocalState = create<LocalState>(persist((set, get) => ({
dark: false, dark: false,
background: undefined, background: undefined,
hideAvatars: false, hideAvatars: false,
hideNicknames: false, hideNicknames: false,
tutorialProgress: 'hidden',
tutorialRef: null,
setTutorialRef: (el: HTMLElement | null) => set(produce(state => {
state.tutorialRef = el;
})),
hideTutorial: () => set(produce(state => {
state.tutorialProgress = 'hidden';
state.tutorialRef = null;
})),
nextTutStep: () => set(produce(state => {
const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress)
if(currIdx < tutorialProgress.length) {
state.tutorialProgress = tutorialProgress[currIdx + 1];
}
})),
prevTutStep: () => set(produce(state => {
const currIdx = tutorialProgress.findIndex(p => p === state.tutorialProgress)
if(currIdx > 0) {
state.tutorialProgress = tutorialProgress[currIdx - 1];
}
})),
remoteContentPolicy: { remoteContentPolicy: {
imageShown: true, imageShown: true,
audioShown: true, audioShown: true,
@ -41,7 +72,10 @@ const useLocalState = create<LocalState>(persist((set, get) => ({
})), })),
set: fn => set(produce(fn)) set: fn => set(produce(fn))
}), { }), {
blacklist: ['suspendedFocus', 'toggleOmnibox', 'omniboxShown'], blacklist: [
'suspendedFocus', 'toggleOmnibox', 'omniboxShown', 'tutorialProgress',
'prevTutStep', 'nextTutStep', 'tutorialRef', 'setTutorialRef'
],
name: 'localReducer' name: 'localReducer'
})); }));

View File

@ -0,0 +1,21 @@
import { useEffect } from "react";
import { TutorialProgress } from "~/types";
import useLocalState, { selectLocalState } from "~/logic/state/local";
const localSelector = selectLocalState(["tutorialProgress", "setTutorialRef"]);
export function useTutorialModal(
onProgress: TutorialProgress,
show: boolean,
anchorRef: HTMLElement | null
) {
const { tutorialProgress, setTutorialRef } = useLocalState(localSelector);
useEffect(() => {
if (show && onProgress === tutorialProgress && anchorRef) {
setTutorialRef(anchorRef);
}
}, [onProgress, tutorialProgress, show, anchorRef]);
return show && onProgress === tutorialProgress;
}