import utils from "../../services/utils"; import options from "../../services/options"; import { t } from "../../services/i18n"; /** * A "call-to-action" is an interactive message for the user, generally to present new features. */ export interface CallToAction { /** * A unique identifier to allow the call-to-action to be dismissed by the user and then never shown again. */ id: string; /** * The title of the call-to-action, displayed as a heading in the message. */ title: string; /** * The message body of the call-to-action. */ message: string; /** * Function that determines whether the call-to-action can be displayed to the user. The check can be based on options or * the platform of the user. If the user already dismissed this call-to-action, the value of this function doesn't matter. * * @returns whether to allow this call-to-action or to skip it, based on the user's environment. */ enabled: () => boolean; /** * A list of buttons to display in the footer of the modal. */ buttons: { /** * The text displayed on the button. */ text: string; /** * The listener that will be called when the button is pressed. Async methods are supported and will be awaited before proceeding to the next step. */ onClick: () => (void | Promise); }[]; } function isNextTheme() { return [ "next", "next-light", "next-dark" ].includes(options.get("theme")); } const CALL_TO_ACTIONS: CallToAction[] = [ { id: "next_theme", title: t("call_to_action.next_theme_title"), message: t("call_to_action.next_theme_message"), enabled: () => !isNextTheme(), buttons: [ { text: t("call_to_action.next_theme_button"), async onClick() { await options.save("theme", "next"); await options.save("backgroundEffects", "true"); utils.reloadFrontendApp("call-to-action"); } } ] }, { id: "background_effects", title: t("call_to_action.background_effects_title"), message: t("call_to_action.background_effects_message"), enabled: () => false, buttons: [ { text: t("call_to_action.background_effects_button"), async onClick() { await options.save("backgroundEffects", "true"); utils.restartDesktopApp(); } } ] } ]; /** * Obtains the list of available call-to-actions, meaning those that are enabled based on the user's environment but also with * without the call-to-actions already dismissed by the user. * * @returns a list iof call to actions to display to the user. */ export function getCallToActions() { const seenCallToActions = new Set(getSeenCallToActions()); return CALL_TO_ACTIONS.filter((callToAction) => !seenCallToActions.has(callToAction.id) && callToAction.enabled()); } /** * Marks the call-to-action as dismissed by the user, meaning that {@link getCallToActions()} will no longer list this particular action. * * @param id the corresponding ID of the call to action. * @returns a promise with the option saving. Generally it's best to wait for the promise to resolve before refreshing the page. */ export async function dismissCallToAction(id: string) { const seenCallToActions = getSeenCallToActions(); if (seenCallToActions.find(seenId => seenId === id)) { return; } seenCallToActions.push(id); await options.save("seenCallToActions", JSON.stringify(seenCallToActions)); } function getSeenCallToActions() { try { return JSON.parse(options.get("seenCallToActions")) as string[]; } catch (e) { return []; } }