mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Add April Fools easter egg
Summary: What happens when you type "rr" instead of "r" in an anchor link's row number? Test Plan: Browser tests. Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3829
This commit is contained in:
@@ -9,8 +9,16 @@ import {Computed, Disposable, dom, Observable} from 'grainjs';
|
||||
import {IPopupOptions} from 'popweasel';
|
||||
|
||||
export interface AttachOptions {
|
||||
/** Defaults to false. */
|
||||
forceShow?: boolean;
|
||||
/** Defaults to false. */
|
||||
hideArrow?: boolean;
|
||||
/** Defaults to false. */
|
||||
hideDontShowTips?: boolean;
|
||||
/** Defaults to true. */
|
||||
markAsSeen?: boolean;
|
||||
/** Defaults to false. */
|
||||
showOnMobile?: boolean;
|
||||
popupOptions?: IPopupOptions;
|
||||
onDispose?(): void;
|
||||
}
|
||||
@@ -60,11 +68,11 @@ export class BehavioralPromptsManager extends Disposable {
|
||||
// Don't show tips if surveying is disabled.
|
||||
// TODO: Move this into a dedicated variable - this is only a short-term fix for hiding
|
||||
// tips in grist-core.
|
||||
!getGristConfig().survey ||
|
||||
// Or on mobile - the design currently isn't mobile-friendly.
|
||||
isNarrowScreen() ||
|
||||
(!getGristConfig().survey && prompt !== 'rickRow') ||
|
||||
// Or if this tip shouldn't be shown on mobile.
|
||||
(isNarrowScreen() && !options.showOnMobile) ||
|
||||
// Or if "Don't show tips" was checked in the past.
|
||||
this._prefs.get().dontShowTips ||
|
||||
(this._prefs.get().dontShowTips && !options.forceShow) ||
|
||||
// Or if this tip has been shown and dismissed in the past.
|
||||
this.hasSeenTip(prompt)
|
||||
) {
|
||||
@@ -88,15 +96,16 @@ export class BehavioralPromptsManager extends Disposable {
|
||||
}
|
||||
};
|
||||
|
||||
const {hideArrow = false, onDispose, popupOptions} = options;
|
||||
const {hideArrow, hideDontShowTips, markAsSeen = true, onDispose, popupOptions} = options;
|
||||
const {title, content} = GristBehavioralPrompts[prompt];
|
||||
const ctl = showBehavioralPrompt(refElement, title(), content(), {
|
||||
onClose: (dontShowTips) => {
|
||||
if (dontShowTips) { this._dontShowTips(); }
|
||||
this._markAsSeen(prompt);
|
||||
if (markAsSeen) { this._markAsSeen(prompt); }
|
||||
},
|
||||
hideArrow,
|
||||
popupOptions,
|
||||
hideDontShowTips,
|
||||
});
|
||||
|
||||
ctl.onDispose(() => {
|
||||
|
||||
@@ -36,7 +36,7 @@ import {DocData} from 'app/client/models/DocData';
|
||||
import {DocInfoRec, DocModel, ViewFieldRec, ViewRec, ViewSectionRec} from 'app/client/models/DocModel';
|
||||
import {DocPageModel} from 'app/client/models/DocPageModel';
|
||||
import {UserError} from 'app/client/models/errors';
|
||||
import {urlState} from 'app/client/models/gristUrlState';
|
||||
import {getMainOrgUrl, urlState} from 'app/client/models/gristUrlState';
|
||||
import {getFilterFunc, QuerySetManager} from 'app/client/models/QuerySet';
|
||||
import {getUserOrgPrefObs, getUserOrgPrefsObs, markAsSeen} from 'app/client/models/UserPrefs';
|
||||
import {App} from 'app/client/ui/App';
|
||||
@@ -48,8 +48,10 @@ import {IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker';
|
||||
import {linkFromId, selectBy} from 'app/client/ui/selectBy';
|
||||
import {startWelcomeTour} from 'app/client/ui/welcomeTour';
|
||||
import {IWidgetType} from 'app/client/ui/widgetTypes';
|
||||
import {isNarrowScreen, mediaSmall, testId} from 'app/client/ui2018/cssVars';
|
||||
import {PlayerState, YouTubePlayer} from 'app/client/ui/YouTubePlayer';
|
||||
import {isNarrowScreen, mediaSmall, mediaXSmall, testId, theme} from 'app/client/ui2018/cssVars';
|
||||
import {IconName} from 'app/client/ui2018/IconList';
|
||||
import {icon} from 'app/client/ui2018/icons';
|
||||
import {invokePrompt} from 'app/client/ui2018/modals';
|
||||
import {FieldEditor} from "app/client/widgets/FieldEditor";
|
||||
import {DiscussionPanel} from 'app/client/widgets/DiscussionEditor';
|
||||
@@ -78,6 +80,7 @@ import {
|
||||
Holder,
|
||||
IDisposable,
|
||||
IDomComponent,
|
||||
keyframes,
|
||||
Observable,
|
||||
styled,
|
||||
subscribe,
|
||||
@@ -87,6 +90,8 @@ import * as ko from 'knockout';
|
||||
import cloneDeepWith = require('lodash/cloneDeepWith');
|
||||
import isEqual = require('lodash/isEqual');
|
||||
|
||||
const RICK_ROLL_YOUTUBE_EMBED_ID = 'dQw4w9WgXcQ';
|
||||
|
||||
const t = makeT('GristDoc');
|
||||
|
||||
const G = getBrowserGlobals('document', 'window');
|
||||
@@ -190,6 +195,9 @@ export class GristDoc extends DisposableWithEvents {
|
||||
private _rawSectionOptions: Observable<RawSectionOptions|null> = Observable.create(this, null);
|
||||
private _activeContent: Computed<IDocPage|RawSectionOptions>;
|
||||
private _docTutorialHolder = Holder.create<DocTutorial>(this);
|
||||
private _isRickRowing: Observable<boolean> = Observable.create(this, false);
|
||||
private _showBackgroundVideoPlayer: Observable<boolean> = Observable.create(this, false);
|
||||
private _backgroundVideoPlayerHolder: Holder<YouTubePlayer> = Holder.create(this);
|
||||
|
||||
|
||||
constructor(
|
||||
@@ -280,6 +288,40 @@ export class GristDoc extends DisposableWithEvents {
|
||||
const cursorPos = this._getCursorPosFromHash(state.hash);
|
||||
await this.recursiveMoveToCursorPos(cursorPos, true);
|
||||
}
|
||||
if (state.hash.rickRow && !this._isRickRowing.get()) {
|
||||
YouTubePlayer.create(this._backgroundVideoPlayerHolder, RICK_ROLL_YOUTUBE_EMBED_ID, {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
origin: getMainOrgUrl(),
|
||||
playerVars: {
|
||||
controls: 0,
|
||||
disablekb: 1,
|
||||
fs: 0,
|
||||
iv_load_policy: 3,
|
||||
modestbranding: 1,
|
||||
},
|
||||
onPlayerStateChange: (_player, event) => {
|
||||
if (event.data === PlayerState.Playing) {
|
||||
this._isRickRowing.set(true);
|
||||
}
|
||||
},
|
||||
}, cssYouTubePlayer.cls(''));
|
||||
this._showBackgroundVideoPlayer.set(true);
|
||||
this._waitForView()
|
||||
.then(() => {
|
||||
const cursor = document.querySelector('.selected_cursor.active_cursor');
|
||||
if (cursor) {
|
||||
this.behavioralPromptsManager.showTip(cursor, 'rickRow', {
|
||||
forceShow: true,
|
||||
hideDontShowTips: true,
|
||||
markAsSeen: false,
|
||||
showOnMobile: true,
|
||||
onDispose: () => this.playRickRollVideo(),
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(reportError);
|
||||
}
|
||||
} catch (e) {
|
||||
reportError(e);
|
||||
} finally {
|
||||
@@ -482,6 +524,14 @@ export class GristDoc extends DisposableWithEvents {
|
||||
return cssViewContentPane(
|
||||
testId('gristdoc'),
|
||||
cssViewContentPane.cls("-contents", isPopup),
|
||||
dom.maybe(this._isRickRowing, () => cssStopRickRowingButton(
|
||||
cssCloseIcon('CrossBig'),
|
||||
dom.on('click', () => {
|
||||
this._isRickRowing.set(false);
|
||||
this._showBackgroundVideoPlayer.set(false);
|
||||
}),
|
||||
testId('gristdoc-stop-rick-rowing'),
|
||||
)),
|
||||
dom.domComputed(this._activeContent, (content) => {
|
||||
return (
|
||||
content === 'code' ? dom.create(CodeEditorPanel, this) :
|
||||
@@ -504,6 +554,13 @@ export class GristDoc extends DisposableWithEvents {
|
||||
})
|
||||
);
|
||||
}),
|
||||
dom.maybe(this._showBackgroundVideoPlayer, () => [
|
||||
cssBackgroundVideo(
|
||||
this._backgroundVideoPlayerHolder.get()?.buildDom(),
|
||||
cssBackgroundVideo.cls('-fade-in-and-out', this._isRickRowing),
|
||||
testId('gristdoc-background-video'),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1123,6 +1180,41 @@ export class GristDoc extends DisposableWithEvents {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts playing the music video for Never Gonna Give You Up in the background.
|
||||
*/
|
||||
public async playRickRollVideo() {
|
||||
const backgroundVideoPlayer = this._backgroundVideoPlayerHolder.get();
|
||||
if (!backgroundVideoPlayer) { return; }
|
||||
|
||||
await backgroundVideoPlayer.isLoaded();
|
||||
backgroundVideoPlayer.play();
|
||||
|
||||
const setVolume = async (start: number, end: number, step: number) => {
|
||||
let volume: number;
|
||||
const condition = start <= end
|
||||
? () => volume <= end
|
||||
: () => volume >= end;
|
||||
const afterthought = start <= end
|
||||
? () => volume += step
|
||||
: () => volume -= step;
|
||||
for (volume = start; condition(); afterthought()) {
|
||||
backgroundVideoPlayer.setVolume(volume);
|
||||
await delay(250);
|
||||
}
|
||||
};
|
||||
|
||||
await setVolume(0, 100, 5);
|
||||
|
||||
await delay(190 * 1000);
|
||||
if (!this._isRickRowing.get()) { return; }
|
||||
|
||||
await setVolume(100, 0, 5);
|
||||
|
||||
this._isRickRowing.set(false);
|
||||
this._showBackgroundVideoPlayer.set(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a view to be ready
|
||||
*/
|
||||
@@ -1448,3 +1540,63 @@ const cssViewContentPane = styled('div', `
|
||||
overflow: hidden;
|
||||
}
|
||||
`);
|
||||
|
||||
const fadeInAndOut = keyframes(`
|
||||
0% {
|
||||
opacity: 0.01;
|
||||
}
|
||||
5%, 95% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.01;
|
||||
}
|
||||
`);
|
||||
|
||||
const cssBackgroundVideo = styled('div', `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
&-fade-in-and-out {
|
||||
animation: ${fadeInAndOut} 200s;
|
||||
}
|
||||
`);
|
||||
|
||||
const cssYouTubePlayer = styled('div', `
|
||||
position: absolute;
|
||||
width: 450%;
|
||||
height: 450%;
|
||||
top: -175%;
|
||||
left: -175%;
|
||||
|
||||
@media ${mediaXSmall} {
|
||||
& {
|
||||
width: 450%;
|
||||
height: 450%;
|
||||
top: -175%;
|
||||
left: -175%;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const cssStopRickRowingButton = styled('div', `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 8px;
|
||||
margin: 16px;
|
||||
border-radius: 24px;
|
||||
background-color: ${theme.toastBg};
|
||||
cursor: pointer;
|
||||
`);
|
||||
|
||||
const cssCloseIcon = styled(icon, `
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
--icon-color: ${theme.toastControlFg};
|
||||
`);
|
||||
|
||||
@@ -4,7 +4,7 @@ import {FocusLayer} from 'app/client/lib/FocusLayer';
|
||||
import {reportSuccess} from 'app/client/models/errors';
|
||||
import {basicButton, bigPrimaryButton, primaryButton} from 'app/client/ui2018/buttons';
|
||||
import {labeledSquareCheckbox} from 'app/client/ui2018/checkbox';
|
||||
import {testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||
import {mediaXSmall, testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||
import {icon} from 'app/client/ui2018/icons';
|
||||
import {cssModalTooltip, modalTooltip} from 'app/client/ui2018/modals';
|
||||
import {dom, DomContents, keyframes, observable, styled, svg} from 'grainjs';
|
||||
@@ -138,6 +138,8 @@ export interface ShowBehavioralPromptOptions {
|
||||
onClose: (dontShowTips: boolean) => void;
|
||||
/** Defaults to false. */
|
||||
hideArrow?: boolean;
|
||||
/** Defaults to false. */
|
||||
hideDontShowTips?: boolean;
|
||||
popupOptions?: IPopupOptions;
|
||||
}
|
||||
|
||||
@@ -147,7 +149,7 @@ export function showBehavioralPrompt(
|
||||
content: DomContents,
|
||||
options: ShowBehavioralPromptOptions
|
||||
) {
|
||||
const {onClose, hideArrow, popupOptions} = options;
|
||||
const {onClose, hideArrow = false, hideDontShowTips = false, popupOptions} = options;
|
||||
const arrow = hideArrow ? null : buildArrow();
|
||||
const dontShowTips = observable(false);
|
||||
const tooltip = modalTooltip(refElement,
|
||||
@@ -180,6 +182,7 @@ export function showBehavioralPrompt(
|
||||
cssSkipTipsCheckboxLabel("Don't show tips"),
|
||||
testId('behavioral-prompt-dont-show-tips')
|
||||
),
|
||||
dom.style('visibility', hideDontShowTips ? 'hidden' : ''),
|
||||
),
|
||||
cssDismissPromptButton('Got it', testId('behavioral-prompt-dismiss'),
|
||||
dom.on('click', () => { onClose(dontShowTips.get()); ctl.close(); })
|
||||
@@ -194,6 +197,10 @@ export function showBehavioralPrompt(
|
||||
offset: {
|
||||
offset: '0,12',
|
||||
},
|
||||
preventOverflow: {
|
||||
boundariesElement: 'window',
|
||||
padding: 32,
|
||||
},
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -340,6 +347,12 @@ const cssBehavioralPromptModal = styled('div', `
|
||||
&[x-placement^=right] {
|
||||
animation-name: ${cssFadeInFromRight};
|
||||
}
|
||||
|
||||
@media ${mediaXSmall} {
|
||||
& {
|
||||
width: 320px;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const cssBehavioralPromptContainer = styled(cssTheme, `
|
||||
|
||||
Reference in New Issue
Block a user