mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
cd0c6de53e
Summary: Extracts code from showExampleCard into a generic function which is reused for document tours. It handles reading and writing to user preferences for automatic showing and explicitly reopening. Test Plan: Manually tested that it automatically shows a tour just once and clicking to reopen works. There's not much new functionality so there's little that needs testing. This is an initial version that's mostly internal and is likely to be polished for users in the future. If I should still add tests, I'd like confirmation that the current behaviour is as desired. Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2944
178 lines
4.8 KiB
TypeScript
178 lines
4.8 KiB
TypeScript
import {IExampleInfo} from 'app/client/ui/ExampleInfo';
|
|
import {prepareForTransition, TransitionWatcher} from 'app/client/ui/transitions';
|
|
import {colors, mediaXSmall, testId, vars} from 'app/client/ui2018/cssVars';
|
|
import {icon} from 'app/client/ui2018/icons';
|
|
import {cssLink} from 'app/client/ui2018/links';
|
|
import {dom, styled} from 'grainjs';
|
|
import {AutomaticHelpToolInfo} from "app/client/ui/Tools";
|
|
|
|
let prevCardClose: (() => void)|null = null;
|
|
|
|
// Open a popup with a card introducing this example, if the user hasn't dismissed it in the past.
|
|
export function showExampleCard(
|
|
example: IExampleInfo, toolInfo: AutomaticHelpToolInfo
|
|
) {
|
|
const {elem: btnElem, markAsSeen, reopen} = toolInfo;
|
|
|
|
// Close the example card.
|
|
function close() {
|
|
prevCardClose = null;
|
|
collapseAndRemoveCard(cardElem, btnElem.getBoundingClientRect());
|
|
markAsSeen();
|
|
}
|
|
|
|
const card = example.welcomeCard;
|
|
if (!card) { return null; }
|
|
const cardElem = cssCard(
|
|
cssImage({src: example.imgUrl}),
|
|
cssBody(
|
|
cssTitle(card.title),
|
|
cssInfo(card.text),
|
|
cssButtons(
|
|
cssLinkBtn(cssLinkIcon('Page'), card.tutorialName,
|
|
{href: example.tutorialUrl, target: '_blank'},
|
|
),
|
|
// TODO: Add a link to the overview video (as popup or to a support page that shows the
|
|
// video). Also include a 'Video' icon.
|
|
// cssLinkBtn(cssLinkIcon('Video'), 'Grist Video Tour'),
|
|
)
|
|
),
|
|
cssCloseButton(cssBigIcon('CrossBig'),
|
|
dom.on('click', close),
|
|
testId('example-card-close'),
|
|
),
|
|
testId('example-card'),
|
|
);
|
|
document.body.appendChild(cardElem);
|
|
|
|
// When reopening, open the card smoothly, for a nicer-looking effect.
|
|
if (reopen) {
|
|
expandCard(cardElem, btnElem.getBoundingClientRect());
|
|
}
|
|
|
|
prevCardClose?.();
|
|
prevCardClose = () => disposeCard(cardElem);
|
|
}
|
|
|
|
function disposeCard(cardElem: HTMLElement) {
|
|
dom.domDispose(cardElem);
|
|
cardElem.remove();
|
|
}
|
|
|
|
// When closing the card, collapse it visually into the button that can open it again, to hint to
|
|
// the user where to find that button. Remove the card after the animation.
|
|
function collapseAndRemoveCard(card: HTMLElement, collapsedRect: DOMRect) {
|
|
const watcher = new TransitionWatcher(card);
|
|
watcher.onDispose(() => disposeCard(card));
|
|
collapseCard(card, collapsedRect);
|
|
}
|
|
|
|
// Implements the collapsing animation by simply setting a scale transform with a suitable origin.
|
|
function collapseCard(card: HTMLElement, collapsedRect: DOMRect) {
|
|
const rect = card.getBoundingClientRect();
|
|
const originX = (collapsedRect.left + collapsedRect.width / 2) - rect.left;
|
|
const originY = (collapsedRect.top + collapsedRect.height / 2) - rect.top;
|
|
Object.assign(card.style, {
|
|
transform: `scale(${collapsedRect.width / rect.width}, ${collapsedRect.height / rect.height})`,
|
|
transformOrigin: `${originX}px ${originY}px`,
|
|
opacity: '0',
|
|
});
|
|
}
|
|
|
|
// To expand the card visually, we reverse the process by collapsing it first with transitions
|
|
// disabled, then resetting properties to their defaults with transitions enabled again.
|
|
function expandCard(card: HTMLElement, collapsedRect: DOMRect) {
|
|
prepareForTransition(card, () => collapseCard(card, collapsedRect));
|
|
Object.assign(card.style, {
|
|
transform: '',
|
|
opacity: '',
|
|
visibility: 'visible',
|
|
});
|
|
}
|
|
|
|
|
|
const cssCard = styled('div', `
|
|
position: absolute;
|
|
left: 24px;
|
|
bottom: 24px;
|
|
margin-right: 24px;
|
|
max-width: 624px;
|
|
padding: 32px 56px 32px 32px;
|
|
background-color: white;
|
|
box-shadow: 0 2px 18px 0 rgba(31,37,50,0.31), 0 0 1px 0 rgba(76,86,103,0.24);
|
|
display: flex;
|
|
overflow: hidden;
|
|
transition-property: opacity, transform;
|
|
transition-duration: 0.5s;
|
|
transition-timing-func: ease-in;
|
|
--title-font-size: ${vars.headerControlFontSize};
|
|
|
|
@media ${mediaXSmall} {
|
|
& {
|
|
flex-direction: column;
|
|
padding: 32px;
|
|
--title-font-size: 18px;
|
|
}
|
|
}
|
|
`);
|
|
|
|
const cssImage = styled('img', `
|
|
flex: none;
|
|
width: 180px;
|
|
height: 140px;
|
|
margin: 0 16px 0 -8px;
|
|
@media ${mediaXSmall} {
|
|
& {
|
|
margin: auto;
|
|
}
|
|
}
|
|
`);
|
|
|
|
const cssBody = styled('div', `
|
|
min-width: 0px;
|
|
`);
|
|
|
|
const cssTitle = styled('div', `
|
|
font-size: var(--title-font-size);
|
|
font-weight: ${vars.headerControlTextWeight};
|
|
margin-bottom: 16px;
|
|
`);
|
|
|
|
const cssInfo = styled('div', `
|
|
margin: 16px 0 24px 0;
|
|
line-height: 1.6;
|
|
`);
|
|
|
|
const cssButtons = styled('div', `
|
|
display: flex;
|
|
`);
|
|
|
|
const cssLinkBtn = styled(cssLink, `
|
|
&:not(:last-child) {
|
|
margin-right: 32px;
|
|
}
|
|
`);
|
|
|
|
const cssLinkIcon = styled(icon, `
|
|
margin-right: 8px;
|
|
margin-top: -2px;
|
|
`);
|
|
|
|
const cssCloseButton = styled('div', `
|
|
position: absolute;
|
|
top: 8px;
|
|
right: 8px;
|
|
padding: 4px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
--icon-color: ${colors.slate};
|
|
|
|
&:hover {
|
|
background-color: ${colors.mediumGreyOpaque};
|
|
}
|
|
`);
|
|
|
|
const cssBigIcon = styled(icon, `
|
|
padding: 12px;
|
|
`);
|