feat: implement preferences pane

This commit is contained in:
Gorjan Petrovski
2021-07-05 16:57:37 +02:00
committed by GitHub
parent b3347b75ba
commit a9870214ea
27 changed files with 572 additions and 206 deletions

View File

@@ -65,6 +65,7 @@ import { NotesContextMenuDirective } from './components/NotesContextMenu';
import { NotesOptionsPanelDirective } from './components/NotesOptionsPanel';
import { IconDirective } from './components/Icon';
import { NoteTagsContainerDirective } from './components/NoteTagsContainer';
import { PreferencesDirective } from './components/preferences';
function reloadHiddenFirefoxTab(): boolean {
/**
@@ -90,7 +91,7 @@ function reloadHiddenFirefoxTab(): boolean {
const startApplication: StartApplication = async function startApplication(
defaultSyncServerHost: string,
bridge: Bridge,
nextVersionSyncServerHost: string,
nextVersionSyncServerHost: string
) {
if (reloadHiddenFirefoxTab()) {
return;
@@ -161,7 +162,8 @@ const startApplication: StartApplication = async function startApplication(
.directive('notesContextMenu', NotesContextMenuDirective)
.directive('notesOptionsPanel', NotesOptionsPanelDirective)
.directive('icon', IconDirective)
.directive('noteTagsContainer', NoteTagsContainerDirective);
.directive('noteTagsContainer', NoteTagsContainerDirective)
.directive('preferences', PreferencesDirective);
// Filters
angular.module('app').filter('trusted', ['$sce', trusted]);
@@ -174,10 +176,12 @@ const startApplication: StartApplication = async function startApplication(
Object.defineProperties(window, {
application: {
get: () =>
(angular
.element(document)
.injector()
.get('mainApplicationGroup') as any).primaryApplication,
(
angular
.element(document)
.injector()
.get('mainApplicationGroup') as any
).primaryApplication,
},
});
}

View File

@@ -13,40 +13,59 @@ import PasswordIcon from '../../icons/ic-textbox-password.svg';
import TrashSweepIcon from '../../icons/ic-trash-sweep.svg';
import MoreIcon from '../../icons/ic-more.svg';
import TuneIcon from '../../icons/ic-tune.svg';
import AccessibilityIcon from '../../icons/ic-accessibility.svg';
import HelpIcon from '../../icons/ic-help.svg';
import KeyboardIcon from '../../icons/ic-keyboard.svg';
import ListedIcon from '../../icons/ic-listed.svg';
import SecurityIcon from '../../icons/ic-security.svg';
import SettingsFilledIcon from '../../icons/ic-settings-filled.svg';
import StarIcon from '../../icons/ic-star.svg';
import ThemesIcon from '../../icons/ic-themes.svg';
import UserIcon from '../../icons/ic-user.svg';
import { toDirective } from './utils';
const ICONS = {
'pencil-off': PencilOffIcon,
'rich-text': RichTextIcon,
'trash': TrashIcon,
'pin': PinIcon,
'unpin': UnpinIcon,
'archive': ArchiveIcon,
'unarchive': UnarchiveIcon,
'hashtag': HashtagIcon,
trash: TrashIcon,
pin: PinIcon,
unpin: UnpinIcon,
archive: ArchiveIcon,
unarchive: UnarchiveIcon,
hashtag: HashtagIcon,
'chevron-right': ChevronRightIcon,
'restore': RestoreIcon,
'close': CloseIcon,
'password': PasswordIcon,
restore: RestoreIcon,
close: CloseIcon,
password: PasswordIcon,
'trash-sweep': TrashSweepIcon,
'more': MoreIcon,
'tune': TuneIcon,
more: MoreIcon,
tune: TuneIcon,
accessibility: AccessibilityIcon,
help: HelpIcon,
keyboard: KeyboardIcon,
listed: ListedIcon,
security: SecurityIcon,
'settings-filled': SettingsFilledIcon,
star: StarIcon,
themes: ThemesIcon,
user: UserIcon,
};
export type IconType = keyof typeof ICONS;
type Props = {
type: keyof (typeof ICONS);
className: string;
}
type: IconType;
className?: string;
};
export const Icon: React.FC<Props> = ({ type, className }) => {
const IconComponent = ICONS[type];
return <IconComponent className={`sn-icon ${className}`} />;
};
export const IconDirective = toDirective<Props>(
Icon,
{
type: '@',
className: '@',
}
);
export const IconDirective = toDirective<Props>(Icon, {
type: '@',
className: '@',
});

View File

@@ -0,0 +1,53 @@
import { FunctionComponent } from 'preact';
import { Icon, IconType } from './Icon';
const ICON_BUTTON_TYPES: {
[type: string]: { className: string };
} = {
normal: {
className: '',
},
primary: {
className: 'info',
},
};
export type IconButtonType = keyof typeof ICON_BUTTON_TYPES;
interface IconButtonProps {
/**
* onClick - preventDefault is handled within the component
*/
onClick: () => void;
type: IconButtonType;
className?: string;
iconType: IconType;
}
/**
* CircleButton component with an icon for SPA
* preventDefault is already handled within the component
*/
export const IconButton: FunctionComponent<IconButtonProps> = ({
onClick,
type,
className,
iconType,
}) => {
const click = (e: MouseEvent) => {
e.preventDefault();
onClick();
};
const typeProps = ICON_BUTTON_TYPES[type];
return (
<button
className={`sn-icon-button ${typeProps.className} ${className ?? ''}`}
onClick={click}
>
<Icon type={iconType} />
</button>
);
};

