mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
233 lines
6.0 KiB
TypeScript
233 lines
6.0 KiB
TypeScript
|
import {makeT} from 'app/client/lib/localization';
|
||
|
import {urlState} from 'app/client/models/gristUrlState';
|
||
|
import {HomeModel} from 'app/client/models/HomeModel';
|
||
|
import {openVideoTour} from 'app/client/ui/OpenVideoTour';
|
||
|
import {bigPrimaryButtonLink} from 'app/client/ui2018/buttons';
|
||
|
import {colors, theme} from 'app/client/ui2018/cssVars';
|
||
|
import {icon} from 'app/client/ui2018/icons';
|
||
|
import {isFeatureEnabled} from 'app/common/gristUrls';
|
||
|
import {getGristConfig} from 'app/common/urlUtils';
|
||
|
import {Computed, dom, IDisposableOwner, makeTestId, styled, subscribeElem} from 'grainjs';
|
||
|
|
||
|
interface BuildOnboardingCardsOptions {
|
||
|
homeModel: HomeModel;
|
||
|
}
|
||
|
|
||
|
const t = makeT('OnboardingCards');
|
||
|
|
||
|
const testId = makeTestId('test-onboarding-');
|
||
|
|
||
|
export function buildOnboardingCards(
|
||
|
owner: IDisposableOwner,
|
||
|
{homeModel}: BuildOnboardingCardsOptions
|
||
|
) {
|
||
|
const {templateOrg, onboardingTutorialDocId} = getGristConfig();
|
||
|
if (!isFeatureEnabled('tutorials') || !templateOrg || !onboardingTutorialDocId) { return null; }
|
||
|
|
||
|
const percentComplete = Computed.create(owner, (use) => {
|
||
|
if (!homeModel.app.currentValidUser) { return 0; }
|
||
|
|
||
|
const tutorial = use(homeModel.onboardingTutorial);
|
||
|
if (!tutorial) { return undefined; }
|
||
|
|
||
|
return tutorial.forks?.[0]?.options?.tutorial?.percentComplete ?? 0;
|
||
|
});
|
||
|
|
||
|
const shouldShowCards = Computed.create(owner, (use) =>
|
||
|
!use(homeModel.app.dismissedPopups).includes('onboardingCards'));
|
||
|
|
||
|
let videoPlayButtonElement: HTMLElement;
|
||
|
|
||
|
return dom.maybe(shouldShowCards, () =>
|
||
|
cssOnboardingCards(
|
||
|
cssTutorialCard(
|
||
|
cssDismissCardsButton(
|
||
|
icon('CrossBig'),
|
||
|
dom.on('click', () => homeModel.app.dismissPopup('onboardingCards', true)),
|
||
|
testId('dismiss-cards'),
|
||
|
),
|
||
|
cssTutorialCardHeader(
|
||
|
t('Complete our basics tutorial'),
|
||
|
),
|
||
|
cssTutorialCardSubHeader(
|
||
|
t('Learn the basic of reference columns, linked widgets, column types, & cards.')
|
||
|
),
|
||
|
cssTutorialCardBody(
|
||
|
cssTutorialProgress(
|
||
|
cssTutorialProgressText(
|
||
|
cssProgressPercentage(
|
||
|
dom.domComputed(percentComplete, (percent) => percent !== undefined ? `${percent}%` : null),
|
||
|
testId('tutorial-percent-complete'),
|
||
|
),
|
||
|
cssStarIcon('Star'),
|
||
|
),
|
||
|
cssTutorialProgressBar(
|
||
|
(elem) => subscribeElem(elem, percentComplete, (val) => {
|
||
|
elem.style.setProperty('--percent-complete', String(val ?? 0));
|
||
|
})
|
||
|
),
|
||
|
),
|
||
|
bigPrimaryButtonLink(
|
||
|
t('Complete the tutorial'),
|
||
|
urlState().setLinkUrl({org: templateOrg, doc: onboardingTutorialDocId}),
|
||
|
),
|
||
|
),
|
||
|
testId('tutorial-card'),
|
||
|
),
|
||
|
cssVideoCard(
|
||
|
cssVideoThumbnail(
|
||
|
cssVideoThumbnailSpacer(),
|
||
|
videoPlayButtonElement = cssVideoPlayButton(
|
||
|
cssPlayIcon('VideoPlay2'),
|
||
|
),
|
||
|
cssVideoThumbnailText(t('3 minute video tour')),
|
||
|
),
|
||
|
dom.on('click', () => openVideoTour(videoPlayButtonElement)),
|
||
|
),
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
const cssOnboardingCards = styled('div', `
|
||
|
display: grid;
|
||
|
grid-template-columns: repeat(auto-fit, minmax(220px, max-content));
|
||
|
gap: 24px;
|
||
|
margin: 24px 0;
|
||
|
`);
|
||
|
|
||
|
const cssTutorialCard = styled('div', `
|
||
|
position: relative;
|
||
|
border-radius: 4px;
|
||
|
color: ${theme.announcementPopupFg};
|
||
|
background-color: ${theme.announcementPopupBg};
|
||
|
padding: 16px 24px;
|
||
|
`);
|
||
|
|
||
|
const cssTutorialCardHeader = styled('div', `
|
||
|
display: flex;
|
||
|
align-items: flex-start;
|
||
|
justify-content: space-between;
|
||
|
font-size: 18px;
|
||
|
font-style: normal;
|
||
|
font-weight: 700;
|
||
|
`);
|
||
|
|
||
|
const cssDismissCardsButton = styled('div', `
|
||
|
position: absolute;
|
||
|
top: 8px;
|
||
|
right: 8px;
|
||
|
padding: 4px;
|
||
|
border-radius: 4px;
|
||
|
cursor: pointer;
|
||
|
--icon-color: ${theme.popupCloseButtonFg};
|
||
|
|
||
|
&:hover {
|
||
|
background-color: ${theme.hover};
|
||
|
}
|
||
|
`);
|
||
|
|
||
|
const cssTutorialCardSubHeader = styled('div', `
|
||
|
font-size: 14px;
|
||
|
font-style: normal;
|
||
|
font-weight: 500;
|
||
|
margin: 8px 0;
|
||
|
`);
|
||
|
|
||
|
const cssTutorialCardBody = styled('div', `
|
||
|
display: flex;
|
||
|
flex-wrap: wrap;
|
||
|
gap: 24px;
|
||
|
margin: 16px 0;
|
||
|
align-items: end;
|
||
|
`);
|
||
|
|
||
|
const cssTutorialProgress = styled('div', `
|
||
|
flex: auto;
|
||
|
min-width: 120px;
|
||
|
`);
|
||
|
|
||
|
const cssTutorialProgressText = styled('div', `
|
||
|
display: flex;
|
||
|
justify-content: space-between;
|
||
|
`);
|
||
|
|
||
|
const cssProgressPercentage = styled('div', `
|
||
|
font-size: 20px;
|
||
|
font-style: normal;
|
||
|
font-weight: 700;
|
||
|
`);
|
||
|
|
||
|
const cssStarIcon = styled(icon, `
|
||
|
--icon-color: ${theme.accentIcon};
|
||
|
width: 24px;
|
||
|
height: 24px;
|
||
|
`);
|
||
|
|
||
|
const cssTutorialProgressBar = styled('div', `
|
||
|
margin-top: 4px;
|
||
|
height: 10px;
|
||
|
border-radius: 8px;
|
||
|
background: ${theme.mainPanelBg};
|
||
|
--percent-complete: 0;
|
||
|
|
||
|
&::after {
|
||
|
content: '';
|
||
|
border-radius: 8px;
|
||
|
background: ${theme.progressBarFg};
|
||
|
display: block;
|
||
|
height: 100%;
|
||
|
width: calc((var(--percent-complete) / 100) * 100%);
|
||
|
}
|
||
|
`);
|
||
|
|
||
|
const cssVideoCard = styled('div', `
|
||
|
width: 220px;
|
||
|
height: 158px;
|
||
|
overflow: hidden;
|
||
|
cursor: pointer;
|
||
|
border-radius: 4px;
|
||
|
`);
|
||
|
|
||
|
const cssVideoThumbnail = styled('div', `
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
justify-content: space-between;
|
||
|
padding: 36px 32px;
|
||
|
background-image: url("img/youtube-screenshot.png");
|
||
|
background-color: rgba(0, 0, 0, 0.4);
|
||
|
background-blend-mode: multiply;
|
||
|
background-size: cover;
|
||
|
transform: scale(1.2);
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
`);
|
||
|
|
||
|
const cssVideoThumbnailSpacer = styled('div', ``);
|
||
|
|
||
|
const cssVideoPlayButton = styled('div', `
|
||
|
display: flex;
|
||
|
justify-content: center;
|
||
|
align-items: center;
|
||
|
align-self: center;
|
||
|
width: 32px;
|
||
|
height: 32px;
|
||
|
background-color: ${theme.controlPrimaryBg};
|
||
|
border-radius: 50%;
|
||
|
|
||
|
.${cssVideoThumbnail.className}:hover & {
|
||
|
background-color: ${theme.controlPrimaryHoverBg};
|
||
|
}
|
||
|
`);
|
||
|
|
||
|
const cssPlayIcon = styled(icon, `
|
||
|
--icon-color: ${theme.controlPrimaryFg};
|
||
|
width: 24px;
|
||
|
height: 24px;
|
||
|
`);
|
||
|
|
||
|
const cssVideoThumbnailText = styled('div', `
|
||
|
color: ${colors.light};
|
||
|
font-weight: 700;
|
||
|
text-align: center;
|
||
|
`);
|