From aefe451bab05543f7a1950a2158aee09e4db4062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?= Date: Wed, 29 Jun 2022 12:19:20 +0200 Subject: [PATCH] (core) Polishing upgrade plan UI Summary: - Update nudge boxes content and collapsing on personal and free team site - New confirmation after upgrading from a free team site - Refactoring ProductUpgrade code, splitting plans / modals and nudges Test Plan: Manual and updated tests Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D3481 --- app/client/lib/localStorageObs.ts | 12 +++++ app/client/models/AppModel.ts | 39 ++++++++++----- app/client/models/UserPrefs.ts | 8 ++-- app/client/ui/BillingPage.ts | 13 ++++- app/client/ui/DocMenu.ts | 51 +++++++++----------- app/client/ui/ProductUpgradesStub.ts | 36 ++++++++------ app/client/ui2018/IconList.ts | 4 ++ app/common/BillingAPI.ts | 2 +- app/common/Features.ts | 7 ++- app/common/Prefs.ts | 3 -- app/gen-server/entity/Product.ts | 14 +++--- static/icons/icons.css | 2 + static/ui-icons/UI/Fireworks.svg | 9 ++++ static/ui-icons/UI/PublicColor.svg | 72 ++++++++++++++++++++++++++++ stubs/app/server/server.ts | 3 +- test/nbrowser/testUtils.ts | 4 +- 16 files changed, 203 insertions(+), 76 deletions(-) create mode 100644 static/ui-icons/UI/Fireworks.svg create mode 100644 static/ui-icons/UI/PublicColor.svg diff --git a/app/client/lib/localStorageObs.ts b/app/client/lib/localStorageObs.ts index e5356a79..d4a537c6 100644 --- a/app/client/lib/localStorageObs.ts +++ b/app/client/lib/localStorageObs.ts @@ -1,3 +1,4 @@ +import {safeJsonParse} from 'app/common/gutil'; import {Observable} from 'grainjs'; /** @@ -96,3 +97,14 @@ export function localStorageObs(key: string, defaultValue?: string): Observable< obs.addListener((val) => (val === null) ? store.removeItem(key) : store.setItem(key, val)); return obs; } + +/** + * Helper to create a JSON observable whose state is stored in localStorage. + */ + export function localStorageJsonObs(key: string, defaultValue: T): Observable { + const store = getStorage(); + const currentValue = safeJsonParse(store.getItem(key) || '', defaultValue ?? null); + const obs = Observable.create(null, currentValue); + obs.addListener((val) => (val === null) ? store.removeItem(key) : store.setItem(key, JSON.stringify(val ?? null))); + return obs; +} diff --git a/app/client/models/AppModel.ts b/app/client/models/AppModel.ts index 02efbdcf..47b0ca47 100644 --- a/app/client/models/AppModel.ts +++ b/app/client/models/AppModel.ts @@ -50,6 +50,10 @@ export interface TopAppModel { * Returns the UntrustedContentOrigin use settings. Throws if not defined. */ getUntrustedContentOrigin(): string; + /** + * Reloads orgs and accounts for current user. + */ + fetchUsersAndOrgs(): Promise; } // AppModel is specific to the currently loaded organization and active user. It gets rebuilt when @@ -110,7 +114,7 @@ export class TopAppModelImpl extends Disposable implements TopAppModel { this.autoDispose(subscribe(this.currentSubdomain, (use) => this.initialize())); this.plugins = this._gristConfig?.plugins || []; - this._fetchUsersAndOrgs().catch(reportError); + this.fetchUsersAndOrgs().catch(reportError); } public initialize(): void { @@ -143,6 +147,15 @@ export class TopAppModelImpl extends Disposable implements TopAppModel { return origin + ":" + G.window.location.port; } + public async fetchUsersAndOrgs() { + const data = await this.api.getSessionAll(); + if (this.isDisposed()) { return; } + bundleChanges(() => { + this.users.set(data.users); + this.orgs.set(data.orgs); + }); + } + private async _doInitialize() { this.appObs.set(null); try { @@ -172,15 +185,6 @@ export class TopAppModelImpl extends Disposable implements TopAppModel { AppModelImpl.create(this.appObs, this, null, null, {error: err.message, status: err.status || 500}); } } - - private async _fetchUsersAndOrgs() { - const data = await this.api.getSessionAll(); - if (this.isDisposed()) { return; } - bundleChanges(() => { - this.users.set(data.users); - this.orgs.set(data.orgs); - }); - } } export class AppModelImpl extends Disposable implements AppModel { @@ -225,13 +229,22 @@ export class AppModelImpl extends Disposable implements AppModel { public async showUpgradeModal() { if (this.planName && this.currentOrg) { - buildUpgradeModal(this, this.planName); + if (this.isPersonal) { + this.showNewSiteModal(); + } else if (this.isTeamSite) { + buildUpgradeModal(this, this.planName); + } else { + throw new Error("Unexpected state"); + } } } - public async showNewSiteModal() { + public showNewSiteModal() { if (this.planName) { - buildNewSiteModal(this, this.planName); + buildNewSiteModal(this, { + planName: this.planName, + onCreate: () => this.topAppModel.fetchUsersAndOrgs().catch(reportError) + }); } } diff --git a/app/client/models/UserPrefs.ts b/app/client/models/UserPrefs.ts index 543879bd..90cb17be 100644 --- a/app/client/models/UserPrefs.ts +++ b/app/client/models/UserPrefs.ts @@ -51,10 +51,10 @@ function makePrefFunctions