View File

@@ -0,0 +1,23 @@
import { Icon, IconType } from '@/components/Icon';
import { FunctionComponent } from 'preact';
interface PreferencesMenuItemProps {
iconType: IconType;
label: string;
selected: boolean;
onClick: () => void;
}
export const PreferencesMenuItem: FunctionComponent<PreferencesMenuItemProps> =
({ iconType, label, selected, onClick }) => (
<div
className={`preferences-menu-item ${selected ? 'selected' : ''}`}
onClick={(e) => {
e.preventDefault();
onClick();
}}
>
<Icon className="icon" type={iconType} />
{label}
</div>
);

View File

@@ -0,0 +1,13 @@
import { FunctionComponent } from 'preact';
export const TitleBar: FunctionComponent<{ className?: string }> = ({
children,
className,
}) => <div className={`sn-titlebar ${className ?? ''}`}>{children}</div>;
export const Title: FunctionComponent<{ className?: string }> = ({
children,
className,
}) => {
return <div className={`sn-title ${className ?? ''}`}>{children}</div>;
};

View File

@@ -0,0 +1,22 @@
import { observer } from 'mobx-react-lite';
import { FunctionComponent } from 'preact';
import { toDirective } from '../utils';
import { PreferencesView } from './view';
interface WrapperProps {
appState: { preferences: { isOpen: boolean; closePreferences: () => void } };
}
const PreferencesViewWrapper: FunctionComponent<WrapperProps> = observer(
({ appState }) => {
if (!appState.preferences.isOpen) return null;
return (
<PreferencesView close={() => appState.preferences.closePreferences()} />
);
}
);
export const PreferencesDirective = toDirective<WrapperProps>(
PreferencesViewWrapper
);

View File

@@ -0,0 +1,50 @@
import { IconType } from '@/components/Icon';
import { action, computed, makeObservable, observable } from 'mobx';
interface PreferenceItem {
icon: IconType;
label: string;
}
interface PreferenceListItem extends PreferenceItem {
id: number;
}
const predefinedItems: PreferenceItem[] = [
{ label: 'General', icon: 'settings-filled' },
{ label: 'Account', icon: 'user' },
{ label: 'Appearance', icon: 'themes' },
{ label: 'Security', icon: 'security' },
{ label: 'Listed', icon: 'listed' },
{ label: 'Shortcuts', icon: 'keyboard' },
{ label: 'Accessibility', icon: 'accessibility' },
{ label: 'Get a free month', icon: 'star' },
{ label: 'Help & feedback', icon: 'help' },
];
export class MockState {
private readonly _items: PreferenceListItem[];
private _selectedId = 0;
constructor(items: PreferenceItem[] = predefinedItems) {
makeObservable<MockState, '_selectedId'>(this, {
_selectedId: observable,
items: computed,
select: action,
});
this._items = items.map((p, idx) => ({ ...p, id: idx }));
this._selectedId = this._items[0].id;
}
select(id: number) {
this._selectedId = id;
}
get items(): (PreferenceListItem & { selected: boolean })[] {
return this._items.map((p) => ({
...p,
selected: p.id === this._selectedId,
}));
}
}

