mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-02 07:06:41 +03:00
interface: display tutorial interface
This commit is contained in:
parent
cca47cc3e7
commit
2a7ee29dc5
55
pkg/interface/src/logic/lib/relativePosition.tsx
Normal file
55
pkg/interface/src/logic/lib/relativePosition.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import _ from "lodash";
|
||||
|
||||
export const alignY = ["top", "bottom"] as const;
|
||||
export type AlignY = typeof alignY[number];
|
||||
export const alignX = ["left", "right"] as const;
|
||||
export type AlignX = typeof alignX[number];
|
||||
|
||||
export function getRelativePosition(
|
||||
relativeTo: HTMLElement | null,
|
||||
alignX: AlignX | AlignX[],
|
||||
alignY: AlignY | AlignY[],
|
||||
offsetX: number = 0,
|
||||
offsetY: number = 0
|
||||
) {
|
||||
const rect = relativeTo?.getBoundingClientRect();
|
||||
if (!rect) {
|
||||
return {};
|
||||
}
|
||||
const bounds = {
|
||||
top: rect.top - offsetY,
|
||||
left: rect.left - offsetX,
|
||||
bottom: document.documentElement.clientHeight - rect.bottom - offsetY,
|
||||
right: document.documentElement.clientWidth - rect.right - offsetX,
|
||||
};
|
||||
const alignXArr = _.isArray(alignX) ? alignX : [alignX];
|
||||
const alignYArr = _.isArray(alignY) ? alignY : [alignY];
|
||||
|
||||
return {
|
||||
..._.reduce(
|
||||
alignXArr,
|
||||
(acc, a, idx) => ({
|
||||
...acc,
|
||||
[a]: _.zipWith(
|
||||
[...Array(idx), `${bounds[a]}px`],
|
||||
acc[a] || [],
|
||||
(a, b) => a || b || null
|
||||
),
|
||||
}),
|
||||
{}
|
||||
),
|
||||
..._.reduce(
|
||||
alignYArr,
|
||||
(acc, a, idx) => ({
|
||||
...acc,
|
||||
[a]: _.zipWith(
|
||||
[...Array(idx), `${bounds[a]}px`],
|
||||
acc[a] || [],
|
||||
(a, b) => a || b || null
|
||||
),
|
||||
}),
|
||||
{}
|
||||
),
|
||||
} as Record<AlignY | AlignX, string[]>;
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
export const tutorialProgress = ['hidden', 'start', 'group-desc', 'channels', 'chat', 'link', 'publish', 'notifications', 'profile', 'leap', 'done'] as const;
|
||||
|
||||
export type TutorialProgress = typeof tutorialProgress[number];
|
||||
interface LocalUpdateSetDark {
|
||||
setDark: boolean;
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import { Content } from './landscape/components/Content';
|
||||
import StatusBar from './components/StatusBar';
|
||||
import Omnibox from './components/leap/Omnibox';
|
||||
import ErrorBoundary from '~/views/components/ErrorBoundary';
|
||||
import { TutorialModal } from '~/views/landscape/components/TutorialModal';
|
||||
|
||||
import GlobalStore from '~/logic/store/store';
|
||||
import GlobalSubscription from '~/logic/subscription/global';
|
||||
@ -149,6 +150,7 @@ class App extends React.Component {
|
||||
</Helmet>
|
||||
<Root background={background}>
|
||||
<Router>
|
||||
<TutorialModal api={this.api} />
|
||||
<ErrorBoundary>
|
||||
<StatusBarWithRouter
|
||||
props={this.props}
|
||||
|
174
pkg/interface/src/views/landscape/components/TutorialModal.tsx
Normal file
174
pkg/interface/src/views/landscape/components/TutorialModal.tsx
Normal file
@ -0,0 +1,174 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { Box, Col, Row, Button, Text, Icon, Action } from "@tlon/indigo-react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { TutorialProgress, tutorialProgress as progress } from "~/types";
|
||||
|
||||
import { Portal } from "~/views/components/Portal";
|
||||
import useLocalState, { selectLocalState } from "~/logic/state/local";
|
||||
import {
|
||||
progressDetails,
|
||||
MODAL_HEIGHT_PX,
|
||||
MODAL_WIDTH_PX,
|
||||
MODAL_WIDTH,
|
||||
MODAL_HEIGHT,
|
||||
} from "~/logic/lib/tutorialModal";
|
||||
import { getRelativePosition } from "~/logic/lib/relativePosition";
|
||||
import { StatelessAsyncButton } from "~/views/components/StatelessAsyncButton";
|
||||
import GlobalApi from "~/logic/api/global";
|
||||
|
||||
const localSelector = selectLocalState([
|
||||
"tutorialProgress",
|
||||
"nextTutStep",
|
||||
"prevTutStep",
|
||||
"tutorialRef",
|
||||
"hideTutorial",
|
||||
]);
|
||||
|
||||
export function TutorialModal(props: { api: GlobalApi }) {
|
||||
const {
|
||||
tutorialProgress,
|
||||
tutorialRef,
|
||||
nextTutStep,
|
||||
prevTutStep,
|
||||
hideTutorial,
|
||||
} = useLocalState(localSelector);
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
alignX,
|
||||
alignY,
|
||||
offsetX,
|
||||
offsetY,
|
||||
} = progressDetails[tutorialProgress];
|
||||
|
||||
const [coords, setCoords] = useState({});
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
const next = useCallback(
|
||||
(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
const idx = progress.findIndex((p) => p === tutorialProgress);
|
||||
const { url } = progressDetails[progress[idx + 1]];
|
||||
history.push(url);
|
||||
nextTutStep();
|
||||
},
|
||||
[nextTutStep, history, tutorialProgress, setCoords]
|
||||
);
|
||||
const prev = useCallback(() => {
|
||||
const idx = progress.findIndex((p) => p === tutorialProgress);
|
||||
history.push(progressDetails[progress[idx - 1]].url);
|
||||
prevTutStep();
|
||||
}, [prevTutStep, history, tutorialProgress]);
|
||||
|
||||
const updatePos = useCallback(() => {
|
||||
const newCoords = getRelativePosition(
|
||||
tutorialRef,
|
||||
alignX,
|
||||
alignY,
|
||||
offsetX,
|
||||
offsetY
|
||||
);
|
||||
if (newCoords) {
|
||||
setCoords(newCoords);
|
||||
}
|
||||
}, [tutorialRef]);
|
||||
|
||||
const dismiss = useCallback(() => {
|
||||
hideTutorial();
|
||||
props.api.settings.putEntry("tutorial", "seen", true);
|
||||
}, [hideTutorial, props.api]);
|
||||
|
||||
const leaveGroup = useCallback(async () => {
|
||||
await props.api.groups.leaveGroup("~hastuc-dibtux", "beginner-island");
|
||||
}, [props.api]);
|
||||
|
||||
const progressIdx = progress.findIndex((p) => p === tutorialProgress);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
tutorialProgress !== "hidden" &&
|
||||
tutorialProgress !== "done" &&
|
||||
tutorialRef
|
||||
) {
|
||||
const interval = setInterval(updatePos, 100);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}
|
||||
return () => {};
|
||||
}, [tutorialRef, tutorialProgress, updatePos]);
|
||||
|
||||
// manually center final window
|
||||
useEffect(() => {
|
||||
if (tutorialProgress === "done") {
|
||||
const { innerWidth, innerHeight } = window;
|
||||
const left = `${(innerWidth - MODAL_WIDTH) / 2}px`;
|
||||
const top = `${(innerHeight - MODAL_HEIGHT) / 2}px`;
|
||||
console.log(`resetting ${top} ${left}`);
|
||||
setCoords({ top, left });
|
||||
}
|
||||
}, [tutorialProgress]);
|
||||
|
||||
if (tutorialProgress === "hidden") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Box
|
||||
position="fixed"
|
||||
{...coords}
|
||||
bg="white"
|
||||
zIndex={50}
|
||||
height={MODAL_HEIGHT_PX}
|
||||
width={MODAL_WIDTH_PX}
|
||||
borderRadius="2"
|
||||
>
|
||||
<Col
|
||||
position="relative"
|
||||
justifyContent="space-between"
|
||||
height="100%"
|
||||
width="100%"
|
||||
borderRadius="2"
|
||||
p="2"
|
||||
bg="lightBlue"
|
||||
>
|
||||
<Box
|
||||
right="8px"
|
||||
top="8px"
|
||||
position="absolute"
|
||||
cursor="pointer"
|
||||
onClick={dismiss}
|
||||
>
|
||||
<Icon icon="X" />
|
||||
</Box>
|
||||
<Text lineHeight="tall" fontWeight="medium">
|
||||
{title}
|
||||
</Text>
|
||||
<Text lineHeight="tall">{description}</Text>
|
||||
{tutorialProgress !== "done" ? (
|
||||
<Row justifyContent="space-between">
|
||||
<Action bg="transparent" onClick={prev}>
|
||||
<Icon icon="ArrowWest" />
|
||||
</Action>
|
||||
<Text>
|
||||
{progressIdx}/{progress.length - 1}
|
||||
</Text>
|
||||
<Action bg="transparent" onClick={next}>
|
||||
<Icon icon="ArrowEast" />
|
||||
</Action>
|
||||
</Row>
|
||||
) : (
|
||||
<Row justifyContent="space-between">
|
||||
<StatelessAsyncButton primary onClick={leaveGroup}>
|
||||
Leave Group
|
||||
</StatelessAsyncButton>
|
||||
<Button onClick={dismiss}>Later</Button>
|
||||
</Row>
|
||||
)}
|
||||
</Col>
|
||||
</Box>
|
||||
</Portal>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user