(prefsTypeName: P) { } // Functions actually exported are: -// - getUserOrgPrefsObs(appModel): Observsble -// - getUserOrgPrefObs(userOrgPrefsObs, prefName): Observsble -// - getUserPrefsObs(appModel): Observsble -// - getUserPrefObs(userPrefsObs, prefName): Observsble +// - getUserOrgPrefsObs(appModel): Observable +// - getUserOrgPrefObs(userOrgPrefsObs, prefName): Observable +// - getUserPrefsObs(appModel): Observable +// - getUserPrefObs(userPrefsObs, prefName): Observable export const {getPrefsObs: getUserOrgPrefsObs, getPrefObs: getUserOrgPrefObs} = makePrefFunctions('userOrgPrefs'); export const {getPrefsObs: getUserPrefsObs, getPrefObs: getUserPrefObs} = makePrefFunctions('userPrefs'); diff --git a/app/client/ui/BillingPage.ts b/app/client/ui/BillingPage.ts index c579c92c..0fc55cc9 100644 --- a/app/client/ui/BillingPage.ts +++ b/app/client/ui/BillingPage.ts @@ -14,11 +14,12 @@ import { createTopBarHome } from 'app/client/ui/TopBar'; import { cssBreadcrumbs, cssBreadcrumbsLink, separator } from 'app/client/ui2018/breadcrumbs'; import { bigBasicButton, bigBasicButtonLink, bigPrimaryButton } from 'app/client/ui2018/buttons'; import { loadingSpinner } from 'app/client/ui2018/loaders'; +import { NEW_DEAL, showTeamUpgradeConfirmation } from 'app/client/ui/ProductUpgrades'; +import { IconName } from 'app/client/ui2018/IconList'; import { BillingTask, IBillingCoupon } from 'app/common/BillingAPI'; import { capitalize } from 'app/common/gutil'; import { Organization } from 'app/common/UserAPI'; import { Disposable, dom, DomArg, IAttrObj, makeTestId, Observable } from 'grainjs'; -import { IconName } from '../ui2018/IconList'; const testId = makeTestId('test-bp-'); const billingTasksNames = { @@ -26,6 +27,7 @@ const billingTasksNames = { signUpLite: 'Complete Sign Up', // task for payment page updateDomain: 'Update Name', // task for summary page cancelPlan: 'Cancel plan', // this is not a task, but a sub page + upgraded: 'Account', }; /** @@ -41,6 +43,8 @@ export class BillingPage extends Disposable { constructor(private _appModel: AppModel) { super(); + // TODO: remove once NEW_DEAL is there. Execute for side effect + void NEW_DEAL(); this._appModel.refreshOrgUsage().catch(reportError); } @@ -77,7 +81,7 @@ export class BillingPage extends Disposable { * Builds the contentMain dom for the current billing page. */ private _buildCurrentPageDom() { - return css.billingWrapper( + const page = css.billingWrapper( dom.domComputed(this._model.currentSubpage, (subpage) => { if (!subpage) { return this._buildSummaryPage(); @@ -86,6 +90,11 @@ export class BillingPage extends Disposable { } }) ); + if (this._model.currentTask.get() === 'upgraded') { + urlState().pushUrl({params: {}}, { replace: true }).catch(() => {}); + showTeamUpgradeConfirmation(this); + } + return page; } private _buildSummaryPage() { diff --git a/app/client/ui/DocMenu.ts b/app/client/ui/DocMenu.ts index 71f6e037..182787ad 100644 --- a/app/client/ui/DocMenu.ts +++ b/app/client/ui/DocMenu.ts @@ -10,12 +10,12 @@ import {getTimeFromNow, HomeModel, makeLocalViewSettings, ViewSettings} from 'ap import {getWorkspaceInfo, workspaceName} from 'app/client/models/WorkspaceInfo'; import * as css from 'app/client/ui/DocMenuCss'; import {buildHomeIntro} from 'app/client/ui/HomeIntro'; -import {createVideoTourTextButton} from 'app/client/ui/OpenVideoTour'; -import {buildUpgradeNudge} from 'app/client/ui/ProductUpgrades'; +import {buildUpgradeButton} from 'app/client/ui/ProductUpgrades'; import {buildPinnedDoc, createPinnedDocs} from 'app/client/ui/PinnedDocs'; import {shadowScroll} from 'app/client/ui/shadowScroll'; import {transition} from 'app/client/ui/transitions'; import {showWelcomeQuestions} from 'app/client/ui/WelcomeQuestions'; +import {createVideoTourTextButton} from 'app/client/ui/OpenVideoTour'; import {buttonSelect, cssButtonSelect} from 'app/client/ui2018/buttonSelect'; import {colors, isNarrowScreenObs} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; @@ -26,12 +26,12 @@ import {IHomePage} from 'app/common/gristUrls'; import {SortPref, ViewPref} from 'app/common/Prefs'; import * as roles from 'app/common/roles'; import {Document, Workspace} from 'app/common/UserAPI'; -import {Computed, computed, dom, DomContents, makeTestId, Observable, observable} from 'grainjs'; -import sortBy = require('lodash/sortBy'); +import {computed, Computed, dom, DomArg, DomContents, IDisposableOwner, + makeTestId, observable, Observable} from 'grainjs'; import {buildTemplateDocs} from 'app/client/ui/TemplateDocs'; import {localStorageBoolObs} from 'app/client/lib/localStorageObs'; import {bigBasicButton} from 'app/client/ui2018/buttons'; -import {getUserOrgPrefObs, getUserOrgPrefsObs} from 'app/client/models/UserPrefs'; +import sortBy = require('lodash/sortBy'); const testId = makeTestId('test-dm-'); @@ -45,27 +45,14 @@ export function createDocMenu(home: HomeModel) { return dom.domComputed(home.loading, loading => ( loading === 'slow' ? css.spinner(loadingSpinner()) : loading ? null : - createLoadedDocMenu(home) + dom.create(createLoadedDocMenu, home) )); } -function createUpgradeNudge(home: HomeModel) { - const isLoggedIn = !!home.app.currentValidUser; - const isOnFreePersonal = home.app.currentOrg?.billingAccount?.product?.name === 'starter'; - const userOrgPrefs = getUserOrgPrefsObs(home.app); - const seenNudge = getUserOrgPrefObs(userOrgPrefs, 'seenFreeTeamUpgradeNudge'); - return dom.maybe(use => isLoggedIn && isOnFreePersonal && !use(seenNudge), - () => buildUpgradeNudge({ - onClose: () => seenNudge.set(true), - // On show prices, we will clear the nudge in database once there is some free team site created - // The better way is to read all workspaces that this person have and decide then - but this is done - // asynchronously - so we potentially can show this nudge to people that already have team site. - onUpgrade: () => home.app.showUpgradeModal() - })); -} -function createLoadedDocMenu(home: HomeModel) { +function createLoadedDocMenu(owner: IDisposableOwner, home: HomeModel) { const flashDocId = observable(null); + const upgradeButton = buildUpgradeButton(owner, home.app); return css.docList( showWelcomeQuestions(home.app.userPrefsObs), css.docMenu( @@ -83,14 +70,16 @@ function createLoadedDocMenu(home: HomeModel) { page === 'templates' ? makeLocalViewSettings(home, 'templates') : workspace ? makeLocalViewSettings(home, workspace.id) : home; - return [ // Hide the sort option only when showing intro. - ((showIntro && page === 'all') ? null : - buildPrefs(viewSettings, {hideSort: showIntro}) + ((showIntro && page === 'all') ? css.prefSelectors(upgradeButton.showUpgradeButton()) : + // This is float:right element + buildPrefs(viewSettings, {hideSort: showIntro}, upgradeButton.showUpgradeButton()) ), - // Build the pinned docs dom. Builds nothing if the selectedOrg is unloaded or + // Build the pinned docs dom. Builds nothing if the selectedOrg is unloaded. + // TODO: this is shown on all pages, but there is a hack in currentWSPinnedDocs that + // removes all pinned docs when on trash page. dom.maybe((use) => use(home.currentWSPinnedDocs).length > 0, () => [ css.docListHeader(css.docHeaderIconDark('PinBig'), 'Pinned Documents'), createPinnedDocs(home, home.currentWSPinnedDocs), @@ -128,7 +117,8 @@ function createLoadedDocMenu(home: HomeModel) { dom('div', showIntro ? buildHomeIntro(home) : null, buildAllDocsBlock(home, home.workspaces, showIntro, flashDocId, viewSettings), - dom.maybe(use => use(isNarrowScreenObs()), () => createUpgradeNudge(home)), + dom.maybe(use => use(isNarrowScreenObs()), + () => upgradeButton.showUpgradeCard()), shouldShowTemplates(home, showIntro) ? buildAllDocsTemplates(home, viewSettings) : null, ) : (page === 'trash') ? @@ -155,7 +145,8 @@ function createLoadedDocMenu(home: HomeModel) { }), testId('doclist') ), - dom.maybe(use => !use(isNarrowScreenObs()) && use(home.currentPage) === 'all', () => createUpgradeNudge(home)), + dom.maybe(use => !use(isNarrowScreenObs()) && ['all', 'workspace'].includes(use(home.currentPage)), + () => upgradeButton.showUpgradeCard()), ); } @@ -309,7 +300,10 @@ function buildOtherSites(home: HomeModel) { * If hideSort is true, will hide the sort dropdown: it has no effect on the list of examples, so * best to hide when those are the only docs shown. */ -function buildPrefs(viewSettings: ViewSettings, options: {hideSort: boolean}): DomContents { +function buildPrefs( + viewSettings: ViewSettings, + options: {hideSort: boolean}, + ...args: DomArg[]): DomContents { return css.prefSelectors( // The Sort selector. options.hideSort ? null : dom.update( @@ -330,6 +324,7 @@ function buildPrefs(viewSettings: ViewSettings, options: {hideSort: boolean}): D cssButtonSelect.cls("-light"), testId('view-mode') ), + ...args ); } diff --git a/app/client/ui/ProductUpgradesStub.ts b/app/client/ui/ProductUpgradesStub.ts index 5e078ffb..07b05944 100644 --- a/app/client/ui/ProductUpgradesStub.ts +++ b/app/client/ui/ProductUpgradesStub.ts @@ -1,15 +1,11 @@ import type {AppModel} from 'app/client/models/AppModel'; import {commonUrls} from 'app/common/gristUrls'; -import {Disposable} from 'grainjs'; +import {Disposable, DomContents, IDisposableOwner, Observable, observable} from 'grainjs'; -export function buildUpgradeNudge(options: { - onClose: () => void; - onUpgrade: () => void +export function buildNewSiteModal(context: Disposable, options: { + planName: string, + onCreate?: () => void }) { - return null; -} - -export function buildNewSiteModal(owner: Disposable, current: string | null) { window.location.href = commonUrls.plans; } @@ -17,11 +13,21 @@ export function buildUpgradeModal(owner: Disposable, planName: string) { window.location.href = commonUrls.plans; } -export class UpgradeButton extends Disposable { - constructor(appModel: AppModel) { - super(); - } - public buildDom() { - return null; - } +export function showTeamUpgradeConfirmation(owner: Disposable) { +} + +export interface UpgradeButton { + showUpgradeCard(): DomContents; + showUpgradeButton(): DomContents; +} + +export function buildUpgradeButton(owner: IDisposableOwner, app: AppModel): UpgradeButton { + return { + showUpgradeCard : () => null, + showUpgradeButton : () => null, + }; +} + +export function NEW_DEAL(): Observable { + return observable(false); } diff --git a/app/client/ui2018/IconList.ts b/app/client/ui2018/IconList.ts index 12957be1..c40d9c2d 100644 --- a/app/client/ui2018/IconList.ts +++ b/app/client/ui2018/IconList.ts @@ -58,6 +58,7 @@ export type IconName = "ChartArea" | "Feedback" | "Filter" | "FilterSimple" | + "Fireworks" | "Folder" | "FontBold" | "FontItalic" | @@ -89,6 +90,7 @@ export type IconName = "ChartArea" | "Pivot" | "Plus" | "Public" | + "PublicColor" | "PublicFilled" | "Redo" | "Remove" | @@ -181,6 +183,7 @@ export const IconList: IconName[] = ["ChartArea", "Feedback", "Filter", "FilterSimple", + "Fireworks", "Folder", "FontBold", "FontItalic", @@ -212,6 +215,7 @@ export const IconList: IconName[] = ["ChartArea", "Pivot", "Plus", "Public", + "PublicColor", "PublicFilled", "Redo", "Remove", diff --git a/app/common/BillingAPI.ts b/app/common/BillingAPI.ts index 3a7900ae..54e40cf3 100644 --- a/app/common/BillingAPI.ts +++ b/app/common/BillingAPI.ts @@ -15,7 +15,7 @@ export type BillingPage = typeof BillingPage.type; // signUpLite - it is a subpage for payment, to finalize (complete) signup process // and set domain and team name when they are not set yet (currently only from landing pages). // signUp - it is landing page for new team sites (it doesn't ask for the name of the team) -export const BillingTask = StringUnion('signUpLite', 'updateDomain', 'signUp', 'cancelPlan'); +export const BillingTask = StringUnion('signUpLite', 'updateDomain', 'signUp', 'cancelPlan', 'upgraded'); export type BillingTask = typeof BillingTask.type; // Note that IBillingPlan includes selected fields from the Stripe plan object along with diff --git a/app/common/Features.ts b/app/common/Features.ts index 0f18d146..4459d8ae 100644 --- a/app/common/Features.ts +++ b/app/common/Features.ts @@ -68,7 +68,12 @@ export function canAddOrgMembers(features: Features): boolean { return features.maxWorkspacesPerOrg !== 1; } + +export const FREE_PERSONAL_PLAN = 'starter'; +export const TEAM_FREE_PLAN = 'teamFree'; +export const TEAM_PLAN = 'team'; + // Returns true if `product` is free. export function isFreeProduct(product: Product): boolean { - return ['starter', 'teamFree', 'Free'].includes(product?.name); + return [FREE_PERSONAL_PLAN, TEAM_FREE_PLAN, 'Free'].includes(product?.name); } diff --git a/app/common/Prefs.ts b/app/common/Prefs.ts index 2bb75e82..c1d6ee8b 100644 --- a/app/common/Prefs.ts +++ b/app/common/Prefs.ts @@ -38,9 +38,6 @@ export interface UserOrgPrefs extends Prefs { // List of document IDs where the user has seen and dismissed the document tour. seenDocTours?: string[]; - - // Whether the user seen the nudge to upgrade to Free Team Site and dismissed it. - seenFreeTeamUpgradeNudge?: boolean; } export type OrgPrefs = Prefs; diff --git a/app/gen-server/entity/Product.ts b/app/gen-server/entity/Product.ts index 8cb14876..ee3c4709 100644 --- a/app/gen-server/entity/Product.ts +++ b/app/gen-server/entity/Product.ts @@ -1,4 +1,4 @@ -import {Features, Product as IProduct} from 'app/common/Features'; +import {Features, FREE_PERSONAL_PLAN, Product as IProduct, TEAM_FREE_PLAN, TEAM_PLAN} from 'app/common/Features'; import {nativeValues} from 'app/gen-server/lib/values'; import * as assert from 'assert'; import {BillingAccount} from 'app/gen-server/entity/BillingAccount'; @@ -100,7 +100,7 @@ export const PRODUCTS: IProduct[] = [ // These are products set up in stripe. // TODO: this is not true anymore { - name: 'starter', + name: FREE_PERSONAL_PLAN, features: starterFeatures, }, { @@ -108,7 +108,7 @@ export const PRODUCTS: IProduct[] = [ features: teamFeatures, }, { - name: 'team', + name: TEAM_PLAN, features: teamFeatures }, @@ -119,7 +119,7 @@ export const PRODUCTS: IProduct[] = [ features: suspendedFeatures }, { - name: 'teamFree', + name: TEAM_FREE_PLAN, features: teamFreeFeatures }, ]; @@ -131,11 +131,11 @@ export const PRODUCTS: IProduct[] = [ export function getDefaultProductNames() { const defaultProduct = process.env.GRIST_DEFAULT_PRODUCT; return { - personal: defaultProduct || 'starter', // Personal site start off on a functional plan. + personal: defaultProduct || FREE_PERSONAL_PLAN, // Personal site start off on a functional plan. teamInitial: defaultProduct || 'stub', // Team site starts off on a limited plan, requiring subscription. teamCancel: 'suspended', // Team site that has been 'turned off'. - team: defaultProduct || 'team', // Functional team site. - teamFree: defaultProduct || 'teamFree', + team: defaultProduct || TEAM_PLAN, // Functional team site. + teamFree: defaultProduct || TEAM_FREE_PLAN, }; } diff --git a/static/icons/icons.css b/static/icons/icons.css index 9c10950c..671d2a82 100644 --- a/static/icons/icons.css +++ b/static/icons/icons.css @@ -59,6 +59,7 @@ --icon-Feedback: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTcuMjE4MjEzMyw5IEM3LjIyNTE1MTk3LDguOTk5ODU1MTUgNy4yMzIwODMyMSw4Ljk5OTg1NTY0IDcuMjM5MDAyNiw5IEw4LjUyNCw5IEw4LjcxMDQ2NjY3LDkuMDM2MDcwOTMgTDEzLDEwLjc2MDE1OTkgTDEzLDMuMjM5ODQwMTEgTDguNzEwNDY2NjcsNC45NjM5MjkwNyBMOC41MjQsNSBMNSw1IEMzLjg5NTQzMDUsNSAzLDUuODk1NDMwNSAzLDcgQzMsNy41MzA0MzI5OCAzLjIxMDcxMzY4LDguMDM5MTQwODEgMy41ODU3ODY0NCw4LjQxNDIxMzU2IEMzLjk2MDg1OTE5LDguNzg5Mjg2MzIgNC40Njk1NjcwMiw5IDUsOSBMNy4yMTgyMTMwNiw5IFogTTguMDQwNTAzNzksMTAgTDguODQ1MDUxNTMsMTEuNTk4MTIwMyBDOS4wNDQ1NTkzMywxMS45ODc3NjEzIDkuMDgxMDU2NTQsMTIuNDQwNzEyNSA4Ljk0NjUwOTQsMTIuODU3MjcwNSBDOC44MTE5NjIyNywxMy4yNzM4Mjg0IDguNTE3Mzk5NSwxMy42MTk4NDgxIDguMTI3NDkyODksMTMuODE5MjQ5NCBDNy4zMTYyMzEwMywxNC4yMzM3NTA0IDYuMzIyNTQ0NjIsMTMuOTEyNzkyMiA1LjkwNzc3MTk4LDEzLjEwMzQ4MDkgTDQuMjU2MTc5MDUsOS45MDYzMzQ3NyBDMy43MzkyNzk1OSw5Ljc3NDAyODg2IDMuMjYyMzY1MzEsOS41MDUwMDYgMi44Nzg2Nzk2Niw5LjEyMTMyMDM0IEMyLjMxNjA3MDUyLDguNTU4NzExMjEgMiw3Ljc5NTY0OTQ3IDIsNyBDMiw1LjM0MzE0NTc1IDMuMzQzMTQ1NzUsNCA1LDQgTDguNDI3Mjc3ODEsNCBMMTMuMzEzNTMzMywyLjAzNjA3MDkzIEMxMy42NDIwNzYyLDEuOTA0MDE5OTQgMTQsMi4xNDU5MTI2MiAxNCwyLjUgTDE0LDExLjUgQzE0LDExLjg1NDA4NzQgMTMuNjQyMDc2MiwxMi4wOTU5ODAxIDEzLjMxMzUzMzMsMTEuOTYzOTI5MSBMOC40MjcyNzc4MSwxMCBMOC4wNDA1MDM3OSwxMCBaIE01LjQzMDExMjQsMTAgTDYuNzk2OTY1ODQsMTIuNjQ1OTUzMSBDNi45NjA4MDQzMSwxMi45NjU2MzUyIDcuMzUyNjIxMzEsMTMuMDkyMTkxMSA3LjY3MjMzOTc4LDEyLjkyODgzNjIgQzcuODI1ODczNDMsMTIuODUwMzE3OSA3Ljk0MTkxMzMxLDEyLjcxNDAwNzEgNy45OTQ5MTY3MywxMi41NDk5MDg1IEM4LjA0NzkyMDE1LDEyLjM4NTgwOTkgOC4wMzM1NDI0NiwxMi4yMDczNzQ2IDcuOTUzNDAyNSwxMi4wNTA4MzQ4IEw2LjkyMDkzMTUxLDEwIEw1LjQzMDExMjQsMTAgWiIgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIi8+PC9zdmc+'); --icon-Filter: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuNSw0IEwxLjUsNCBDMS4yMjQsNCAxLDMuNzc2IDEsMy41IEMxLDMuMjI0IDEuMjI0LDMgMS41LDMgTDIuNSwzIEMyLjc3NiwzIDMsMy4yMjQgMywzLjUgQzMsMy43NzYgMi43NzYsNCAyLjUsNCBaIE00LjUsNyBMMS41LDcgQzEuMjI0LDcgMSw2Ljc3NiAxLDYuNSBDMSw2LjIyNCAxLjIyNCw2IDEuNSw2IEw0LjUsNiBDNC43NzYsNiA1LDYuMjI0IDUsNi41IEM1LDYuNzc2IDQuNzc2LDcgNC41LDcgWiBNNi41LDEwIEwxLjUsMTAgQzEuMjI0LDEwIDEsOS43NzYgMSw5LjUgQzEsOS4yMjQgMS4yMjQsOSAxLjUsOSBMNi41LDkgQzYuNzc2LDkgNyw5LjIyNCA3LDkuNSBDNyw5Ljc3NiA2Ljc3NiwxMCA2LjUsMTAgWiBNOC41LDEzIEwxLjUsMTMgQzEuMjI0LDEzIDEsMTIuNzc2IDEsMTIuNSBDMSwxMi4yMjQgMS4yMjQsMTIgMS41LDEyIEw4LjUsMTIgQzguNzc2LDEyIDksMTIuMjI0IDksMTIuNSBDOSwxMi43NzYgOC43NzYsMTMgOC41LDEzIFogTTE0Ljk3Mjg4OCwyLjI4NDkwODg1IEMxNS4wMjY1MzgyLDIuNDU3MzYxNzggMTQuOTk2ODg2NCwyLjY1NTk2NzI1IDE0Ljg5Nzg5MzIsMi43ODc4MDY4MyBMMTEuNjgxMzA2OCw3LjA3NjY2OTY1IEMxMS42MTE3MzQ3LDcuMTY5NTg4MDkgMTEuNTM4MDA5LDcuNDA2ODA3MDIgMTEuNTM4MDA5LDcuNTM4MTg1MDggTDExLjUzODAwOSwxMy41Mzc4ODU2IEMxMS41MzgwMDksMTMuNzI0NjQ1NSAxMS40NTM3ODQsMTMuODkyOTQ0OCAxMS4zMjQzMzE0LDEzLjk2NDE3MiBDMTEuMjgxNTI2NiwxMy45ODgwMTcgMTEuMjM2NTI5NywxMy45OTk0MDEgMTEuMTkxOTk0MywxMy45OTk0MDEgQzExLjEwMTg4NTEsMTMuOTk5NDAxIDExLjAxMzI3NTgsMTMuOTUyNDgwMyAxMC45NDcxNjUsMTMuODY0MTc3IEw5LjU2MjY0NDg2LDEyLjAxODExNTMgQzkuNDk3Njg3NzksMTEuOTMxNjU4MSA5LjQ2MTIyODc2LDExLjgxNDI3OTMgOS40NjEyMjg3NiwxMS42OTE4MjM5IEw5LjQ2MTIyODc2LDcuNTM4MTg1MDggQzkuNDYxMjI4NzYsNy40MDY4MDcwMiA5LjM4NzYxODQ0LDcuMTY5NTg4MDkgOS4zMTc5MzA5Miw3LjA3Njk3NzMzIEw2LjEwMTM0NDUyLDIuNzg3OTYwNjcgQzYuMDAyMzUxMzMsMi42NTU5NjcyNSA1Ljk3MjgxNDksMi40NTc1MTU2MiA2LjAyNjM0OTY4LDIuMjg0OTA4ODUgQzYuMDc5ODg0NDUsMi4xMTIzMDIwOSA2LjIwNjEwNjU0LDIgNi4zNDYwNTg0NSwyIEwxNC42NTMxNzkzLDIgQzE0Ljc5MzI0NjYsMiAxNC45MTk0Njg2LDIuMTEyMzAyMDkgMTQuOTcyODg4LDIuMjg0OTA4ODUgWiIgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIi8+PC9zdmc+'); --icon-FilterSimple: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTkuOTUwODMzMzMsMC4yMiBDOS44NzgyODY2NywwLjA4NDQ5NzcxMDggOS43MzcwMzQsLTYuMTY5Njc5NTFlLTA1IDkuNTgzMzMzMzMsLTQuNjI1OTMyOGUtMTcgTDAuNDE2NjY2NjY3LC00LjYyNTkzMjhlLTE3IEMwLjI2MzEwMzQ1NCw3LjkzMjczNDM1ZS0wNSAwLjEyMjAzMTQsMC4wODQ2MTg0ODc1IDAuMDQ5NTQ5NDAxMiwwLjIxOTk5OTUyOSBDLTAuMDIyOTMyNTk3OCwwLjM1NTM4MDU3MSAtMC4wMTUwNzQwNTE3LDAuNTE5NjU2MDYyIDAuMDcsMC42NDc1IEwzLjMzMzMzMzMzLDUuNTQyNSBMMy4zMzMzMzMzMyw5LjU4MzMzMzMzIEMzLjMzMzMzMzMzLDkuODEzNDUyIDMuNTE5ODgxMzYsMTAgMy43NSwxMCBMNi4yNSwxMCBDNi40ODAxMTg2NCwxMCA2LjY2NjY2NjY3LDkuODEzNDUyIDYuNjY2NjY2NjcsOS41ODMzMzMzMyBMNi42NjY2NjY2Nyw1LjU0MjUgTDkuOTMsMC42NDc1IEMxMC4wMTUxOTU2LDAuNTE5NzI3OTkxIDEwLjAyMzIwMTMsMC4zNTU0NTA1NDcgOS45NTA4MzMzMywwLjIyIFoiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMgMykiIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg=='); + --icon-Fireworks: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTguNjY2NDcgMTUuMzMzM0g3LjMzMzE0QzcuMzMzMTQgOS44NDc5NyA0LjQ0NzggOC42MzA2MyA0LjQxODQ3IDguNjE5M0wzLjc5OTggOC4zNzEzIDQuMjk1MTQgNy4xMzMzIDQuOTE0NDcgNy4zODA2M0M1LjAyNTE0IDcuNDI1OTcgNi45NTE4IDguMjM1MyA3Ljk5OTggMTEuMTE0IDkuMDQ3OCA4LjIzNTk3IDEwLjk3NDUgNy40MjY2MyAxMS4wODUxIDcuMzgwNjNMMTEuNzA0NSA3LjEzMzMgMTIuMTk5OCA4LjM3MTMgMTEuNTg0NSA4LjYxNzNDMTEuNDM5OCA4LjY3OTk3IDguNjY2NDcgOS45NDE5NyA4LjY2NjQ3IDE1LjMzMzN6TTEwLjQ1MzEgMS43OTA1NEw4Ljc1NzcyIDEuNTQ0NTQgNy45OTk3Mi4wMDg1NDQ5MiA3LjI0MTcyIDEuNTQ0NTQgNS41NDYzOSAxLjc5MDU0IDYuNzczMDUgMi45ODY1NCA2LjQ4MzcyIDQuNjc1MjEgNy45OTk3MiAzLjg3Nzg4IDkuNTE1NzIgNC42NzUyMSA5LjIyNjM5IDIuOTg2NTQgMTAuNDUzMSAxLjc5MDU0ek01LjI5OTE3IDEyLjM1OEwzLjY5ODUgMTIuMTI1MyAyLjk4MzE3IDEwLjY3NTMgMi4yNjcxNyAxMi4xMjUzLjY2NjUwNCAxMi4zNTggMS44MjQ1IDEzLjQ4NjYgMS41NTExNyAxNS4wODA2IDIuOTgzMTcgMTQuMzI4NiA0LjQxNDUgMTUuMDgwNiA0LjE0MTE3IDEzLjQ4NjYgNS4yOTkxNyAxMi4zNTh6TTE1LjMzMzMgMTIuMzU4TDEzLjczMjcgMTIuMTI1MyAxMy4wMTY3IDEwLjY3NTMgMTIuMzAxMyAxMi4xMjUzIDEwLjcwMDcgMTIuMzU4IDExLjg1ODcgMTMuNDg2NiAxMS41ODUzIDE1LjA4MDYgMTMuMDE2NyAxNC4zMjg2IDE0LjQ0ODcgMTUuMDgwNiAxNC4xNzUzIDEzLjQ4NjYgMTUuMzMzMyAxMi4zNTh6IiBmaWxsPSIjZmZmIi8+PHBhdGggZD0iTTguNjY2MzQgNy41MzMxM1Y1LjM0MThINy4zMzMwMVY3LjUyMDQ2QzcuNTc2MjEgNy43NjgxMSA3LjgwMjUgOC4wMzE4MSA4LjAxMDM0IDguMzA5OCA4LjIxMjIgOC4wMzcxNyA4LjQzMTMyIDcuNzc3NzUgOC42NjYzNCA3LjUzMzEzek0xLjY2NjUgNy4zMzMyNUMyLjIxODc5IDcuMzMzMjUgMi42NjY1IDYuODg1NTQgMi42NjY1IDYuMzMzMjUgMi42NjY1IDUuNzgwOTcgMi4yMTg3OSA1LjMzMzI1IDEuNjY2NSA1LjMzMzI1IDEuMTE0MjIgNS4zMzMyNS42NjY1MDQgNS43ODA5Ny42NjY1MDQgNi4zMzMyNS42NjY1MDQgNi44ODU1NCAxLjExNDIyIDcuMzMzMjUgMS42NjY1IDcuMzMzMjV6TTE0LjMzMyA3LjMzMzI1QzE0Ljg4NTMgNy4zMzMyNSAxNS4zMzMgNi44ODU1NCAxNS4zMzMgNi4zMzMyNSAxNS4zMzMgNS43ODA5NyAxNC44ODUzIDUuMzMzMjUgMTQuMzMzIDUuMzMzMjUgMTMuNzgwNyA1LjMzMzI1IDEzLjMzMyA1Ljc4MDk3IDEzLjMzMyA2LjMzMzI1IDEzLjMzMyA2Ljg4NTU0IDEzLjc4MDcgNy4zMzMyNSAxNC4zMzMgNy4zMzMyNXoiIGZpbGw9IiNmZmYiLz48L3N2Zz4='); --icon-Folder: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTMsNSBMMTMsNSBDMTMuNTUyMjg0Nyw1IDE0LDUuNDQ3NzE1MjUgMTQsNiBMMTQsMTMgQzE0LDEzLjU1MjI4NDcgMTMuNTUyMjg0NywxNCAxMywxNCBMMywxNCBDMi40NDc3MTUyNSwxNCAyLDEzLjU1MjI4NDcgMiwxMyBMMiw2IEMyLDUuNDQ3NzE1MjUgMi40NDc3MTUyNSw1IDMsNSBaIE00LDIgTDEyLDIgTDEyLDQgTDQsNCBMNCwyIFoiIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg=='); --icon-FontBold: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTMuMTk1MDk4OCAxLjgzNTQxOTloNS42OTAzODE4Yy43NTQ1OTUyIDAgMS40NzgyNzI0LjI5OTc1NjcgMi4wMTE4NTM0LjgzMzMzNzUuNTMzNTgxLjUzMzU4MDguODMzMzM3IDEuMjU3MjU4Mi44MzMzMzcgMi4wMTE4NTM1IDAgLjc1NDU5NTItLjI5OTc1NiAxLjQ3ODI3MjctLjgzMzMzNyAyLjAxMTg1MzVDMTAuMzYzNzUzIDcuMjI2MDQ1MSA5LjY0MDA3NTggNy41MjU4MDE4IDguODg1NDgwNiA3LjUyNTgwMThINS4wOTE4OTI3TTUuMDkxODkyNyA3LjUyNTgwMThoNS4yMTYxODMzYy44ODAzNTIgMCAxLjcyNDY1My4zNDk3MTgyIDIuMzQ3MTY5Ljk3MjIyMDcuNjIyNTAyLjYyMjUxNTIuOTcyMjIgMS40NjY4MTYxLjk3MjIyIDIuMzQ3MTY4NXYwYzAgLjg4MDM1My0uMzQ5NzE4IDEuNzI0NjU0LS45NzIyMiAyLjM0NzIxOS0uNjIyNTE2LjYyMjQwMi0xLjQ2NjgxNy45NzIxNy0yLjM0NzE2OS45NzIxN0gzLjE5NTA5ODhNNS4wOTE4OTI3IDEuODM1NDE5OVYxNC4xNjQ1OCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDMwMDAwIiBzdHJva2Utd2lkdGg9IjEuMjY1IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L3N2Zz4='); --icon-FontItalic: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PGRlZnM+PGNsaXBQYXRoIGlkPSJhIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMCAwSDEyVjEySDB6Ii8+PC9jbGlwUGF0aD48L2RlZnM+PGcgdHJhbnNmb3JtPSJtYXRyaXgoMS4xMzU2IDAgMCAxLjEzNTYgLjk4MyAxLjM5KSIgY2xpcC1wYXRoPSJ1cmwoI2EpIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGZpbGw9Im5vbmUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0ibSA0Ljg3NSwwLjM3NSBoIDQuNSIgc3Ryb2tlPSIjMDAwIi8+PHBhdGggZD0ibSAyLjYyNSwxMS42MjUgaCA0LjUiIHN0cm9rZT0iIzA2MDAwMCIvPjxwYXRoIGQ9Im0gNy4xMjUsMC4zNzUgLTIuMjUsMTEuMjUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW9wYWNpdHk9Ii45OSIvPjwvZz48L3N2Zz4='); @@ -90,6 +91,7 @@ --icon-Pivot: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTMsMSBMMTMsMSBDMTQuMTA0NTY5NSwxIDE1LDEuODk1NDMwNSAxNSwzIEwxNSwxMyBDMTUsMTQuMTA0NTY5NSAxNC4xMDQ1Njk1LDE1IDEzLDE1IEwzLDE1IEMxLjg5NTQzMDUsMTUgMSwxNC4xMDQ1Njk1IDEsMTMgTDEsMyBDMSwxLjg5NTQzMDUgMS44OTU0MzA1LDEgMywxIFogTTUuMzc1LDQuNSBMNS4zNzUsNC43ODU0NjM0MSBMNy42MzQ2NTAxLDguMDIyNzA3MzIgTDUuMzc1LDExLjA5NDUxMjIgTDUuMzc1LDExLjUgTDEwLjYyNSwxMS41IEwxMC42MjUsMTAuMDMyODE3MSBMOS44MjY0OTkwMSwxMC43MjY0MTQ2IEw2LjkxOTczMTYxLDEwLjcyNjQxNDYgTDYuOTE5NzMxNjEsMTAuNDE2OTYzNCBMOC45MDI4MzMsNy42MTU4NTM2NiBMNi45MTk3MzE2MSw0Ljk5MDg1MzY2IEw5Ljc3NDMxMjEzLDQuOTg3MzUzNjYgTDEwLjYyNSw2LjA5NTMxNzA3IEwxMC42MjUsNC41IEw1LjM3NSw0LjUgWiIgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIi8+PC9zdmc+'); --icon-Plus: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTguNSw3LjUgTDEzLDcuNSBDMTMuMjc2MTQyNCw3LjUgMTMuNSw3LjcyMzg1NzYzIDEzLjUsOCBDMTMuNSw4LjI3NjE0MjM3IDEzLjI3NjE0MjQsOC41IDEzLDguNSBMOC41LDguNSBMOC41LDEzIEM4LjUsMTMuMjc2MTQyNCA4LjI3NjE0MjM3LDEzLjUgOCwxMy41IEM3LjcyMzg1NzYzLDEzLjUgNy41LDEzLjI3NjE0MjQgNy41LDEzIEw3LjUsOC41IEwzLDguNSBDMi43MjM4NTc2Myw4LjUgMi41LDguMjc2MTQyMzcgMi41LDggQzIuNSw3LjcyMzg1NzYzIDIuNzIzODU3NjMsNy41IDMsNy41IEw3LjUsNy41IEw3LjUsMyBDNy41LDIuNzIzODU3NjMgNy43MjM4NTc2MywyLjUgOCwyLjUgQzguMjc2MTQyMzcsMi41IDguNSwyLjcyMzg1NzYzIDguNSwzIEw4LjUsNy41IFoiIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg=='); --icon-Public: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNMCAwSDE2VjE2SDB6Ii8+PGcgc3Ryb2tlPSIjMTZCMzc4IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik05Ljc3NSAxMS4yOTNMNy4zMTIgMTAuNTkzQzcuMTIyMjE2NjEgMTAuNTM4OTUxMSA2Ljk4MTk3MzAyIDEwLjM3ODMzNyA2Ljk1NCAxMC4xODNMNi43NDcgOC43MjZDNy44MTQxMjAyNiA4LjIzODMwNjE1IDguNDk5MDAyMDkgNy4xNzMyODE3IDguNSA2TDguNSA0LjYyNkM4LjUyNTE5NzU2IDIuOTczMDAzMjUgNy4yNDI1MDU0NyAxLjU5NDE1MzM3IDUuNTkyIDEuNSA0Ljc4MDUzMjcgMS40NzUxMDMyIDMuOTkzNjIwMiAxLjc4MDE0ODI3IDMuNDEwOTUzMjMgMi4zNDU0Nzg0NiAyLjgyODI4NjI1IDIuOTEwODA4NjQgMi40OTk2MTgxNiAzLjY4ODE1MDk1IDIuNSA0LjVMMi41IDZDMi41MDA5OTc5MSA3LjE3MzI4MTcgMy4xODU4Nzk3NCA4LjIzODMwNjE1IDQuMjUzIDguNzI2TDQuMDQ2IDEwLjE3OUM0LjAxODAyNjk4IDEwLjM3NDMzNyAzLjg3Nzc4MzM5IDEwLjUzNDk1MTEgMy42ODggMTAuNTg5TDEuMjI1IDExLjI4OUMuNzk1OTk1Njg4IDExLjQxMTcwNzIuNTAwMTk4MjMgMTEuODAzNzkxOC41IDEyLjI1TC41IDE0LjUgMTAuNSAxNC41IDEwLjUgMTIuMjU0QzEwLjQ5OTgwMTggMTEuODA3NzkxOCAxMC4yMDQwMDQzIDExLjQxNTcwNzIgOS43NzUgMTEuMjkzek0xMi41IDE0LjVMMTUuNSAxNC41IDE1LjUgMTEuMjgxQzE1LjQ5OTk4NzkgMTAuODIyMzIwNiAxNS4xODc5MzExIDEwLjQyMjQ1OTEgMTQuNzQzIDEwLjMxMUwxMS44MjYgOS41ODJDMTEuNjI4NDcxIDkuNTMyNzA4NDEgMTEuNDgwNTUyNCA5LjM2ODU3NDEgMTEuNDUyIDkuMTY3TDExLjI0NyA3LjcyNkMxMi4zMTQxMjAzIDcuMjM4MzA2MTUgMTIuOTk5MDAyMSA2LjE3MzI4MTcgMTMgNUwxMyAzLjYyNkMxMy4wMjUxOTc2IDEuOTczMDAzMjUgMTEuNzQyNTA1NS41OTQxNTMzNjUgMTAuMDkyLjUgOS41MzQ0NjYxNC40ODI3MzcyNzIgOC45ODMxNjAzOS42MjEyNTYzMDYgOC41LjkiLz48L2c+PC9nPjwvc3ZnPg=='); + --icon-PublicColor: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTQiIHdpZHRoPSIxNiI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNMCAwSDE2VjE2SDB6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIC0xKSIvPjxnIG9wYWNpdHk9Ii43ODMiPjxwYXRoIGQ9Ik0gMTUuMjc1LDEwLjI5MyAxMy4wODcsOS42NjggQyAxMi43MjY0NzgsOS41NjQ5MTc4IDEyLjQ1MzkyLDkuMjY4ODEwNSAxMi4zODEsOC45MDEgTCAxMi4yNDcsOC4yMjYgQyAxMy4zMTQxMiw3LjczODMwNjEgMTMuOTk5MDAyLDYuNjczMjgxNyAxNCw1LjUgViA0LjEyNiBDIDE0LjAyNTE5OCwyLjQ3MzAwMzMgMTIuNzQyNTA2LDEuMDk0MTUzNCAxMS4wOTIsMSA5Ljg3OTM3MDksMC45NjM0MTA4MiA4Ljc2NDA2NTUsMS42NjA3NzI3IDguMjY2LDIuNzY3IDguNzQzMDQ3OCwzLjQ2MTI3ODUgOC45OTg5MTk4LDQuMjgzNjI0NyA5LDUuMTI2IFYgNi41IEMgOC45OTgyMDc3LDYuODYzNzcwNiA4Ljk0NjA0NTYsNy4yMjU1NDA0IDguODQ1LDcuNTc1IDkuMTA0MjMyNSw3Ljg0Njk0MjMgOS40MTIyMzk1LDguMDY3NzcxMSA5Ljc1Myw4LjIyNiBMIDkuNjE5LDguOSBDIDkuNTQ2MDc5Niw5LjI2NzgxMDUgOS4yNzM1MjE5LDkuNTYzOTE3OCA4LjkxMyw5LjY2NyBMIDguMDcsOS45MDggOS41NSwxMC4zMzEgYyAwLjg1Njk2NCwwLjI0NzU1NCAxLjQ0NzcwNiwxLjAzMDk5OSAxLjQ1LDEuOTIzIFYgMTQuNSBjIC0wLjAwMTcsMC4xNzA3MiAtMC4wMzI3OCwwLjMzOTg3MSAtMC4wOTIsMC41IEggMTUuNSBjIDAuMjc2MTQyLDAgMC41LC0wLjIyMzg1OCAwLjUsLTAuNSB2IC0zLjI0NiBjIC0xLjk4ZS00LC0wLjQ0NjIwOCAtMC4yOTU5OTYsLTAuODM4MjkzIC0wLjcyNSwtMC45NjEgeiIgZmlsbD0iI2U2YTExNyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtMSkiLz48cGF0aCBkPSJNIDkuMjc1LDExLjI5MyA3LjA4NywxMC42NjggQyA2LjcyNjIwMjMsMTAuNTY0NzU0IDYuNDUzNTgzLDEwLjI2ODE5NCA2LjM4MSw5LjkgTCA2LjI0Nyw5LjIyNSBDIDcuMzEzNzkxNCw4LjczNzQ1NjkgNy45OTg2MTEyLDcuNjcyOTE5NSA4LDYuNSBWIDUuMTI2IEMgOC4wMjUxOTc2LDMuNDczMDAzMyA2Ljc0MjUwNTUsMi4wOTQxNTM0IDUuMDkyLDIgNC4yODA1MzI3LDEuOTc1MTAzMiAzLjQ5MzYyMDIsMi4yODAxNDgzIDIuOTEwOTUzMiwyLjg0NTQ3ODUgMi4zMjgyODYzLDMuNDEwODA4NiAxLjk5OTYxODIsNC4xODgxNTA5IDIsNSB2IDEuNSBjIDkuOTc5ZS00LDEuMTczMjgxNyAwLjY4NTg3OTcsMi4yMzgzMDYyIDEuNzUzLDIuNzI2IEwgMy42MTksOS45IEMgMy41NDYwOCwxMC4yNjc4MTEgMy4yNzM1MjE5LDEwLjU2MzkxOCAyLjkxMywxMC42NjcgTCAwLjcyNSwxMS4yOTIgQyAwLjI5NTYzOTI2LDExLjQxNDgwOSAtMi40ODE3NzE2ZS00LDExLjgwNzQyMSAwLDEyLjI1NCBWIDE0LjUgQyAwLDE0Ljc3NjE0MiAwLjIyMzg1NzYzLDE1IDAuNSwxNSBoIDkgQyA5Ljc3NjE0MjQsMTUgMTAsMTQuNzc2MTQyIDEwLDE0LjUgViAxMi4yNTQgQyA5Ljk5OTgwMTgsMTEuODA3NzkyIDkuNzA0MDA0MywxMS40MTU3MDcgOS4yNzUsMTEuMjkzIFoiIGZpbGw9IiNmZmYiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTEpIi8+PC9nPjwvZz48L3N2Zz4='); --icon-PublicFilled: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNMCAwSDE2VjE2SDB6IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIC0xKSIvPjxnIGZpbGw9IiMxNkIzNzgiIGZpbGwtcnVsZT0ibm9uemVybyI+PHBhdGggZD0iTTE1LjI3NSwxMC4yOTMgTDEzLjA4Nyw5LjY2OCBDMTIuNzI2NDc4MSw5LjU2NDkxNzggMTIuNDUzOTIwNCw5LjI2ODgxMDUgMTIuMzgxLDguOTAxIEwxMi4yNDcsOC4yMjYgQzEzLjMxNDEyMDMsNy43MzgzMDYxNSAxMy45OTkwMDIxLDYuNjczMjgxNyAxNCw1LjUgTDE0LDQuMTI2IEMxNC4wMjUxOTc2LDIuNDczMDAzMjUgMTIuNzQyNTA1NSwxLjA5NDE1MzM3IDExLjA5MiwxIEM5Ljg3OTM3MDksMC45NjM0MTA4MjIgOC43NjQwNjU1LDEuNjYwNzcyNjkgOC4yNjYsMi43NjcgQzguNzQzMDQ3OCwzLjQ2MTI3ODUgOC45OTg5MTk4NCw0LjI4MzYyNDc0IDksNS4xMjYgTDksNi41IEM4Ljk5ODIwNzc0LDYuODYzNzcwNTcgOC45NDYwNDU1OCw3LjIyNTU0MDM4IDguODQ1LDcuNTc1IEM5LjEwNDIzMjUzLDcuODQ2OTQyMjYgOS40MTIyMzk1Myw4LjA2Nzc3MTA2IDkuNzUzLDguMjI2IEw5LjYxOSw4LjkgQzkuNTQ2MDc5NTcsOS4yNjc4MTA1IDkuMjczNTIxODcsOS41NjM5MTc4IDguOTEzLDkuNjY3IEw4LjA3LDkuOTA4IEw5LjU1LDEwLjMzMSBDMTAuNDA2OTY0MywxMC41Nzg1NTM2IDEwLjk5NzcwNTksMTEuMzYxOTk5MyAxMSwxMi4yNTQgTDExLDE0LjUgQzEwLjk5ODM0MjgsMTQuNjcwNzE5OCAxMC45NjcyMTg5LDE0LjgzOTg3MTUgMTAuOTA4LDE1IEwxNS41LDE1IEMxNS43NzYxNDI0LDE1IDE2LDE0Ljc3NjE0MjQgMTYsMTQuNSBMMTYsMTEuMjU0IEMxNS45OTk4MDE4LDEwLjgwNzc5MTggMTUuNzA0MDA0MywxMC40MTU3MDcyIDE1LjI3NSwxMC4yOTMgWiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtMSkiLz48cGF0aCBkPSJNOS4yNzUsMTEuMjkzIEw3LjA4NywxMC42NjggQzYuNzI2MjAyMzIsMTAuNTY0NzUzOSA2LjQ1MzU4MywxMC4yNjgxOTM1IDYuMzgxLDkuOSBMNi4yNDcsOS4yMjUgQzcuMzEzNzkxNDEsOC43Mzc0NTY5MyA3Ljk5ODYxMTI1LDcuNjcyOTE5NTQgOCw2LjUgTDgsNS4xMjYgQzguMDI1MTk3NTYsMy40NzMwMDMyNSA2Ljc0MjUwNTQ3LDIuMDk0MTUzMzcgNS4wOTIsMiBDNC4yODA1MzI3LDEuOTc1MTAzMiAzLjQ5MzYyMDIsMi4yODAxNDgyNyAyLjkxMDk1MzIzLDIuODQ1NDc4NDYgQzIuMzI4Mjg2MjUsMy40MTA4MDg2NCAxLjk5OTYxODE2LDQuMTg4MTUwOTUgMiw1IEwyLDYuNSBDMi4wMDA5OTc5MSw3LjY3MzI4MTcgMi42ODU4Nzk3NCw4LjczODMwNjE1IDMuNzUzLDkuMjI2IEwzLjYxOSw5LjkgQzMuNTQ2MDc5NTcsMTAuMjY3ODEwNSAzLjI3MzUyMTg3LDEwLjU2MzkxNzggMi45MTMsMTAuNjY3IEwwLjcyNSwxMS4yOTIgQzAuMjk1NjM5MjYyLDExLjQxNDgwOTEgLTAuMDAwMjQ4MTc3MTU3LDExLjgwNzQyMTIgMCwxMi4yNTQgTDAsMTQuNSBDMy4zODE3NjU4MWUtMTcsMTQuNzc2MTQyNCAwLjIyMzg1NzYyNSwxNSAwLjUsMTUgTDkuNSwxNSBDOS43NzYxNDIzNywxNSAxMCwxNC43NzYxNDI0IDEwLDE0LjUgTDEwLDEyLjI1NCBDOS45OTk4MDE3NywxMS44MDc3OTE4IDkuNzA0MDA0MzEsMTEuNDE1NzA3MiA5LjI3NSwxMS4yOTMgWiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtMSkiLz48L2c+PC9nPjwvc3ZnPg=='); --icon-Redo: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTIuMzU1MzI4LDUgTDYuNTAwMTAyMzgsNSBDNi43NzYyNDQ3Niw1IDcuMDAwMTAyMzgsNS4yMjM4NTc2MyA3LjAwMDEwMjM4LDUuNSBDNy4wMDAxMDIzOCw1Ljc3NjE0MjM3IDYuNzc2MjQ0NzYsNiA2LjUwMDEwMjM4LDYgTDEuNTEwMjEyMzgsNiBDMS40ODY4NTQ0NCw2LjAwMDQ5MTk4IDEuNDYzMzU4OTcsNS45OTkzNDQxNCAxLjQzOTg5NjI3LDUuOTk2NDk5MjUgQzEuMzYwMzg0NTQsNS45ODY4NzA1OCAxLjI4NjYwNzQzLDUuOTU4NjY1NjMgMS4yMjMwNzYwNCw1LjkxNjMwNDg0IEMxLjA5OTMxODAxLDUuODM0MTc1NDcgMS4wMjE5NjcwOSw1LjcwMzQ3ODggMS4wMDQwMTY1Niw1LjU2Mjg2NjI5IEMxLjAwMTkxMjc3LDUuNTQ2MDk2MDIgMS4wMDA2Mzk1Niw1LjUyOTA2NzQgMS4wMDAyMzk0Miw1LjUxMTgyMjkyIEMwLjk5OTk1NjM3MSw1LjUwNDI3MzIxIDAuOTk5OTQyNjg0LDUuNDk2NzA0NiAxLjAwMDEwMjM4LDUuNDg5MTI2OTMgTDEuMDAwMTAyMzgsMC41IEMxLjAwMDEwMjM4LDAuMjIzODU3NjI1IDEuMjIzOTYwMDEsMCAxLjUwMDEwMjM4LDAgQzEuNzc2MjQ0NzYsMCAyLjAwMDEwMjM4LDAuMjIzODU3NjI1IDIuMDAwMTAyMzgsMC41IEwyLjAwMDEwMjM4LDMuNzc0NjkzMTYgQzMuMzY4NjQzMjgsMi4wMzk0MDM0MyA1LjMyOTE3Nzk4LDEgNy41MDAxMDIzOCwxIEMxMS42NDIyNDQ4LDEgMTUuMDAwMTAyNCw0LjM1Nzg1NzYzIDE1LjAwMDEwMjQsOC41IEMxNS4wMDAxMDI0LDEyLjY0MjE0MjQgMTEuNjQyMjQ0OCwxNiA3LjUwMDEwMjM4LDE2IEM3LjIyMzk2MDAxLDE2IDcuMDAwMTAyMzgsMTUuNzc2MTQyNCA3LjAwMDEwMjM4LDE1LjUgQzcuMDAwMTAyMzgsMTUuMjIzODU3NiA3LjIyMzk2MDAxLDE1IDcuNTAwMTAyMzgsMTUgQzExLjA4OTk2LDE1IDE0LjAwMDEwMjQsMTIuMDg5ODU3NiAxNC4wMDAxMDI0LDguNSBDMTQuMDAwMTAyNCw0LjkxMDE0MjM3IDExLjA4OTk2LDIgNy41MDAxMDIzOCwyIEM1LjQxNTg0ODkyLDIgMy41NDU2NTQwMiwzLjEzMDY0MDcgMi4zNTUzMjgsNSBaIiBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iIHRyYW5zZm9ybT0ibWF0cml4KC0xIDAgMCAxIDE2IDApIi8+PC9zdmc+'); --icon-Remove: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTYsNCBMNiwyLjUgQzYsMi4yMjM4NTc2MyA2LjIyMzg1NzYzLDIgNi41LDIgTDkuNSwyIEM5Ljc3NjE0MjM3LDIgMTAsMi4yMjM4NTc2MyAxMCwyLjUgTDEwLDQgTDEzLjUsNCBDMTMuNzc2MTQyNCw0IDE0LDQuMjIzODU3NjMgMTQsNC41IEMxNCw0Ljc3NjE0MjM3IDEzLjc3NjE0MjQsNSAxMy41LDUgTDIuNSw1IEMyLjIyMzg1NzYzLDUgMiw0Ljc3NjE0MjM3IDIsNC41IEMyLDQuMjIzODU3NjMgMi4yMjM4NTc2Myw0IDIuNSw0IEw2LDQgWiBNNyw0IEw5LDQgTDksMyBMNywzIEw3LDQgWiBNMTEsNi41IEMxMSw2LjIyMzg1NzYzIDExLjIyMzg1NzYsNiAxMS41LDYgQzExLjc3NjE0MjQsNiAxMiw2LjIyMzg1NzYzIDEyLDYuNSBMMTIsMTIuNSBDMTIsMTMuMzI4NDI3MSAxMS4zMjg0MjcxLDE0IDEwLjUsMTQgTDUuNSwxNCBDNC42NzE1NzI4OCwxNCA0LDEzLjMyODQyNzEgNCwxMi41IEw0LDYuNSBDNCw2LjIyMzg1NzYzIDQuMjIzODU3NjMsNiA0LjUsNiBDNC43NzYxNDIzNyw2IDUsNi4yMjM4NTc2MyA1LDYuNSBMNSwxMi41IEM1LDEyLjc3NjE0MjQgNS4yMjM4NTc2MywxMyA1LjUsMTMgTDEwLjUsMTMgQzEwLjc3NjE0MjQsMTMgMTEsMTIuNzc2MTQyNCAxMSwxMi41IEwxMSw2LjUgWiIgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJub256ZXJvIi8+PC9zdmc+'); diff --git a/static/ui-icons/UI/Fireworks.svg b/static/ui-icons/UI/Fireworks.svg new file mode 100644 index 00000000..d57ad0ea --- /dev/null +++ b/static/ui-icons/UI/Fireworks.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/static/ui-icons/UI/PublicColor.svg b/static/ui-icons/UI/PublicColor.svg new file mode 100644 index 00000000..bb865581 --- /dev/null +++ b/static/ui-icons/UI/PublicColor.svg @@ -0,0 +1,72 @@ + + + + + + image/svg+xml + + 1 + + + + + + + + + + 1 + Created with Sketch. + + + + + + + + + + + + diff --git a/stubs/app/server/server.ts b/stubs/app/server/server.ts index 4c328efc..aa961daa 100644 --- a/stubs/app/server/server.ts +++ b/stubs/app/server/server.ts @@ -6,6 +6,7 @@ import {isAffirmative} from 'app/common/gutil'; import {HomeDBManager} from 'app/gen-server/lib/HomeDBManager'; +import {TEAM_FREE_PLAN} from 'app/common/Features'; const debugging = isAffirmative(process.env.DEBUG) || isAffirmative(process.env.VERBOSE); @@ -91,7 +92,7 @@ export async function main() { }, { setUserAsOwner: false, useNewPlan: true, - planType: 'teamFree' + planType: TEAM_FREE_PLAN }); } } diff --git a/test/nbrowser/testUtils.ts b/test/nbrowser/testUtils.ts index 554f3031..7ba7f3c0 100644 --- a/test/nbrowser/testUtils.ts +++ b/test/nbrowser/testUtils.ts @@ -82,7 +82,9 @@ export function setupTestSuite(options?: TestSuiteOptions) { checkForExtraWindows(); // After every suite, clear sessionStorage and localStorage to avoid affecting other tests. - after(clearCurrentWindowStorage); + if (!process.env.NO_CLEANUP) { + after(clearCurrentWindowStorage); + } // Also, log out, to avoid logins interacting, unless NO_CLEANUP is requested (useful for // debugging tests). if (!process.env.NO_CLEANUP) {