View File

@@ -0,0 +1,33 @@
import { observer } from 'mobx-react-lite';
import { FunctionComponent } from 'preact';
import { PreferencesMenuItem } from '../PreferencesMenuItem';
import { MockState } from './mock-state';
interface PreferencesMenuProps {
store: MockState;
}
const PreferencesMenu: FunctionComponent<PreferencesMenuProps> = observer(
({ store }) => (
<div className="h-full w-auto flex flex-col px-3 py-6">
{store.items.map((pref) => (
<PreferencesMenuItem
key={pref.id}
iconType={pref.icon}
label={pref.label}
selected={pref.selected}
onClick={() => store.select(pref.id)}
/>
))}
</div>
)
);
export const PreferencesPane: FunctionComponent = () => {
const store = new MockState();
return (
<div className="h-full w-full flex flex-row">
<PreferencesMenu store={store}></PreferencesMenu>
</div>
);
};

View File

@@ -0,0 +1,28 @@
import { IconButton } from '@/components/IconButton';
import { TitleBar, Title } from '@/components/TitleBar';
import { FunctionComponent } from 'preact';
import { PreferencesPane } from './pane';
interface PreferencesViewProps {
close: () => void;
}
export const PreferencesView: FunctionComponent<PreferencesViewProps> = ({
close,
}) => (
<div className="sn-full-screen flex flex-col bg-contrast z-index-preferences">
<TitleBar className="items-center justify-between">
{/* div is added so flex justify-between can center the title */}
<div className="h-8 w-8" />
<Title className="text-lg">Your preferences for Standard Notes</Title>
<IconButton
onClick={() => {
close();
}}
type="normal"
iconType="close"
/>
</TitleBar>
<PreferencesPane />
</div>
);

View File

@@ -22,6 +22,7 @@ import { SyncState } from './sync_state';
import { SearchOptionsState } from './search_options_state';
import { NotesState } from './notes_state';
import { TagsState } from './tags_state';
import { PreferencesState } from './preferences_state';
export enum AppStateEvent {
TagChanged,
@@ -63,6 +64,7 @@ export class AppState {
showBetaWarning: boolean;
readonly accountMenu = new AccountMenuState();
readonly actionsMenu = new ActionsMenuState();
readonly preferences = new PreferencesState();
readonly noAccountWarning: NoAccountWarningState;
readonly noteTags: NoteTagsState;
readonly sync = new SyncState();
@@ -89,21 +91,18 @@ export class AppState {
async () => {
await this.notifyEvent(AppStateEvent.ActiveEditorChanged);
},
this.appEventObserverRemovers,
this.appEventObserverRemovers
);
this.noteTags = new NoteTagsState(
application,
this,
this.appEventObserverRemovers
);
this.tags = new TagsState(
application,
this.appEventObserverRemovers,
),
this.noAccountWarning = new NoAccountWarningState(
application,
this.appEventObserverRemovers
);
(this.tags = new TagsState(application, this.appEventObserverRemovers)),
(this.noAccountWarning = new NoAccountWarningState(
application,
this.appEventObserverRemovers
));
this.searchOptions = new SearchOptionsState(
application,
this.appEventObserverRemovers
@@ -128,6 +127,7 @@ export class AppState {
makeObservable(this, {
showBetaWarning: observable,
isSessionsModalVisible: observable,
preferences: observable,
enableBetaWarning: action,
disableBetaWarning: action,

View File

@@ -0,0 +1,26 @@
import { action, computed, makeObservable, observable } from 'mobx';
export class PreferencesState {
private _open = false;
constructor() {
makeObservable<PreferencesState, '_open'>(this, {
_open: observable,
openPreferences: action,
closePreferences: action,
isOpen: computed,
});
}
openPreferences = (): void => {
this._open = true;
};
closePreferences = (): void => {
this._open = false;
};
get isOpen() {
return this._open;
}
}

View File

@@ -26,6 +26,9 @@
application='self.application'
app-state='self.appState'
)
preferences(
app-state='self.appState'
)
challenge-modal(
ng-repeat="challenge in self.challenges track by challenge.id"
class="sk-modal"
@@ -36,3 +39,4 @@
notes-context-menu(
app-state='self.appState'
)

View File

@@ -33,44 +33,47 @@ const ACCOUNT_SWITCHER_ENABLED = false;
const ACCOUNT_SWITCHER_FEATURE_KEY = 'account_switcher';
type DockShortcut = {
name: string,
component: SNComponent,
name: string;
component: SNComponent;
icon: {
type: string
background_color: string
border_color: string
}
}
type: string;
background_color: string;
border_color: string;
};
};
class FooterViewCtrl extends PureViewCtrl<unknown, {
outOfSync: boolean;
hasPasscode: boolean;
dataUpgradeAvailable: boolean;
dockShortcuts: DockShortcut[];
hasAccountSwitcher: boolean;
showBetaWarning: boolean;
showDataUpgrade: boolean;
}> {
private $rootScope: ng.IRootScopeService
private rooms: SNComponent[] = []
private themesWithIcons: SNTheme[] = []
private showSyncResolution = false
private unregisterComponent: any
private rootScopeListener1: any
private rootScopeListener2: any
public arbitraryStatusMessage?: string
public user?: any
private offline = true
public showAccountMenu = false
private didCheckForOffline = false
private queueExtReload = false
private reloadInProgress = false
public hasError = false
public isRefreshing = false
public lastSyncDate?: string
public newUpdateAvailable = false
public dockShortcuts: DockShortcut[] = []
public roomShowState: Partial<Record<string, boolean>> = {}
class FooterViewCtrl extends PureViewCtrl<
unknown,
{
outOfSync: boolean;
hasPasscode: boolean;
dataUpgradeAvailable: boolean;
dockShortcuts: DockShortcut[];
hasAccountSwitcher: boolean;
showBetaWarning: boolean;
showDataUpgrade: boolean;
}
> {
private $rootScope: ng.IRootScopeService;
private rooms: SNComponent[] = [];
private themesWithIcons: SNTheme[] = [];
private showSyncResolution = false;
private unregisterComponent: any;
private rootScopeListener1: any;
private rootScopeListener2: any;
public arbitraryStatusMessage?: string;
public user?: any;
private offline = true;
public showAccountMenu = false;
private didCheckForOffline = false;
private queueExtReload = false;
private reloadInProgress = false;
public hasError = false;
public isRefreshing = false;
public lastSyncDate?: string;
public newUpdateAvailable = false;
public dockShortcuts: DockShortcut[] = [];
public roomShowState: Partial<Record<string, boolean>> = {};
private observerRemovers: Array<() => void> = [];
private completedInitialSync = false;
private showingDownloadStatus = false;
@@ -117,7 +120,7 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
this.showAccountMenu = this.appState.accountMenu.show;
this.setState({
showBetaWarning: showBetaWarning,
showDataUpgrade: !showBetaWarning
showDataUpgrade: !showBetaWarning,
});
});
}
@@ -128,7 +131,9 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
/** Enable permanently for this user so they don't lose the feature after its disabled */
localStorage.setItem(ACCOUNT_SWITCHER_FEATURE_KEY, JSON.stringify(true));
}
const hasAccountSwitcher = stringValue ? JSON.parse(stringValue) : ACCOUNT_SWITCHER_ENABLED;
const hasAccountSwitcher = stringValue
? JSON.parse(stringValue)
: ACCOUNT_SWITCHER_ENABLED;
this.setState({ hasAccountSwitcher });
}
@@ -148,7 +153,7 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
reloadUpgradeStatus() {
this.application.checkForSecurityUpdate().then((available) => {
this.setState({
dataUpgradeAvailable: available
dataUpgradeAvailable: available,
});
});
}
@@ -176,19 +181,25 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
async reloadPasscodeStatus() {
const hasPasscode = this.application.hasPasscode();
this.setState({
hasPasscode: hasPasscode
hasPasscode: hasPasscode,
});
}
addRootScopeListeners() {
this.rootScopeListener1 = this.$rootScope.$on(RootScopeMessages.ReloadExtendedData, () => {
this.reloadExtendedData();
});
this.rootScopeListener2 = this.$rootScope.$on(RootScopeMessages.NewUpdateAvailable, () => {
this.$timeout(() => {
this.onNewUpdateAvailable();
});
});
this.rootScopeListener1 = this.$rootScope.$on(
RootScopeMessages.ReloadExtendedData,
() => {
this.reloadExtendedData();
}
);
this.rootScopeListener2 = this.$rootScope.$on(
RootScopeMessages.NewUpdateAvailable,
() => {
this.$timeout(() => {
this.onNewUpdateAvailable();
});
}
);
}
/** @override */
@@ -202,11 +213,11 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
}
break;
case AppStateEvent.BeganBackupDownload:
statusService.setMessage("Saving local backup…");
statusService.setMessage('Saving local backup…');
break;
case AppStateEvent.EndedBackupDownload: {
const successMessage = "Successfully saved backup.";
const errorMessage = "Unable to save local backup.";
const successMessage = 'Successfully saved backup.';
const errorMessage = 'Unable to save local backup.';
statusService.setMessage(data.success ? successMessage : errorMessage);
const twoSeconds = 2000;
@@ -237,12 +248,12 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
break;
case ApplicationEvent.EnteredOutOfSync:
this.setState({
outOfSync: true
outOfSync: true,
});
break;
case ApplicationEvent.ExitedOutOfSync:
this.setState({
outOfSync: false
outOfSync: false,
});
break;
case ApplicationEvent.CompletedFullSync:
@@ -290,17 +301,15 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
CollectionSort.Title,
'asc',
(theme: SNTheme) => {
return (
theme.package_info &&
theme.package_info.dock_icon
);
return theme.package_info && theme.package_info.dock_icon;
}
);
this.observerRemovers.push(this.application.streamItems(
ContentType.Component,
async () => {
const components = this.application.getItems(ContentType.Component) as SNComponent[];
this.observerRemovers.push(
this.application.streamItems(ContentType.Component, async () => {
const components = this.application.getItems(
ContentType.Component
) as SNComponent[];
this.rooms = components.filter((candidate) => {
return candidate.area === ComponentArea.Rooms && !candidate.deleted;
});
@@ -308,33 +317,38 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
this.queueExtReload = false;
this.reloadExtendedData();
}
}
));
})
);
this.observerRemovers.push(this.application.streamItems(
ContentType.Theme,
async () => {
const themes = this.application.getDisplayableItems(ContentType.Theme) as SNTheme[];
this.observerRemovers.push(
this.application.streamItems(ContentType.Theme, async () => {
const themes = this.application.getDisplayableItems(
ContentType.Theme
) as SNTheme[];
this.themesWithIcons = themes;
this.reloadDockShortcuts();
}
));
})
);
}
registerComponentHandler() {
this.unregisterComponent = this.application.componentManager!.registerHandler({
identifier: 'room-bar',
areas: [ComponentArea.Rooms, ComponentArea.Modal],
focusHandler: (component, focused) => {
if (component.isEditor() && focused) {
if (component.package_info?.identifier === 'org.standardnotes.standard-sheets') {
return;
this.unregisterComponent =
this.application.componentManager!.registerHandler({
identifier: 'room-bar',
areas: [ComponentArea.Rooms, ComponentArea.Modal],
focusHandler: (component, focused) => {
if (component.isEditor() && focused) {
if (
component.package_info?.identifier ===
'org.standardnotes.standard-sheets'
) {
return;
}
this.closeAllRooms();
this.closeAccountMenu();
}
this.closeAllRooms();
this.closeAccountMenu();
}
}
});
},
});
}
updateSyncStatus() {
@@ -354,17 +368,17 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
statusManager.setMessage('');
}, 2000);
} else if (stats.uploadTotalCount > 20) {
const completionPercentage = stats.uploadCompletionCount === 0
? 0
: stats.uploadCompletionCount / stats.uploadTotalCount;
const completionPercentage =
stats.uploadCompletionCount === 0
? 0
: stats.uploadCompletionCount / stats.uploadTotalCount;
const stringPercentage = completionPercentage.toLocaleString(
undefined,
{ style: 'percent' }
);
const stringPercentage = completionPercentage.toLocaleString(undefined, {
style: 'percent',
});
statusManager.setMessage(
`Syncing ${stats.uploadTotalCount} items (${stringPercentage} complete)`,
`Syncing ${stats.uploadTotalCount} items (${stringPercentage} complete)`
);
} else {
statusManager.setMessage('');
@@ -398,8 +412,10 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
* then closing it after a short delay.
*/
const extWindow = this.rooms.find((room) => {
return room.package_info.identifier === this.application
.getNativeExtService().extManagerId;
return (
room.package_info.identifier ===
this.application.getNativeExtService().extManagerId
);
});
if (!extWindow) {
this.queueExtReload = true;
@@ -419,11 +435,13 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
}
async openSecurityUpdate() {
if (await confirmDialog({
title: STRING_UPGRADE_ACCOUNT_CONFIRM_TITLE,
text: STRING_UPGRADE_ACCOUNT_CONFIRM_TEXT,
confirmButtonText: STRING_UPGRADE_ACCOUNT_CONFIRM_BUTTON,
})) {
if (
await confirmDialog({
title: STRING_UPGRADE_ACCOUNT_CONFIRM_TITLE,
text: STRING_UPGRADE_ACCOUNT_CONFIRM_TEXT,
confirmButtonText: STRING_UPGRADE_ACCOUNT_CONFIRM_BUTTON,
})
) {
preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_UPGRADE, async () => {
await this.application.upgradeProtocolVersion();
});
@@ -453,25 +471,27 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
refreshData() {
this.isRefreshing = true;
this.application.sync({
queueStrategy: SyncQueueStrategy.ForceSpawnNew,
checkIntegrity: true
}).then((response) => {
this.$timeout(() => {
this.isRefreshing = false;
}, 200);
if (response && response.error) {
this.application.alertService!.alert(
STRING_GENERIC_SYNC_ERROR
);
} else {
this.syncUpdated();
}
});
this.application
.sync({
queueStrategy: SyncQueueStrategy.ForceSpawnNew,
checkIntegrity: true,
})
.then((response) => {
this.$timeout(() => {
this.isRefreshing = false;
}, 200);
if (response && response.error) {
this.application.alertService!.alert(STRING_GENERIC_SYNC_ERROR);
} else {
this.syncUpdated();
}
});
}
syncUpdated() {
this.lastSyncDate = dateToLocalizedString(this.application.getLastSyncDate()!);
this.lastSyncDate = dateToLocalizedString(
this.application.getLastSyncDate()!
);
}
onNewUpdateAvailable() {
@@ -480,9 +500,7 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
clickedNewUpdateAnnouncement() {
this.newUpdateAvailable = false;
this.application.alertService!.alert(
STRING_NEW_UPDATE_READY
);
this.application.alertService!.alert(STRING_NEW_UPDATE_READY);
}
reloadDockShortcuts() {
@@ -499,7 +517,7 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
shortcuts.push({
name: name,
component: theme,
icon: icon
icon: icon,
} as DockShortcut);
}
this.setState({
@@ -514,7 +532,7 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
} else {
return a.name.localeCompare(b.name);
}
})
}),
});
}
@@ -553,7 +571,7 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
text:
'If you wish to go back to a stable version, make sure to sign out ' +
'of this beta app first.<br>You can silence this warning from the ' +
'<em>Account</em> menu.'
'<em>Account</em> menu.',
});
}
@@ -563,6 +581,10 @@ class FooterViewCtrl extends PureViewCtrl<unknown, {
}
this.appState.accountMenu.setShow(false);
}
clickPreferences() {
this.appState.preferences.openPreferences();
}
}
export class FooterView extends WebDirective {
@@ -575,7 +597,7 @@ export class FooterView extends WebDirective {
this.controllerAs = 'ctrl';
this.bindToController = true;
this.scope = {
application: '='
application: '=',
};
}
}