This commit is contained in:
antelle 2021-07-04 13:02:48 +02:00
parent aa11e1a65e
commit d5a61acd01
No known key found for this signature in database
GPG Key ID: 63C9777AAB7C563C
41 changed files with 1432 additions and 807 deletions

View File

@ -10,25 +10,6 @@ import { WindowClass } from 'comp/browser/window-class';
const logger = new Logger('settings-manager');
const DesktopLocaleKeys = [
'sysMenuAboutKeeWeb',
'sysMenuServices',
'sysMenuHide',
'sysMenuHideOthers',
'sysMenuUnhide',
'sysMenuQuit',
'sysMenuEdit',
'sysMenuUndo',
'sysMenuRedo',
'sysMenuCut',
'sysMenuCopy',
'sysMenuPaste',
'sysMenuSelectAll',
'sysMenuWindow',
'sysMenuMinimize',
'sysMenuClose'
];
const SettingsManager = {
activeTheme: null as string | null,
@ -38,57 +19,68 @@ const SettingsManager = {
'fr-FR': 'Français'
} as Record<string, string>,
allThemes: {
dark: 'setGenThemeDark',
light: 'setGenThemeLight',
sd: 'setGenThemeSd',
sl: 'setGenThemeSl',
fb: 'setGenThemeFb',
bl: 'setGenThemeBl',
db: 'setGenThemeDb',
lb: 'setGenThemeLb',
te: 'setGenThemeTe',
lt: 'setGenThemeLt',
dc: 'setGenThemeDc',
hc: 'setGenThemeHc'
} as Record<string, string>,
get builtInThemes(): Record<string, string> {
return {
dark: Locale.setGenThemeDark,
light: Locale.setGenThemeLight,
sd: Locale.setGenThemeSd,
sl: Locale.setGenThemeSl,
fb: Locale.setGenThemeFb,
bl: Locale.setGenThemeBl,
db: Locale.setGenThemeDb,
lb: Locale.setGenThemeLb,
te: Locale.setGenThemeTe,
lt: Locale.setGenThemeLt,
dc: Locale.setGenThemeDc,
hc: Locale.setGenThemeHc
};
},
get allThemes(): Record<string, string> {
return {
...this.builtInThemes,
...this.customThemes
};
},
// changing something here? don't forget about desktop/app.js
autoSwitchedThemes: [
{
name: 'setGenThemeDefault',
dark: 'dark',
light: 'light'
},
{
name: 'setGenThemeSol',
dark: 'sd',
light: 'sl'
},
{
name: 'setGenThemeBlue',
dark: 'fb',
light: 'bl'
},
{
name: 'setGenThemeBrown',
dark: 'db',
light: 'lb'
},
{
name: 'setGenThemeTerminal',
dark: 'te',
light: 'lt'
},
{
name: 'setGenThemeHighContrast',
dark: 'dc',
light: 'hc'
}
],
get autoSwitchedThemes(): { name: string; dark: string; light: string }[] {
return [
{
name: Locale.setGenThemeDefault,
dark: 'dark',
light: 'light'
},
{
name: Locale.setGenThemeSol,
dark: 'sd',
light: 'sl'
},
{
name: Locale.setGenThemeBlue,
dark: 'fb',
light: 'bl'
},
{
name: Locale.setGenThemeBrown,
dark: 'db',
light: 'lb'
},
{
name: Locale.setGenThemeTerminal,
dark: 'te',
light: 'lt'
},
{
name: Locale.setGenThemeHighContrast,
dark: 'dc',
light: 'hc'
}
];
},
customLocales: new Map<string, Record<string, string>>(),
customThemeNames: new Map<string, string>(),
customThemes: {} as Record<string, string>,
init(): void {
Events.on('dark-mode-changed', () => this.darkModeChanged());
@ -183,10 +175,7 @@ const SettingsManager = {
Events.emit('locale-changed', loc);
if (Launcher) {
const localeValuesForDesktopApp: Record<string, string> = {};
for (const key of DesktopLocaleKeys) {
localeValuesForDesktopApp[key] = Locale.get(key);
}
const localeValuesForDesktopApp = this.getDesktopAppLocaleValues();
Launcher.ipcRenderer.invoke('set-locale', loc, localeValuesForDesktopApp).catch(noop);
}
},
@ -197,6 +186,27 @@ const SettingsManager = {
return 'en-US';
}
return language;
},
getDesktopAppLocaleValues(): Record<string, string> {
return {
sysMenuAboutKeeWeb: Locale.sysMenuAboutKeeWeb.with('KeeWeb'),
sysMenuServices: Locale.sysMenuServices,
sysMenuHide: Locale.sysMenuHide.with('KeeWeb'),
sysMenuHideOthers: Locale.sysMenuHideOthers,
sysMenuUnhide: Locale.sysMenuUnhide,
sysMenuQuit: Locale.sysMenuQuit.with('KeeWeb'),
sysMenuEdit: Locale.sysMenuEdit,
sysMenuUndo: Locale.sysMenuUndo,
sysMenuRedo: Locale.sysMenuRedo,
sysMenuCut: Locale.sysMenuCut,
sysMenuCopy: Locale.sysMenuCopy,
sysMenuPaste: Locale.sysMenuPaste,
sysMenuSelectAll: Locale.sysMenuSelectAll,
sysMenuWindow: Locale.sysMenuWindow,
sysMenuMinimize: Locale.sysMenuMinimize,
sysMenuClose: Locale.sysMenuClose
};
}
};

View File

@ -18,6 +18,7 @@ export type AppSettingsAutoUpdate = 'install' | 'check';
export type AppSettingsRememberKeyFiles = 'path' | 'data';
export type AppSettingsTitlebarStyle = 'default' | 'hidden' | 'hidden-inset';
export type AppSettingsDeviceOwnerAuth = 'memory' | 'file';
export type AppSettingsFontSize = 0 | 1 | 2;
class AppSettings extends Model {
theme: string | null = null; // UI theme
@ -31,7 +32,7 @@ class AppSettings extends Model {
clipboardSeconds = 0; // number of seconds after which the clipboard will be cleared
autoSave = true; // auto-save open files
autoSaveInterval = 0; // interval between performing automatic sync, minutes, -1: on every change
rememberKeyFiles: AppSettingsRememberKeyFiles = 'path'; // remember keyfiles selected on the Open screen
rememberKeyFiles: AppSettingsRememberKeyFiles | null = 'path'; // remember keyfiles selected on the Open screen
idleMinutes = 15; // app lock timeout after inactivity, minutes
minimizeOnClose = false; // minimise the app instead of closing
minimizeOnFieldCopy = false; // minimise the app on copy
@ -51,7 +52,7 @@ class AppSettings extends Model {
hideEmptyFields = false; // hide empty fields in entries
skipHttpsWarning = false; // disable the non-HTTPS warning
demoOpened = false; // hide the demo button inside the More... menu
fontSize: 0 | 1 | 2 = 0; // font size
fontSize: AppSettingsFontSize = 0; // font size
tableViewColumns: string[] | null = null; // columns displayed in the table view
generatorPresets: PasswordGeneratorAppSetting | null = null; // presets used in the password generator
generatorHidePassword = false; // hide password in the generator
@ -478,8 +479,13 @@ class AppSettings extends Model {
}
private setRememberKeyFiles(value: unknown) {
if (value === 'path' || value === 'data') {
this.rememberKeyFiles = value;
if (value) {
if (value === 'path' || value === 'data') {
this.rememberKeyFiles = value;
return true;
}
} else {
this.rememberKeyFiles = null;
return true;
}
return false;

View File

@ -41,7 +41,7 @@ export type MenuItemFilter =
class MenuItem extends Model {
readonly id: string;
title?: string;
locTitle?: LocaleKey;
locTitle?: () => string;
icon?: string;
customIcon?: string;
active = false;

View File

@ -44,7 +44,7 @@ class Menu extends Model {
this.sections = [];
this.allItemsItem = new MenuItem({
locTitle: 'menuAllItems',
locTitle: () => Locale.menuAllItems,
icon: 'th-large',
active: true,
shortcut: Keys.DOM_VK_A,
@ -58,7 +58,7 @@ class Menu extends Model {
this.groupsSection.grow = true;
this.colorsItem = new MenuItem({
locTitle: 'menuColors',
locTitle: () => Locale.menuColors,
icon: 'bookmark',
shortcut: Keys.DOM_VK_C,
cls: 'menu__item-colors',
@ -75,7 +75,7 @@ class Menu extends Model {
this.trashSection = new MenuSection(
new MenuItem({
locTitle: 'menuTrash',
locTitle: () => Locale.menuTrash,
icon: 'trash-alt',
shortcut: Keys.DOM_VK_D,
filter: { type: 'trash' },
@ -103,44 +103,44 @@ class Menu extends Model {
this.generalSection = new MenuSection(
new MenuItem({
locTitle: 'menuSetGeneral',
locTitle: () => Locale.menuSetGeneral,
icon: 'cog',
page: 'general',
anchor: undefined,
active: true
}),
new MenuItem({
locTitle: 'setGenAppearance',
locTitle: () => Locale.setGenAppearance,
icon: '0',
page: 'general',
anchor: 'appearance'
}),
new MenuItem({
locTitle: 'setGenFunction',
locTitle: () => Locale.setGenFunction,
icon: '0',
page: 'general',
anchor: 'function'
}),
new MenuItem({
locTitle: 'setGenAudit',
locTitle: () => Locale.setGenAudit,
icon: '0',
page: 'general',
anchor: 'audit'
}),
new MenuItem({
locTitle: 'setGenLock',
locTitle: () => Locale.setGenLock,
icon: '0',
page: 'general',
anchor: 'lock'
}),
new MenuItem({
locTitle: 'setGenStorage',
locTitle: () => Locale.setGenStorage,
icon: '0',
page: 'general',
anchor: 'storage'
}),
new MenuItem({
locTitle: 'advanced',
locTitle: () => StringFormat.capFirst(Locale.advanced),
icon: '0',
page: 'general',
anchor: 'advanced'
@ -148,30 +148,46 @@ class Menu extends Model {
);
this.shortcutsSection = new MenuSection(
new MenuItem({ locTitle: 'shortcuts', icon: 'keyboard', page: 'shortcuts' })
new MenuItem({
locTitle: () => StringFormat.capFirst(Locale.shortcuts),
icon: 'keyboard',
page: 'shortcuts'
})
);
if (Features.supportsBrowserExtensions) {
this.browserSection = new MenuSection(
new MenuItem({
locTitle: 'menuSetBrowser',
locTitle: () => Locale.menuSetBrowser,
icon: Features.browserIcon,
page: 'browser'
})
);
}
this.pluginsSection = new MenuSection(
new MenuItem({ locTitle: 'plugins', icon: 'puzzle-piece', page: 'plugins' })
new MenuItem({
locTitle: () => StringFormat.capFirst(Locale.plugins),
icon: 'puzzle-piece',
page: 'plugins'
})
);
if (Launcher) {
this.devicesSection = new MenuSection(
new MenuItem({ locTitle: 'menuSetDevices', icon: 'usb', page: 'devices' })
new MenuItem({
locTitle: () => Locale.menuSetDevices,
icon: 'usb',
page: 'devices'
})
);
}
this.aboutSection = new MenuSection(
new MenuItem({ locTitle: 'menuSetAbout', icon: 'info', page: 'about' })
new MenuItem({ locTitle: () => Locale.menuSetAbout, icon: 'info', page: 'about' })
);
this.helpSection = new MenuSection(
new MenuItem({ locTitle: 'help', icon: 'question', page: 'help' })
new MenuItem({
locTitle: () => StringFormat.capFirst(Locale.help),
icon: 'question',
page: 'help'
})
);
this.filesSection = new MenuSection();
this.filesSection.scrollable = true;
@ -299,7 +315,7 @@ class Menu extends Model {
for (const section of menu) {
for (const item of section.items) {
if (item.locTitle) {
item.title = StringFormat.capFirst(Locale.get(item.locTitle));
item.title = item.locTitle();
}
}
}

View File

@ -342,9 +342,7 @@ class Plugin extends Model {
el.addEventListener('load', () => {
URL.revokeObjectURL(objectUrl);
if (theme) {
const locKey = this.getThemeLocaleKey(theme.name);
SettingsManager.allThemes[theme.name] = locKey;
SettingsManager.customThemeNames.set(locKey, theme.title);
SettingsManager.customThemes[theme.name] = theme.title;
for (const styleSheet of Array.from(document.styleSheets)) {
const node = styleSheet.ownerNode as HTMLElement;
if (node?.id === id) {
@ -470,16 +468,12 @@ class Plugin extends Model {
}
}
getThemeLocaleKey(name: string): string {
return `setGenThemeCustom_${name}`;
}
removeTheme(theme: PluginManifestTheme): void {
delete SettingsManager.allThemes[theme.name];
if (AppSettings.theme === theme.name) {
AppSettings.theme = SettingsManager.getDefaultTheme();
}
SettingsManager.customThemeNames.delete(this.getThemeLocaleKey(theme.name));
delete SettingsManager.customThemes[theme.name];
}
loadPluginSettings(): void {

View File

@ -15,6 +15,10 @@ class StorageCache extends StorageBase {
return !Launcher;
}
get locName(): string {
return 'Cache';
}
async save(id: string, data: ArrayBuffer): Promise<StorageSaveResult> {
await this._io.save(id, data);
return {};
@ -38,6 +42,10 @@ class StorageCache extends StorageBase {
watch: undefined;
unwatch: undefined;
getPathForName: undefined;
getOpenConfig: undefined;
getSettingsConfig: undefined;
applyConfig: undefined;
applySetting: undefined;
}
export { StorageCache };

View File

@ -38,6 +38,10 @@ class StorageDropbox extends StorageBase {
return AppSettings.dropbox;
}
get locName(): string {
return Locale.dropbox;
}
private toFullPath(path: string) {
const rootFolder = AppSettings.dropboxFolder;
if (rootFolder) {
@ -114,29 +118,29 @@ class StorageDropbox extends StorageBase {
getOpenConfig(): StorageOpenConfig {
const keyField: StorageConfigFieldText = {
id: 'key',
title: 'dropboxAppKey',
desc: 'dropboxAppKeyDesc',
title: Locale.dropboxAppKey,
desc: Locale.dropboxAppKeyDesc,
type: 'text',
required: true,
pattern: '\\w+'
};
const secretField: StorageConfigFieldPassword = {
id: 'secret',
title: 'dropboxAppSecret',
desc: 'dropboxAppSecretDesc',
title: Locale.dropboxAppSecret,
desc: Locale.dropboxAppSecretDesc,
type: 'password',
required: true,
pattern: '\\w+'
};
const folderField: StorageConfigFieldText = {
id: 'folder',
title: 'dropboxFolder',
desc: 'dropboxFolderDesc',
title: Locale.dropboxFolder,
desc: Locale.dropboxFolderDesc,
type: 'text',
placeholder: 'dropboxFolderPlaceholder'
placeholder: Locale.dropboxFolderPlaceholder
};
return {
desc: 'dropboxSetupDesc',
desc: Locale.dropboxSetupDesc,
fields: [keyField, secretField, folderField]
};
}
@ -146,15 +150,19 @@ class StorageDropbox extends StorageBase {
const appKey = this.getKey();
const linkField: StorageConfigFieldSelect = {
id: 'link',
title: 'dropboxLink',
title: Locale.dropboxLink,
type: 'select',
value: 'custom',
options: { app: 'dropboxLinkApp', full: 'dropboxLinkFull', custom: 'dropboxLinkCustom' }
options: {
app: Locale.dropboxLinkApp,
full: Locale.dropboxLinkFull,
custom: Locale.dropboxLinkCustom
}
};
const keyField: StorageConfigFieldText = {
id: 'key',
title: 'dropboxAppKey',
desc: 'dropboxAppKeyDesc',
title: Locale.dropboxAppKey,
desc: Locale.dropboxAppKeyDesc,
type: 'text',
required: true,
pattern: '\\w+',
@ -162,8 +170,8 @@ class StorageDropbox extends StorageBase {
};
const secretField: StorageConfigFieldPassword = {
id: 'secret',
title: 'dropboxAppSecret',
desc: 'dropboxAppSecretDesc',
title: Locale.dropboxAppSecret,
desc: Locale.dropboxAppSecretDesc,
type: 'password',
required: true,
pattern: '\\w+',
@ -171,8 +179,8 @@ class StorageDropbox extends StorageBase {
};
const folderField: StorageConfigFieldText = {
id: 'folder',
title: 'dropboxFolder',
desc: 'dropboxFolderSettingsDesc',
title: Locale.dropboxFolder,
desc: Locale.dropboxFolderSettingsDesc,
type: 'text',
value: AppSettings.dropboxFolder ?? ''
};

View File

@ -18,6 +18,10 @@ class StorageFileCache extends StorageBase {
return !!Launcher;
}
get locName(): string {
return 'File cache';
}
private async init(): Promise<string> {
if (this._path) {
return Promise.resolve(this._path);
@ -108,6 +112,10 @@ class StorageFileCache extends StorageBase {
unwatch: undefined;
watch: undefined;
getPathForName: undefined;
getOpenConfig: undefined;
getSettingsConfig: undefined;
applyConfig: undefined;
applySetting: undefined;
}
export { StorageFileCache };

View File

@ -13,6 +13,7 @@ import {
} from 'storage/types';
import * as fs from 'fs';
import * as NodePath from 'path';
import { Locale } from 'util/locale';
interface StorageFileWatcherCallbackItem {
file: string;
@ -40,6 +41,10 @@ class StorageFile extends StorageBase {
return !!Launcher;
}
get locName(): string {
return Locale.file;
}
async load(path: string): Promise<StorageFileData> {
this._logger.info('Load', path);
const ts = this._logger.ts();
@ -185,6 +190,10 @@ class StorageFile extends StorageBase {
list: undefined;
remove: undefined;
getPathForName: undefined;
getOpenConfig: undefined;
getSettingsConfig: undefined;
applyConfig: undefined;
applySetting: undefined;
}
export { StorageFile };

View File

@ -35,6 +35,10 @@ class StorageGDrive extends StorageBase {
return AppSettings.gdrive;
}
get locName(): string {
return Locale.gdrive;
}
getPathForName(fileName: string): string {
return NewFileIdPrefix + fileName;
}
@ -398,6 +402,10 @@ class StorageGDrive extends StorageBase {
mkdir: undefined;
watch: undefined;
unwatch: undefined;
getOpenConfig: undefined;
getSettingsConfig: undefined;
applyConfig: undefined;
applySetting: undefined;
}
export { StorageGDrive };

View File

@ -13,6 +13,7 @@ import {
StorageRevConflictError,
StorageSaveResult
} from 'storage/types';
import { Locale } from 'util/locale';
// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
@ -31,6 +32,10 @@ class StorageOneDrive extends StorageBase {
return AppSettings.onedrive;
}
get locName(): string {
return Locale.onedrive;
}
getPathForName(fileName: string): string {
return '/drive/root:/' + fileName + '.kdbx';
}
@ -323,6 +328,10 @@ class StorageOneDrive extends StorageBase {
watch: undefined;
unwatch: undefined;
getOpenConfig: undefined;
getSettingsConfig: undefined;
applyConfig: undefined;
applySetting: undefined;
}
export { StorageOneDrive };

View File

@ -32,6 +32,10 @@ class StorageWebDav extends StorageBase {
return AppSettings.webdav;
}
get locName(): string {
return Locale.webdav;
}
get needShowOpenConfig(): boolean {
return true;
}
@ -39,24 +43,24 @@ class StorageWebDav extends StorageBase {
getOpenConfig(): StorageOpenConfig {
const pathField: StorageConfigFieldText = {
id: 'path',
title: 'openUrl',
desc: 'openUrlDesc',
title: Locale.openUrl,
desc: Locale.openUrlDesc,
type: 'text',
required: true,
pattern: '^https://.+'
};
const userField: StorageConfigFieldText = {
id: 'user',
title: 'openUser',
desc: 'openUserDesc',
placeholder: 'openUserPlaceholder',
title: Locale.openUser,
desc: Locale.openUserDesc,
placeholder: Locale.openUserPlaceholder,
type: 'text'
};
const passwordField: StorageConfigFieldPassword = {
id: 'password',
title: 'openPass',
desc: 'openPassDesc',
placeholder: 'openPassPlaceholder',
title: Locale.openPass,
desc: Locale.openPassDesc,
placeholder: Locale.openPassPlaceholder,
type: 'password'
};
@ -68,14 +72,14 @@ class StorageWebDav extends StorageBase {
getSettingsConfig(): StorageSettingsConfig {
const methodField: StorageConfigFieldSelect = {
id: 'webdavSaveMethod',
title: 'webdavSaveMethod',
title: Locale.webdavSaveMethod,
type: 'select',
value: AppSettings.webdavSaveMethod,
options: { default: 'webdavSaveMove', put: 'webdavSavePut' }
options: { move: Locale.webdavSaveMove, put: Locale.webdavSavePut }
};
const reloadField: StorageConfigFieldCheckbox = {
id: 'webdavStatReload',
title: 'webdavStatReload',
title: Locale.webdavStatReload,
type: 'checkbox',
value: AppSettings.webdavStatReload ? 'true' : null
};
@ -408,6 +412,7 @@ class StorageWebDav extends StorageBase {
watch: undefined;
unwatch: undefined;
getPathForName: undefined;
applyConfig: undefined;
}
export { StorageWebDav };

View File

@ -85,9 +85,7 @@ abstract class StorageBase {
}
abstract load(id: string, opts?: StorageFileOptions): Promise<StorageFileData>;
abstract stat(id: string, opts?: StorageFileOptions): Promise<StorageFileStat>;
abstract save(
id: string,
data: ArrayBuffer,
@ -96,22 +94,22 @@ abstract class StorageBase {
): Promise<StorageSaveResult>;
abstract remove?(id: string, opts?: StorageFileOptions): Promise<void>;
abstract list?(dir?: string): Promise<StorageListItem[]>;
abstract mkdir?(path: string): Promise<void>;
abstract watch?(path: string, callback: StorageFileWatcherCallback): void;
abstract unwatch?(path: string, callback: StorageFileWatcherCallback): void;
abstract getPathForName?(fileName: string): string;
abstract getOpenConfig?(): StorageOpenConfig;
abstract getSettingsConfig?(): StorageSettingsConfig;
abstract applyConfig?(config: Record<string, string | null>): Promise<void>;
abstract applySetting?(key: string, value: string | null): void;
protected getOAuthConfig(): StorageOAuthConfig {
throw new Error('OAuth is not supported');
}
abstract get enabled(): boolean;
abstract get locName(): string;
get loggedIn(): boolean {
return !!this.getStoredOAuthToken();
@ -494,6 +492,8 @@ abstract class StorageBase {
protected async oauthRevokeToken(url?: string, usePost?: boolean): Promise<void> {
const token = this.getStoredOAuthToken();
if (token) {
this.deleteStoredToken();
this._oauthToken = undefined;
if (url) {
await this.xhr({
url: url.replace('{token}', token.accessToken),
@ -501,8 +501,6 @@ abstract class StorageBase {
method: usePost ? 'POST' : 'GET'
});
}
this.deleteStoredToken();
this._oauthToken = undefined;
}
}
@ -568,7 +566,7 @@ abstract class StorageBase {
}
this._logger.info('OAuth code exchanged', response);
this.oauthProcessReturn(response);
this.oauthProcessReturn(response.data);
}
private async oauthExchangeRefreshToken(): Promise<void> {
@ -617,24 +615,6 @@ abstract class StorageBase {
get needShowOpenConfig(): boolean {
return false;
}
getOpenConfig(): StorageOpenConfig {
throw new Error('getOpenConfig is not implemented');
}
getSettingsConfig(): StorageSettingsConfig {
throw new Error('getSettingsConfig is not implemented');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
applyConfig(config: Record<string, string | null>): Promise<void> {
throw new Error('applyConfig is not implemented');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
applySetting(key: string, value: string | null): void {
throw new Error('applySetting is not implemented');
}
}
export { StorageBase };

View File

@ -74,8 +74,7 @@ export interface StorageSaveResult {
path?: string;
}
export interface StorageConfigField {
type: string;
export interface StorageConfigFieldBase {
id: string;
title: string;
desc?: string;
@ -83,27 +82,33 @@ export interface StorageConfigField {
value?: string | null;
}
export interface StorageConfigFieldText extends StorageConfigField {
export interface StorageConfigFieldText extends StorageConfigFieldBase {
type: 'text';
pattern?: string;
placeholder?: string;
}
export interface StorageConfigFieldPassword extends StorageConfigField {
export interface StorageConfigFieldPassword extends StorageConfigFieldBase {
type: 'password';
pattern?: string;
placeholder?: string;
}
export interface StorageConfigFieldSelect extends StorageConfigField {
export interface StorageConfigFieldSelect extends StorageConfigFieldBase {
type: 'select';
options: Record<string, string>;
}
export interface StorageConfigFieldCheckbox extends StorageConfigField {
export interface StorageConfigFieldCheckbox extends StorageConfigFieldBase {
type: 'checkbox';
}
export type StorageConfigField =
| StorageConfigFieldText
| StorageConfigFieldPassword
| StorageConfigFieldSelect
| StorageConfigFieldCheckbox;
export interface StorageOpenConfig {
desc?: string;
fields: StorageConfigField[];

View File

@ -1,8 +1,6 @@
import { Events } from 'framework/events';
import { View } from 'framework/views/view';
import { AutoType } from 'auto-type';
import { Storage } from 'storage';
import { RuntimeInfo } from 'const/runtime-info';
import { Updater } from 'comp/app/updater';
import { Launcher } from 'comp/launcher';
import { SettingsManager } from 'comp/settings/settings-manager';
@ -10,14 +8,9 @@ import { Alerts } from 'comp/ui/alerts';
import { Links } from 'const/links';
import { AppSettingsModel } from 'models/app-settings-model';
import { UpdateModel } from 'models/update-model';
import { SemVer } from 'util/data/semver';
import { Features } from 'util/features';
import { DateFormat } from 'comp/i18n/date-format';
import { Locale } from 'util/locale';
import { SettingsLogsView } from 'views/settings/settings-logs-view';
import { SettingsPrvView } from 'views/settings/settings-prv-view';
import { mapObject, minmax } from 'util/fn';
import { ThemeWatcher } from 'comp/browser/theme-watcher';
import { minmax } from 'util/fn';
import { NativeModules } from 'comp/launcher/native-modules';
import template from 'templates/settings/settings-general.hbs';
@ -80,184 +73,6 @@ class SettingsGeneralView extends View {
this.listenTo(Events, 'theme-applied', this.render);
}
render() {
const updateReady = UpdateModel.updateStatus === 'ready';
const updateFound = UpdateModel.updateStatus === 'found';
const updateManual = UpdateModel.updateManual;
const storageProviders = this.getStorageProviders();
super.render({
themes: this.getAllThemes(),
autoSwitchTheme: AppSettingsModel.autoSwitchTheme,
activeTheme: SettingsManager.activeTheme,
locales: SettingsManager.allLocales,
activeLocale: SettingsManager.activeLocale,
fontSize: AppSettingsModel.fontSize,
expandGroups: AppSettingsModel.expandGroups,
canClearClipboard: !!Launcher,
clipboardSeconds: AppSettingsModel.clipboardSeconds,
rememberKeyFiles: AppSettingsModel.rememberKeyFiles,
supportFiles: !!Launcher,
autoSave: AppSettingsModel.autoSave,
autoSaveInterval: AppSettingsModel.autoSaveInterval,
idleMinutes: AppSettingsModel.idleMinutes,
minimizeOnClose: AppSettingsModel.minimizeOnClose,
minimizeOnFieldCopy: AppSettingsModel.minimizeOnFieldCopy,
devTools: Launcher && Launcher.devTools,
canAutoUpdate: Updater.enabled,
canAutoSaveOnClose: !!Launcher,
canMinimize: !!Launcher,
canDetectMinimize: !!Launcher,
canDetectOsSleep: Launcher && Launcher.canDetectOsSleep(),
canAutoType: AutoType.enabled,
auditPasswords: AppSettingsModel.auditPasswords,
auditPasswordEntropy: AppSettingsModel.auditPasswordEntropy,
excludePinsFromAudit: AppSettingsModel.excludePinsFromAudit,
checkPasswordsOnHIBP: AppSettingsModel.checkPasswordsOnHIBP,
auditPasswordAge: AppSettingsModel.auditPasswordAge,
hibpLink: Links.HaveIBeenPwned,
hibpPrivacyLink: Links.HaveIBeenPwnedPrivacy,
lockOnMinimize: Launcher && AppSettingsModel.lockOnMinimize,
lockOnCopy: AppSettingsModel.lockOnCopy,
lockOnAutoType: AppSettingsModel.lockOnAutoType,
lockOnOsLock: AppSettingsModel.lockOnOsLock,
tableView: AppSettingsModel.tableView,
canSetTableView: !Features.isMobile,
autoUpdate: Updater.getAutoUpdateType(),
updateInProgress: Updater.updateInProgress(),
updateInfo: this.getUpdateInfo(),
updateWaitingReload: updateReady && !Launcher,
showUpdateBlock: Updater.enabled && !updateManual,
updateReady,
updateFound,
updateManual,
releaseNotesLink: Links.ReleaseNotes,
colorfulIcons: AppSettingsModel.colorfulIcons,
useMarkdown: AppSettingsModel.useMarkdown,
useGroupIconForEntries: AppSettingsModel.useGroupIconForEntries,
directAutotype: AppSettingsModel.directAutotype,
autoTypeTitleFilterEnabled: AppSettingsModel.autoTypeTitleFilterEnabled,
fieldLabelDblClickAutoType: AppSettingsModel.fieldLabelDblClickAutoType,
supportsTitleBarStyles: Features.supportsTitleBarStyles,
supportsCustomTitleBarAndDraggableWindow:
Features.supportsCustomTitleBarAndDraggableWindow,
titlebarStyle: AppSettingsModel.titlebarStyle,
storageProviders,
showReloadApp: Features.isStandalone,
hasDeviceOwnerAuth: Features.isDesktop && Features.isMac,
deviceOwnerAuth: AppSettingsModel.deviceOwnerAuth,
deviceOwnerAuthTimeout: AppSettingsModel.deviceOwnerAuthTimeoutMinutes,
disableOfflineStorage: AppSettingsModel.disableOfflineStorage,
shortLivedStorageToken: AppSettingsModel.shortLivedStorageToken
});
this.renderProviderViews(storageProviders);
}
renderProviderViews(storageProviders) {
storageProviders.forEach(function (prv) {
if (this.views[prv.name]) {
this.views[prv.name].remove();
}
if (prv.hasConfig) {
const prvView = new SettingsPrvView(prv, {
parent: this.$el.find('.settings__general-' + prv.name)[0]
});
this.views[prv.name] = prvView;
prvView.render();
}
}, this);
}
getUpdateInfo() {
switch (UpdateModel.status) {
case 'checking':
return Locale.setGenUpdateChecking + '...';
case 'error': {
let errMsg = Locale.setGenErrorChecking;
if (UpdateModel.lastError) {
errMsg += ': ' + UpdateModel.lastError;
}
if (UpdateModel.lastSuccessCheckDate) {
errMsg +=
'. ' +
Locale.setGenLastCheckSuccess.replace(
'{}',
DateFormat.dtStr(UpdateModel.lastSuccessCheckDate)
) +
': ' +
Locale.setGenLastCheckVer.replace('{}', UpdateModel.lastVersion);
}
return errMsg;
}
case 'ok': {
let msg =
Locale.setGenCheckedAt +
' ' +
DateFormat.dtStr(UpdateModel.lastCheckDate) +
': ';
const cmp = SemVer.compareVersions(RuntimeInfo.version, UpdateModel.lastVersion);
if (cmp >= 0) {
msg += Locale.setGenLatestVer;
} else {
msg +=
Locale.setGenNewVer.replace('{}', UpdateModel.lastVersion) +
' ' +
DateFormat.dStr(UpdateModel.lastVersionReleaseDate);
}
switch (UpdateModel.updateStatus) {
case 'downloading':
return msg + '. ' + Locale.setGenDownloadingUpdate;
case 'extracting':
return msg + '. ' + Locale.setGenExtractingUpdate;
case 'error':
return msg + '. ' + Locale.setGenCheckErr;
}
return msg;
}
default:
return Locale.setGenNeverChecked;
}
}
getStorageProviders() {
const storageProviders = [];
Object.keys(Storage).forEach((name) => {
const prv = Storage[name];
if (!prv.system) {
storageProviders.push(prv);
}
});
storageProviders.sort((x, y) => (x.uipos || Infinity) - (y.uipos || Infinity));
return storageProviders.map((sp) => ({
name: sp.name,
enabled: sp.enabled,
hasConfig: !!sp.getSettingsConfig,
loggedIn: sp.loggedIn
}));
}
getAllThemes() {
const { autoSwitchTheme } = AppSettingsModel;
if (autoSwitchTheme) {
const themes = {};
const ignoredThemes = {};
for (const config of SettingsManager.autoSwitchedThemes) {
ignoredThemes[config.dark] = true;
ignoredThemes[config.light] = true;
const activeTheme = ThemeWatcher.dark ? config.dark : config.light;
themes[activeTheme] = Locale[config.name];
}
for (const [th, name] of Object.entries(SettingsManager.allThemes)) {
if (!ignoredThemes[th]) {
themes[th] = Locale[name];
}
}
return themes;
} else {
return mapObject(SettingsManager.allThemes, (theme) => Locale[theme]);
}
}
changeTheme(e) {
const theme = e.target.closest('.settings__general-theme').dataset.theme;
if (theme === '...') {

View File

@ -1,42 +0,0 @@
import { View } from 'framework/views/view';
import { Storage } from 'storage';
import template from 'templates/settings/settings-prv.hbs';
class SettingsPrvView extends View {
template = template;
events = {
'change .settings__general-prv-field-sel': 'changeField',
'input .settings__general-prv-field-txt': 'changeField',
'change .settings__general-prv-field-check': 'changeCheckbox'
};
render() {
const storage = Storage[this.model.name];
if (storage && storage.getSettingsConfig) {
super.render(storage.getSettingsConfig());
}
}
changeField(e) {
const id = e.target.dataset.id;
const value = e.target.value;
if (value && !e.target.checkValidity()) {
return;
}
const storage = Storage[this.model.name];
storage.applySetting(id, value);
if ($(e.target).is('select')) {
this.render();
}
}
changeCheckbox(e) {
const id = e.target.dataset.id;
const value = !!e.target.checked;
const storage = Storage[this.model.name];
storage.applySetting(id, value);
}
}
export { SettingsPrvView };

View File

@ -11,6 +11,7 @@ import { OpenController } from 'comp/app/open-controller';
import { OpenState } from 'models/ui/open-state';
import { GeneratorState } from 'models/ui/generator-state';
import { Alerts } from 'comp/ui/alerts';
import { StorageBase } from 'storage/storage-base';
const logger = new Logger('open');
@ -18,7 +19,7 @@ export const OpenButtons: FunctionComponent = () => {
const secondRowVisible = useModelField(OpenState, 'secondRowVisible');
const storageInProgress = useModelField(OpenState, 'storageInProgress');
const storageProviders = [];
const storageProviders: StorageBase[] = [];
if (AppSettings.canOpenStorage) {
for (const prv of Object.values(Storage.getAll())) {

View File

@ -10,7 +10,16 @@ import {
PasswordGeneratorPreset
} from 'util/generators/password-generator';
import { Locale } from 'util/locale';
import { StringFormat } from 'util/formatting/string-format';
const CharRangeTitles: Record<CharRange, string> = {
upper: Locale.genPsUpper,
lower: Locale.genPsLower,
digits: Locale.genPsDigits,
special: Locale.genPsSpecial,
brackets: Locale.genPsBrackets,
high: Locale.genPsHigh,
ambiguous: Locale.genPsAmbiguous
};
export const GeneratorPresetsPanel: FunctionComponent = () => {
const backClicked = () => Workspace.showList();
@ -58,7 +67,7 @@ export const GeneratorPresetsPanel: FunctionComponent = () => {
const range = key as CharRange;
return {
name: range,
title: Locale.get(`genPs${StringFormat.capFirst(range)}`) ?? '',
title: CharRangeTitles[range] ?? '',
enabled: !!selected[range],
sample: rangeOverride[range] || CharRanges[range]
};

View File

@ -1,6 +1,11 @@
import { FunctionComponent, h } from 'preact';
import { SettingsGeneralAdvancedView } from 'views/settings/general/settings-general-advanced-view';
import { Launcher } from 'comp/launcher';
import { Features } from 'util/features';
export const SettingsGeneralAdvanced: FunctionComponent = () => {
return h(SettingsGeneralAdvancedView, null);
return h(SettingsGeneralAdvancedView, {
devTools: !!Launcher,
showReloadApp: Features.isStandalone
});
};

View File

@ -1,6 +1,48 @@
import { FunctionComponent, h } from 'preact';
import { SettingsGeneralAppearanceView } from 'views/settings/general/settings-general-appearance-view';
import { SettingsManager } from 'comp/settings/settings-manager';
import { Locale } from 'util/locale';
import { ThemeWatcher } from 'comp/browser/theme-watcher';
import { AppSettings } from 'models/app-settings';
import { Features } from 'util/features';
export const SettingsGeneralAppearance: FunctionComponent = () => {
return h(SettingsGeneralAppearanceView, null);
const getAllThemes = () => {
const themes: Record<string, string> = {};
if (AppSettings.autoSwitchTheme) {
const ignoredThemes = new Set<string>();
for (const config of SettingsManager.autoSwitchedThemes) {
ignoredThemes.add(config.dark);
ignoredThemes.add(config.light);
const activeTheme = ThemeWatcher.dark ? config.dark : config.light;
themes[activeTheme] = config.name;
}
for (const [th, name] of Object.entries(SettingsManager.allThemes)) {
if (!ignoredThemes.has(th)) {
themes[th] = name;
}
}
} else {
for (const [th, name] of Object.entries(SettingsManager.allThemes)) {
themes[th] = name;
}
}
return themes;
};
return h(SettingsGeneralAppearanceView, {
locales: SettingsManager.allLocales,
activeLocale: Locale.localeName,
themes: getAllThemes(),
activeTheme: SettingsManager.activeTheme ?? '',
autoSwitchTheme: AppSettings.autoSwitchTheme,
fontSize: AppSettings.fontSize,
supportsTitleBarStyles: Features.supportsTitleBarStyles,
titlebarStyle: AppSettings.titlebarStyle,
supportsCustomTitleBarAndDraggableWindow: Features.supportsCustomTitleBarAndDraggableWindow,
expandGroups: AppSettings.expandGroups,
canSetTableView: !Features.isMobile,
tableView: AppSettings.tableView,
colorfulIcons: AppSettings.colorfulIcons
});
};

View File

@ -1,6 +1,13 @@
import { FunctionComponent, h } from 'preact';
import { SettingsGeneralAuditView } from 'views/settings/general/settings-general-audit-view';
import { AppSettings } from 'models/app-settings';
export const SettingsGeneralAudit: FunctionComponent = () => {
return h(SettingsGeneralAuditView, null);
return h(SettingsGeneralAuditView, {
auditPasswords: AppSettings.auditPasswords,
auditPasswordEntropy: AppSettings.auditPasswordEntropy,
excludePinsFromAudit: AppSettings.excludePinsFromAudit,
checkPasswordsOnHIBP: AppSettings.checkPasswordsOnHIBP,
auditPasswordAge: AppSettings.auditPasswordAge
});
};

View File

@ -1,6 +1,29 @@
import { FunctionComponent, h } from 'preact';
import { SettingsGeneralFunctionView } from 'views/settings/general/settings-general-function-view';
import { Launcher } from 'comp/launcher';
import { AppSettings } from 'models/app-settings';
import { Features } from 'util/features';
export const SettingsGeneralFunction: FunctionComponent = () => {
return h(SettingsGeneralFunctionView, null);
return h(SettingsGeneralFunctionView, {
canAutoSaveOnClose: !!Launcher,
autoSave: AppSettings.autoSave,
autoSaveInterval: AppSettings.autoSaveInterval,
rememberKeyFiles: AppSettings.rememberKeyFiles,
supportFiles: !!Launcher,
canClearClipboard: !!Launcher,
clipboardSeconds: AppSettings.clipboardSeconds,
canMinimize: !!Launcher,
minimizeOnClose: AppSettings.minimizeOnClose,
minimizeOnFieldCopy: AppSettings.minimizeOnFieldCopy,
canAutoType: !!Launcher,
directAutotype: AppSettings.directAutotype,
autoTypeTitleFilterEnabled: AppSettings.autoTypeTitleFilterEnabled,
fieldLabelDblClickAutoType: AppSettings.fieldLabelDblClickAutoType,
useMarkdown: AppSettings.useMarkdown,
useGroupIconForEntries: AppSettings.useGroupIconForEntries,
hasDeviceOwnerAuth: Features.isDesktop && Features.isMac,
deviceOwnerAuth: AppSettings.deviceOwnerAuth,
deviceOwnerAuthTimeout: AppSettings.deviceOwnerAuthTimeoutMinutes
});
};

View File

@ -1,6 +1,17 @@
import { FunctionComponent, h } from 'preact';
import { SettingsGeneralLockView } from 'views/settings/general/settings-general-lock-view';
import { AppSettings } from 'models/app-settings';
import { Launcher } from 'comp/launcher';
export const SettingsGeneralLock: FunctionComponent = () => {
return h(SettingsGeneralLockView, null);
return h(SettingsGeneralLockView, {
idleMinutes: AppSettings.idleMinutes,
canDetectMinimize: !!Launcher,
lockOnMinimize: AppSettings.lockOnMinimize,
lockOnCopy: AppSettings.lockOnCopy,
canAutoType: !!Launcher,
lockOnAutoType: AppSettings.lockOnAutoType,
canDetectOsSleep: !!Launcher,
lockOnOsLock: AppSettings.lockOnOsLock
});
};

View File

@ -0,0 +1,26 @@
import { FunctionComponent, h } from 'preact';
import { SettingsGeneralStorageProviderView } from 'views/settings/general/settings-general-storage-provider-view';
import { Storage } from 'storage';
export const SettingsGeneralStorageProvider: FunctionComponent<{ name: string }> = ({ name }) => {
const storage = Storage.get(name);
if (!storage) {
return null;
}
const config = storage.getSettingsConfig?.();
if (!config) {
return null;
}
const fieldChanged = (id: string, value: string | null) => {
storage.applySetting?.(id, value);
};
return h(SettingsGeneralStorageProviderView, {
name,
fields: config.fields,
fieldChanged
});
};

View File

@ -1,6 +1,24 @@
import { FunctionComponent, h } from 'preact';
import { SettingsGeneralStorageView } from 'views/settings/general/settings-general-storage-view';
import { AppSettings } from 'models/app-settings';
import { Storage } from 'storage';
export const SettingsGeneralStorage: FunctionComponent = () => {
return h(SettingsGeneralStorageView, null);
const getStorageProviders = () => {
const storageProviders = Object.values(Storage.getAll()).filter((prv) => !prv.system);
storageProviders.sort((x, y) => (x.uipos || Infinity) - (y.uipos || Infinity));
return storageProviders.map((sp) => ({
name: sp.name,
locName: sp.locName,
enabled: sp.enabled,
hasConfig: !!sp.getSettingsConfig,
loggedIn: sp.loggedIn
}));
};
return h(SettingsGeneralStorageView, {
disableOfflineStorage: AppSettings.disableOfflineStorage,
shortLivedStorageToken: AppSettings.shortLivedStorageToken,
storageProviders: getStorageProviders()
});
};

View File

@ -1,6 +1,85 @@
import { FunctionComponent, h } from 'preact';
import { SettingsGeneralUpdateView } from 'views/settings/general/settings-general-update-view';
import { Updater } from 'comp/app/updater';
import { Launcher } from 'comp/launcher';
import { AppSettings } from 'models/app-settings';
import { useModelWatcher } from 'util/ui/hooks';
import { Locale } from 'util/locale';
import { SemVer } from 'util/data/semver';
import { RuntimeInfo } from 'const/runtime-info';
import { DateFormat } from 'util/formatting/date-format';
import { RuntimeData } from 'models/runtime-data';
export const SettingsGeneralUpdate: FunctionComponent = () => {
return h(SettingsGeneralUpdateView, null);
useModelWatcher(Updater);
const getUpdateInfo = () => {
switch (Updater.status) {
case 'checking':
return Locale.setGenUpdateChecking + '...';
case 'error': {
let errMsg = Locale.setGenErrorChecking;
if (Updater.updateError) {
errMsg += ': ' + Updater.updateError;
}
if (RuntimeData.lastSuccessUpdateCheckDate && RuntimeData.lastUpdateVersion) {
errMsg +=
'. ' +
Locale.setGenLastCheckSuccess.with(
DateFormat.dtStr(RuntimeData.lastSuccessUpdateCheckDate)
) +
': ' +
Locale.setGenLastCheckVer.with(RuntimeData.lastUpdateVersion);
}
return errMsg;
}
case 'ok': {
if (!RuntimeData.lastUpdateVersion) {
return Locale.setGenErrorChecking + ': no version';
}
let msg =
Locale.setGenCheckedAt +
' ' +
(RuntimeData.lastUpdateCheckDate
? DateFormat.dtStr(RuntimeData.lastUpdateCheckDate)
: '') +
': ';
const cmp = SemVer.compareVersions(
RuntimeInfo.version,
RuntimeData.lastUpdateVersion
);
if (cmp >= 0) {
msg += Locale.setGenLatestVer;
} else {
msg +=
Locale.setGenNewVer.with(RuntimeData.lastUpdateVersion) +
' ' +
(RuntimeData.lastUpdateVersionReleaseDate
? DateFormat.dStr(RuntimeData.lastUpdateVersionReleaseDate)
: '');
}
switch (Updater.updateStatus) {
case 'downloading':
return msg + '. ' + Locale.setGenDownloadingUpdate;
case 'extracting':
return msg + '. ' + Locale.setGenExtractingUpdate;
case 'error':
return msg + '. ' + Locale.setGenCheckErr;
}
return msg;
}
default:
return Locale.setGenNeverChecked;
}
};
return h(SettingsGeneralUpdateView, {
updateWaitingReload: Updater.updateStatus === 'ready' && !Launcher,
autoUpdate: AppSettings.autoUpdate,
showUpdateBlock: Updater.enabled,
updateInfo: getUpdateInfo(),
updateInProgress: Updater.updateInProgress,
updateReady: Updater.updateStatus === 'ready',
updateFound: Updater.updateStatus === 'found'
});
};

View File

@ -1,6 +1,10 @@
import { FunctionComponent, h } from 'preact';
import { SettingsGeneralView } from 'views/settings/settings-general-view';
import { useModelWatcher } from 'util/ui/hooks';
import { AppSettings } from 'models/app-settings';
export const SettingsGeneral: FunctionComponent = () => {
useModelWatcher(AppSettings);
return h(SettingsGeneralView, null);
};

View File

@ -26,7 +26,7 @@ function withReplace(name: string): LocWithReplace {
// prettier-ignore
export const Locale = {
set,
get,
// get,
get localeName(): string { return activeLocaleName; },
// this code is generated using npm run generate-locale

View File

@ -1,8 +1,17 @@
import { FunctionComponent } from 'preact';
import { LocWithReplace } from 'util/locale';
import { StringFormat } from 'util/formatting/string-format';
export const LocalizedWith: FunctionComponent<{ str: LocWithReplace }> = ({ str, children }) => {
const [first, ...rest] = str.with('{}').split('{}');
export const LocalizedWith: FunctionComponent<{ str: LocWithReplace; capitalize?: boolean }> = ({
str,
capitalize,
children
}) => {
let val = str.with('{}');
if (capitalize) {
val = StringFormat.capFirst(val);
}
const [first, ...rest] = val.split('{}');
return (
<>
{first}

View File

@ -6,6 +6,7 @@ import { classes } from 'util/ui/classes';
interface StorageProvider {
name: string;
locName: string;
icon?: string;
}
@ -126,7 +127,7 @@ export const OpenButtonsView: FunctionComponent<{
onClick={() => storageClicked(prv.name)}
>
{prv.icon ? <i class={`fa fa-${prv.icon} open__icon-i`} /> : null}
<div class="open__icon-text">{Locale.get(prv.name)}</div>
<div class="open__icon-text">{prv.locName}</div>
</div>
))}
{showDemoInSecondRow ? (

View File

@ -1,5 +1,34 @@
import { FunctionComponent } from 'preact';
import { Locale } from 'util/locale';
export const SettingsGeneralAdvancedView: FunctionComponent = () => {
return <></>;
export const SettingsGeneralAdvancedView: FunctionComponent<{
devTools: boolean;
showReloadApp: boolean;
}> = ({ devTools, showReloadApp }) => {
return (
<>
<h2 id="advanced">{Locale.advanced}</h2>
<a class="settings__general-show-advanced">{Locale.setGenShowAdvanced}</a>
<div class="settings__general-advanced hide">
{devTools ? (
<>
<button class="btn-silent settings__general-dev-tools-link">
{Locale.setGenDevTools}
</button>
<button class="btn-silent settings__general-try-beta-link">
{Locale.setGenTryBeta}
</button>
</>
) : null}
{showReloadApp ? (
<button class="btn-silent settings__general-reload-app-link">
{Locale.setGenReloadApp}
</button>
) : null}
<button class="btn-silent settings__general-show-logs-link">
{Locale.setGenShowAppLogs}
</button>
</div>
</>
);
};

View File

@ -1,5 +1,172 @@
import { FunctionComponent } from 'preact';
import { Locale } from 'util/locale';
import { AppSettingsFontSize, AppSettingsTitlebarStyle } from 'models/app-settings';
import { classes } from 'util/ui/classes';
export const SettingsGeneralAppearanceView: FunctionComponent = () => {
return <></>;
export const SettingsGeneralAppearanceView: FunctionComponent<{
locales: Record<string, string>;
activeLocale: string;
themes: Record<string, string>;
activeTheme: string;
autoSwitchTheme: boolean;
fontSize: AppSettingsFontSize;
supportsTitleBarStyles: boolean;
titlebarStyle: AppSettingsTitlebarStyle;
supportsCustomTitleBarAndDraggableWindow: boolean;
expandGroups: boolean;
canSetTableView: boolean;
tableView: boolean;
colorfulIcons: boolean;
}> = ({
locales,
activeLocale,
themes,
activeTheme,
autoSwitchTheme,
fontSize,
supportsTitleBarStyles,
titlebarStyle,
supportsCustomTitleBarAndDraggableWindow,
expandGroups,
canSetTableView,
tableView,
colorfulIcons
}) => {
return (
<>
<h2 id="appearance">{Locale.setGenAppearance}</h2>
<div>
<label for="settings__general-locale">{Locale.setGenLocale}:</label>
<select
class="settings__general-locale settings__select input-base"
id="settings__general-locale"
>
{Object.entries(locales).map(([key, name]) => (
<option key={key} value={key} selected={key === activeLocale}>
{name}
</option>
))}
<option value="...">({Locale.setGenLocOther})</option>
</select>
</div>
<div>
<label>{Locale.setGenTheme}:</label>
<div class="settings__general-themes">
{Object.entries(themes).map(([key, name]) => (
<div
key={key}
class={classes({
[`th-${key}`]: true,
'settings__general-theme': true,
'settings__general-theme--selected': key === activeTheme
})}
data-theme={key}
>
<div class="settings__general-theme-name">{name}</div>
<button class="settings__general-theme-button">
<i class="fa fa-ellipsis-h" />
</button>
</div>
))}
<div
class="settings__general-theme settings__general-theme-plugins"
data-theme="..."
>
<div class="settings__general-theme-plugins-name">
{Locale.setGenMoreThemes}
</div>
<i class="settings__general-theme-plugins-icon fa fa-puzzle-piece" />
</div>
</div>
</div>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-auto-switch-theme"
id="settings__general-auto-switch-theme"
checked={autoSwitchTheme}
/>
<label for="settings__general-auto-switch-theme">
{Locale.setGenAutoSwitchTheme}
</label>
</div>
<div>
<label for="settings__general-font-size">{Locale.setGenFontSize}:</label>
<select
class="settings__general-font-size settings__select input-base"
id="settings__general-font-size"
>
<option value="0" selected={fontSize === 0}>
{Locale.setGenFontSizeNormal}
</option>
<option value="1" selected={fontSize === 1}>
{Locale.setGenFontSizeLarge}
</option>
<option value="2" selected={fontSize === 2}>
{Locale.setGenFontSizeLargest}
</option>
</select>
</div>
{supportsTitleBarStyles ? (
<>
<div>
<label for="settings__general-titlebar-style">
{Locale.setGenTitlebarStyle}:
</label>
<select
class="settings__general-titlebar-style settings__select input-base"
id="settings__general-titlebar-style"
>
<option value="default" selected={titlebarStyle === 'default'}>
{Locale.setGenTitlebarStyleDefault}
</option>
<option value="hidden" selected={titlebarStyle === 'hidden'}>
{Locale.setGenTitlebarStyleHidden}
</option>
{supportsCustomTitleBarAndDraggableWindow ? (
<option
value="hidden-inset"
selected={titlebarStyle === 'hidden-inset'}
>
{Locale.setGenTitlebarStyleHiddenInset}
</option>
) : null}
</select>
</div>
</>
) : null}
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-expand"
id="settings__general-expand"
checked={expandGroups}
/>
<label for="settings__general-expand">{Locale.setGenShowSubgroups}</label>
</div>
{canSetTableView ? (
<>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-table-view"
id="settings__general-table-view"
checked={tableView}
/>
<label for="settings__general-table-view">{Locale.setGenTableView}</label>
</div>
</>
) : null}
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-colorful-icons"
id="settings__general-colorful-icons"
checked={colorfulIcons}
/>
<label for="settings__general-colorful-icons">{Locale.setGenColorfulIcons}</label>
</div>
</>
);
};

View File

@ -1,5 +1,111 @@
import { FunctionComponent } from 'preact';
import { Locale } from 'util/locale';
import { LocalizedWith } from 'views/components/localized-with';
import { Links } from 'const/links';
export const SettingsGeneralAuditView: FunctionComponent = () => {
return <></>;
export const SettingsGeneralAuditView: FunctionComponent<{
auditPasswords: boolean;
auditPasswordEntropy: boolean;
excludePinsFromAudit: boolean;
checkPasswordsOnHIBP: boolean;
auditPasswordAge: number;
}> = ({
auditPasswords,
auditPasswordEntropy,
excludePinsFromAudit,
checkPasswordsOnHIBP,
auditPasswordAge
}) => {
return (
<>
<h2 id="audit">{Locale.setGenAudit}</h2>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-audit-passwords"
id="settings__general-audit-passwords"
checked={auditPasswords}
/>
<label for="settings__general-audit-passwords">{Locale.setGenAuditPasswords}</label>
</div>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-audit-password-entropy"
id="settings__general-audit-password-entropy"
checked={auditPasswordEntropy}
/>
<label for="settings__general-audit-password-entropy">
{Locale.setGenAuditPasswordEntropy}
</label>
</div>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-exclude-pins-from-audit"
id="settings__general-exclude-pins-from-audit"
checked={excludePinsFromAudit}
/>
<label for="settings__general-exclude-pins-from-audit">
{Locale.setGenExcludePinsFromAudit}
</label>
</div>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-check-passwords-on-hibp"
id="settings__general-check-passwords-on-hibp"
checked={checkPasswordsOnHIBP}
/>
<label for="settings__general-check-passwords-on-hibp">
<LocalizedWith str={Locale.setGenCheckPasswordsOnHIBP}>
<a href={Links.HaveIBeenPwned} rel="noreferrer noopener" target="_blank">
Have I Been Pwned
</a>
</LocalizedWith>
</label>
<i class="fa fa-info-circle info-btn settings__general-toggle-help-hibp" />
<div class="settings__general-help-hibp hide">
<LocalizedWith str={Locale.setGenHelpHIBP}>
<a
href={Links.HaveIBeenPwnedPrivacy}
rel="noreferrer noopener"
target="_blank"
>
{Locale.setGenHelpHIBPLink}
</a>
</LocalizedWith>
</div>
</div>
<div>
<label for="settings__general-audit-password-age">
{Locale.setGenAuditPasswordAge}:
</label>
<select
class="settings__select input-base settings__general-audit-password-age"
id="settings__general-audit-password-age"
value={auditPasswordAge}
>
<option value="0">{Locale.setGenAuditPasswordAgeOff}</option>
<option value="1">{Locale.setGenAuditPasswordAgeOneYear}</option>
<option value="2">
<LocalizedWith str={Locale.setGenAuditPasswordAgeYears}>2</LocalizedWith>
</option>
<option value="3">
<LocalizedWith str={Locale.setGenAuditPasswordAgeYears}>3</LocalizedWith>
</option>
<option value="5">
<LocalizedWith str={Locale.setGenAuditPasswordAgeYears}>5</LocalizedWith>
</option>
<option value="10">
<LocalizedWith str={Locale.setGenAuditPasswordAgeYears}>10</LocalizedWith>
</option>
</select>
</div>
</>
);
};

View File

@ -1,5 +1,288 @@
import { FunctionComponent } from 'preact';
import { Locale } from 'util/locale';
import { LocalizedWith } from 'views/components/localized-with';
import { AppSettingsDeviceOwnerAuth, AppSettingsRememberKeyFiles } from 'models/app-settings';
import { StringFormat } from 'util/formatting/string-format';
export const SettingsGeneralFunctionView: FunctionComponent = () => {
return <></>;
export const SettingsGeneralFunctionView: FunctionComponent<{
canAutoSaveOnClose: boolean;
autoSave: boolean;
autoSaveInterval: number;
rememberKeyFiles: AppSettingsRememberKeyFiles | null;
supportFiles: boolean;
canClearClipboard: boolean;
clipboardSeconds: number;
canMinimize: boolean;
minimizeOnClose: boolean;
minimizeOnFieldCopy: boolean;
canAutoType: boolean;
directAutotype: boolean;
autoTypeTitleFilterEnabled: boolean;
fieldLabelDblClickAutoType: boolean;
useMarkdown: boolean;
useGroupIconForEntries: boolean;
hasDeviceOwnerAuth: boolean;
deviceOwnerAuth: AppSettingsDeviceOwnerAuth | null;
deviceOwnerAuthTimeout: number;
}> = ({
canAutoSaveOnClose,
autoSave,
autoSaveInterval,
rememberKeyFiles,
supportFiles,
canClearClipboard,
clipboardSeconds,
canMinimize,
minimizeOnClose,
minimizeOnFieldCopy,
canAutoType,
directAutotype,
autoTypeTitleFilterEnabled,
fieldLabelDblClickAutoType,
useMarkdown,
useGroupIconForEntries,
hasDeviceOwnerAuth,
deviceOwnerAuth,
deviceOwnerAuthTimeout
}) => {
return (
<>
<h2 id="function">{Locale.setGenFunction}</h2>
{canAutoSaveOnClose ? (
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-auto-save"
id="settings__general-auto-save"
checked={autoSave}
/>
<label for="settings__general-auto-save">{Locale.setGenAutoSyncOnClose}</label>
</div>
) : null}
<div>
<label for="settings__general-auto-save-interval">
{Locale.setGenAutoSyncTimer}:
</label>
<select
class="settings__select input-base settings__general-auto-save-interval"
id="settings__general-auto-save-interval"
value={autoSaveInterval}
>
<option value="0">{Locale.setGenAutoSyncTimerOff}</option>
<option value="-1">{Locale.setGenAutoSyncTimerOnChange}</option>
<option value="1">
<LocalizedWith str={Locale.setGenAutoSyncTimerInterval}>1</LocalizedWith>
</option>
<option value="5">
<LocalizedWith str={Locale.setGenAutoSyncTimerInterval}>5</LocalizedWith>
</option>
<option value="15">
<LocalizedWith str={Locale.setGenAutoSyncTimerInterval}>15</LocalizedWith>
</option>
<option value="30">
<LocalizedWith str={Locale.setGenAutoSyncTimerInterval}>30</LocalizedWith>
</option>
<option value="60">
<LocalizedWith str={Locale.setGenAutoSyncTimerInterval}>60</LocalizedWith>
</option>
</select>
</div>
<div>
<label for="settings__general-remember-key-files">
{Locale.setGenRememberKeyFiles}:
</label>
<select
class="settings__general-remember-key-files settings__select input-base"
id="settings__general-remember-key-files"
>
<option value="" selected={!rememberKeyFiles}>
{Locale.setGenNoRememberKeyFiles}
</option>
<option value="data" selected={rememberKeyFiles === 'data'}>
{Locale.setGenRememberKeyFilesData}
</option>
{supportFiles ? (
<option value="path" selected={rememberKeyFiles === 'path'}>
{Locale.setGenRememberKeyFilesPath}
</option>
) : null}
</select>
</div>
{canClearClipboard ? (
<div>
<label for="settings__general-clipboard">{Locale.setGenClearClip}:</label>
<select
class="settings__general-clipboard settings__select input-base"
id="settings__general-clipboard"
value={clipboardSeconds}
>
<option value="0">{Locale.setGenNoClear}</option>
<option value="5">
<LocalizedWith str={Locale.setGenClearSeconds}>5</LocalizedWith>
</option>
<option value="10">
<LocalizedWith str={Locale.setGenClearSeconds}>10</LocalizedWith>
</option>
<option value="15">
<LocalizedWith str={Locale.setGenClearSeconds}>15</LocalizedWith>
</option>
<option value="60">{Locale.setGenClearMinute}</option>
</select>
</div>
) : null}
{canMinimize ? (
<>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-minimize"
id="settings__general-minimize"
checked={minimizeOnClose}
/>
<label for="settings__general-minimize">{Locale.setGenMinInstead}</label>
</div>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-minimize-on-field-copy"
id="settings__general-minimize-on-field-copy"
checked={minimizeOnFieldCopy}
/>
<label for="settings__general-minimize-on-field-copy">
{Locale.setGenMinOnFieldCopy}
</label>
</div>
</>
) : null}
{canAutoType ? (
<>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-direct-autotype"
id="settings__general-direct-autotype"
checked={directAutotype}
/>
<label for="settings__general-direct-autotype">
{Locale.setGenDirectAutotype}
</label>
</div>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-autotype-title-filter"
id="settings__general-autotype-title-filter"
checked={autoTypeTitleFilterEnabled}
/>
<label for="settings__general-autotype-title-filter">
{Locale.setGenAutoTypeTitleFilterEnabled}
</label>
</div>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-field-label-dblclick-autotype"
id="settings__general-field-label-dblclick-autotype"
checked={fieldLabelDblClickAutoType}
/>
<label for="settings__general-field-label-dblclick-autotype">
{Locale.setGenFieldLabelDblClickAutoType}
</label>
</div>
</>
) : null}
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-use-markdown"
id="settings__general-use-markdown"
checked={useMarkdown}
/>
<label for="settings__general-use-markdown">{Locale.setGenUseMarkdown}</label>
</div>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-use-group-icon-for-entries"
id="settings__general-use-group-icon-for-entries"
checked={useGroupIconForEntries}
/>
<label for="settings__general-use-group-icon-for-entries">
{Locale.setGenUseGroupIconForEntries}
</label>
</div>
{hasDeviceOwnerAuth ? (
<>
<div>
<label for="settings__general-device-owner-auth">
{Locale.setGenTouchId}:
</label>
<select
class="settings__general-device-owner-auth settings__select input-base"
id="settings__general-device-owner-auth"
>
<option value="" selected={!deviceOwnerAuth}>
{Locale.setGenTouchIdDisabled}
</option>
<option value="memory" selected={deviceOwnerAuth === 'memory'}>
{Locale.setGenTouchIdMemory}
</option>
<option value="file" selected={deviceOwnerAuth === 'file'}>
{Locale.setGenTouchIdFile}
</option>
</select>
</div>
{deviceOwnerAuth ? (
<>
<label for="settings__general-device-owner-auth-timeout">
{Locale.setGenTouchIdPass}:
</label>
<select
class="settings__general-device-owner-auth-timeout settings__select input-base"
id="settings__general-device-owner-auth-timeout"
value={deviceOwnerAuthTimeout}
>
<option value="1">{StringFormat.capFirst(Locale.oneMinute)}</option>
<option value="5">
<LocalizedWith str={Locale.minutes} capitalize={true}>
5
</LocalizedWith>
</option>
<option value="30">
<LocalizedWith str={Locale.minutes} capitalize={true}>
30
</LocalizedWith>
</option>
<option value="60">{StringFormat.capFirst(Locale.oneHour)}</option>
<option value="120">
<LocalizedWith str={Locale.hours} capitalize={true}>
2
</LocalizedWith>
</option>
<option value="480">
<LocalizedWith str={Locale.hours} capitalize={true}>
8
</LocalizedWith>
</option>
<option value="1440">{StringFormat.capFirst(Locale.oneDay)}</option>
<option value="10080">
{StringFormat.capFirst(Locale.oneWeek)}
</option>
{deviceOwnerAuth === 'file' ? (
<option value="43200">
{StringFormat.capFirst(Locale.oneMonth)}
</option>
) : null}
{deviceOwnerAuth === 'file' ? (
<option value="525600">
{StringFormat.capFirst(Locale.oneYear)}
</option>
) : null}
</select>
</>
) : null}
</>
) : null}
</>
);
};

View File

@ -1,5 +1,114 @@
import { FunctionComponent } from 'preact';
import { Locale } from 'util/locale';
import { LocalizedWith } from 'views/components/localized-with';
export const SettingsGeneralLockView: FunctionComponent = () => {
return <></>;
export const SettingsGeneralLockView: FunctionComponent<{
idleMinutes: number;
canDetectMinimize: boolean;
lockOnMinimize: boolean;
lockOnCopy: boolean;
canAutoType: boolean;
lockOnAutoType: boolean;
canDetectOsSleep: boolean;
lockOnOsLock: boolean;
}> = ({
idleMinutes,
canDetectMinimize,
lockOnMinimize,
lockOnCopy,
canAutoType,
lockOnAutoType,
canDetectOsSleep,
lockOnOsLock
}) => {
return (
<>
<h2 id="lock">{Locale.setGenLock}</h2>
<div>
<label for="settings__general-idle-minutes">{Locale.setGenLockInactive}:</label>
<select
class="settings__general-idle-minutes settings__select input-base"
id="settings__general-idle-minutes"
value={idleMinutes}
>
<option value="0">{Locale.setGenNoAutoLock}</option>
<option value="5">
<LocalizedWith str={Locale.setGenLockMinutes}>5</LocalizedWith>
</option>
<option value="10">
<LocalizedWith str={Locale.setGenLockMinutes}>10</LocalizedWith>
</option>
<option value="15">
<LocalizedWith str={Locale.setGenLockMinutes}>15</LocalizedWith>
</option>
<option value="30">
<LocalizedWith str={Locale.setGenLockMinutes}>30</LocalizedWith>
</option>
<option value="60">{Locale.setGenLockHour}</option>
<option value="180">
<LocalizedWith str={Locale.setGenLockHours}>3</LocalizedWith>
</option>
<option value="360">
<LocalizedWith str={Locale.setGenLockHours}>6</LocalizedWith>
</option>
<option value="720">
<LocalizedWith str={Locale.setGenLockHours}>12</LocalizedWith>
</option>
<option value="1440">{Locale.setGenLockDay}</option>
</select>
</div>
{canDetectMinimize ? (
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-lock-on-minimize"
id="settings__general-lock-on-minimize"
checked={lockOnMinimize}
/>
<label for="settings__general-lock-on-minimize">
{Locale.setGenLockMinimize}
</label>
</div>
) : null}
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-lock-on-copy"
id="settings__general-lock-on-copy"
checked={lockOnCopy}
/>
<label for="settings__general-lock-on-copy">{Locale.setGenLockCopy}</label>
</div>
{canAutoType ? (
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-lock-on-auto-type"
id="settings__general-lock-on-auto-type"
checked={lockOnAutoType}
/>
<label for="settings__general-lock-on-auto-type">
{Locale.setGenLockAutoType}
</label>
</div>
) : null}
{canDetectOsSleep ? (
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-lock-on-os-lock"
id="settings__general-lock-on-os-lock"
checked={lockOnOsLock}
/>
<label for="settings__general-lock-on-os-lock">
{Locale.setGenLockOrSleep}
</label>
</div>
) : null}
</>
);
};

View File

@ -0,0 +1,96 @@
import { FunctionComponent } from 'preact';
import { StorageConfigField } from 'storage/types';
export const SettingsGeneralStorageProviderView: FunctionComponent<{
name: string;
fields: StorageConfigField[];
fieldChanged: (id: string, value: string | null) => void;
}> = ({ name, fields, fieldChanged }) => {
const inputChanged = (e: Event, fieldId: string) => {
const input = e.target as HTMLInputElement;
if (!input?.checkValidity()) {
return;
}
fieldChanged(fieldId, input.value);
};
return (
<div class={`settings__general-prv settings__general-prv-${name}`}>
<div class="settings__general-prv-fields">
{fields.map((field) =>
field.type === 'select' ? (
<div>
<label for={`settings__general-prv-field-sel-${field.id}`}>
{field.title}:
</label>
<select
class="settings__select input-base settings__general-prv-field settings__general-prv-field-sel"
id={`settings__general-prv-field-sel-${field.id}`}
data-id={field.id}
value={field.value || ''}
onChange={(e) =>
fieldChanged(field.id, (e.target as HTMLSelectElement).value)
}
>
{Object.entries(field.options).map(([fieldValue, fieldTitle]) => (
<option key={fieldValue} value={fieldValue}>
{fieldTitle}
</option>
))}
</select>
</div>
) : field.type === 'checkbox' ? (
<div>
<input
type="checkbox"
class="input-base settings__general-prv-field settings__input settings__general-prv-field-check"
id={`settings__general-prv-field-check-${field.id}`}
checked={!!field.value}
value={field.value || ''}
data-id={field.id}
onClick={(e) =>
fieldChanged(
field.id,
(e.target as HTMLInputElement).checked ? 'true' : null
)
}
/>
<label for={`settings__general-prv-field-check-${field.id}`}>
{field.title}
</label>
{field.desc ? (
<div class="settings__general-prv-field-desc muted-color">
{field.desc}
</div>
) : null}
</div>
) : (
<div>
<label for={`settings__general-prv-field-txt-${field.id}`}>
{field.title}:
</label>
{field.desc ? (
<div class="settings__general-prv-field-desc muted-color">
{field.desc}
</div>
) : null}
<input
type={field.type}
class="input-base settings__general-prv-field settings__input settings__general-prv-field-txt"
id={`settings__general-prv-field-txt-${field.id}`}
autocomplete="off"
value={field.value || ''}
data-id={field.id}
placeholder={field.placeholder}
required={field.required}
pattern={field.pattern}
onInput={(e) => inputChanged(e, field.id)}
/>
</div>
)
)}
</div>
</div>
);
};

View File

@ -1,5 +1,73 @@
import { FunctionComponent } from 'preact';
import { Locale } from 'util/locale';
import { SettingsGeneralStorageProvider } from 'ui/settings/general/settings-general-storage-provider';
export const SettingsGeneralStorageView: FunctionComponent = () => {
return <></>;
interface SettingsGeneralStorageProviderItem {
name: string;
locName: string;
enabled: boolean;
loggedIn: boolean;
hasConfig: boolean;
}
export const SettingsGeneralStorageView: FunctionComponent<{
disableOfflineStorage: boolean;
shortLivedStorageToken: boolean;
storageProviders: SettingsGeneralStorageProviderItem[];
}> = ({ disableOfflineStorage, shortLivedStorageToken, storageProviders }) => {
return (
<>
<h2 id="storage">{Locale.setGenStorage}</h2>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-disable-offline-storage"
id="settings__general-disable-offline-storage"
checked={disableOfflineStorage}
/>
<label for="settings__general-disable-offline-storage">
{Locale.setGenDisableOfflineStorage}
</label>
</div>
<div>
<input
type="checkbox"
class="settings__input input-base settings__general-short-lived-storage-token"
id="settings__general-short-lived-storage-token"
checked={shortLivedStorageToken}
/>
<label for="settings__general-short-lived-storage-token">
{Locale.setGenShortLivedStorageToken}
</label>
</div>
{storageProviders.map((prv) => (
<div key={prv.name}>
<h4 class="settings__general-storage-header">
<input
type="checkbox"
id={`settings__general-prv-check-${prv.name}`}
class="settings__general-prv-check"
data-storage={prv.name}
checked={prv.enabled}
/>
<label for={`settings__general-prv-check-${prv.name}`}>{prv.locName}</label>
</h4>
{prv.enabled && prv.hasConfig ? (
<div class={`settings__general-prv-wrap settings__general-${prv.name}}`}>
<SettingsGeneralStorageProvider name={prv.name} />
</div>
) : null}
{prv.loggedIn ? (
<button
class="btn-silent settings__general-prv-logout"
data-storage={prv.name}
>
{Locale.setGenStorageLogout}
</button>
) : null}
</div>
))}
</>
);
};

View File

@ -1,5 +1,86 @@
import { FunctionComponent } from 'preact';
import { Locale } from 'util/locale';
import { AppSettingsAutoUpdate } from 'models/app-settings';
import { Links } from 'const/links';
export const SettingsGeneralUpdateView: FunctionComponent = () => {
return <></>;
export const SettingsGeneralUpdateView: FunctionComponent<{
updateWaitingReload: boolean;
autoUpdate: AppSettingsAutoUpdate | null;
showUpdateBlock: boolean;
updateInfo: string;
updateInProgress: boolean;
updateReady: boolean;
updateFound: boolean;
}> = ({
updateWaitingReload,
autoUpdate,
showUpdateBlock,
updateInfo,
updateInProgress,
updateReady,
updateFound
}) => {
return (
<>
{updateWaitingReload ? (
<>
<h2 class="action-color">{Locale.setGenUpdate}</h2>
<div>
{Locale.setGenNewVersion}.{' '}
<a href={Links.ReleaseNotes} target="_blank" rel="noreferrer">
{Locale.setGenReleaseNotes}
</a>
</div>
<div class="settings__general-update-buttons">
<button class="settings__general-restart-btn">
{Locale.setGenReloadToUpdate}
</button>
</div>
</>
) : null}
{showUpdateBlock ? (
<>
<h2>{Locale.setGenUpdate}</h2>
<div>
<select class="settings__general-auto-update settings__select input-base">
<option value="install" selected={autoUpdate === 'install'}>
{Locale.setGenUpdateAuto}
</option>
<option value="check" selected={autoUpdate === 'check'}>
{Locale.setGenUpdateCheck}
</option>
<option value="" selected={!autoUpdate}>
{Locale.setGenNoUpdate}
</option>
</select>
<div>{updateInfo}</div>
<a href={Links.ReleaseNotes} target="_blank" rel="noreferrer">
{Locale.setGenReleaseNotes}
</a>
</div>
<div class="settings__general-update-buttons">
{updateInProgress ? (
<button class="settings__general-update-btn btn-silent" disabled>
{Locale.setGenUpdateChecking}
</button>
) : (
<button class="settings__general-update-btn btn-silent">
{Locale.setGenCheckUpdate}
</button>
)}
{updateReady ? (
<button class="settings__general-restart-btn">
{Locale.setGenRestartToUpdate}
</button>
) : null}
{updateFound ? (
<button class="settings__general-update-found-btn">
{Locale.setGenDownloadAndRestart}
</button>
) : null}
</div>
</>
) : null}
</>
);
};

View File

@ -1,345 +0,0 @@
{{#if updateWaitingReload}}
<h2 class="action-color">{{res 'setGenUpdate'}}</h2>
<div>{{res 'setGenNewVersion'}}. <a href="{{releaseNotesLink}}" target="_blank">{{res 'setGenReleaseNotes'}}</a></div>
<div class="settings__general-update-buttons">
<button class="settings__general-restart-btn">{{res 'setGenReloadToUpdate'}}</button>
</div>
{{else if updateManual}}
<h2 class="action-color">{{res 'setGenUpdate'}}</h2>
<div>{{res 'setGenUpdateManual'}}</div>
<div class="settings__general-update-buttons">
<button class="settings__general-download-update-btn">{{res 'setGenDownloadUpdate'}}</button>
</div>
{{/if}}
{{#if showUpdateBlock}}
<h2>{{res 'setGenUpdate'}}</h2>
<div>
<select class="settings__general-auto-update settings__select input-base">
<option value="install" {{#ifeq autoUpdate 'install'}}selected{{/ifeq}}>{{res 'setGenUpdateAuto'}}</option>
<option value="check" {{#ifeq autoUpdate 'check'}}selected{{/ifeq}}>{{res 'setGenUpdateCheck'}}</option>
<option value="" {{#unless autoUpdate}}selected{{/unless}}>{{res 'setGenNoUpdate'}}</option>
</select>
<div>{{updateInfo}}</div>
<a href="{{releaseNotesLink}}" target="_blank">{{res 'setGenReleaseNotes'}}</a>
</div>
<div class="settings__general-update-buttons">
{{#if updateInProgress}}
<button class="settings__general-update-btn btn-silent" disabled>{{res 'setGenUpdateChecking'}}</button>
{{else}}
<button class="settings__general-update-btn btn-silent">{{res 'setGenCheckUpdate'}}</button>
{{/if}}
{{#if updateReady}}<button class="settings__general-restart-btn">{{res 'setGenRestartToUpdate'}}</button>{{/if}}
{{#if updateFound}}<button class="settings__general-update-found-btn">{{res 'setGenDownloadAndRestart'}}</button>{{/if}}
</div>
{{/if}}
<h2 id="appearance">{{res 'setGenAppearance'}}</h2>
{{#if locales}}
<div>
<label for="settings__general-locale">{{res 'setGenLocale'}}:</label>
<select class="settings__general-locale settings__select input-base" id="settings__general-locale">
{{#each locales as |name key|}}
<option value="{{key}}" {{#ifeq key ../activeLocale}}selected{{/ifeq}}>{{name}}</option>
{{/each}}
<option value="...">({{res 'setGenLocOther'}})</option>
</select>
</div>
{{/if}}
<div>
<label>{{res 'setGenTheme'}}:</label>
<div class="settings__general-themes">
{{#each themes as |name key|}}
<div class="th-{{key}} settings__general-theme
{{~#ifeq key ../activeTheme}} settings__general-theme--selected{{/ifeq~}}"
data-theme="{{key}}"
>
<div class="settings__general-theme-name">{{name}}</div>
<button class="settings__general-theme-button"><i class="fa fa-ellipsis-h"></i></button>
</div>
{{/each}}
<div class="settings__general-theme settings__general-theme-plugins" data-theme="...">
<div class="settings__general-theme-plugins-name">{{res 'setGenMoreThemes'}}</div>
<i class="settings__general-theme-plugins-icon fa fa-puzzle-piece"></i>
</div>
</div>
</div>
<div>
<input type="checkbox" class="settings__input input-base settings__general-auto-switch-theme" id="settings__general-auto-switch-theme" {{#if autoSwitchTheme}}checked{{/if}} />
<label for="settings__general-auto-switch-theme">{{res 'setGenAutoSwitchTheme'}}</label>
</div>
<div>
<label for="settings__general-font-size">{{res 'setGenFontSize'}}:</label>
<select class="settings__general-font-size settings__select input-base" id="settings__general-font-size">
<option value="0" {{#ifeq fontSize 0}}selected{{/ifeq}}>{{res 'setGenFontSizeNormal'}}</option>
<option value="1" {{#ifeq fontSize 1}}selected{{/ifeq}}>{{res 'setGenFontSizeLarge'}}</option>
<option value="2" {{#ifeq fontSize 2}}selected{{/ifeq}}>{{res 'setGenFontSizeLargest'}}</option>
</select>
</div>
{{#if supportsTitleBarStyles}}
<div>
<label for="settings__general-titlebar-style">{{res 'setGenTitlebarStyle'}}:</label>
<select class="settings__general-titlebar-style settings__select input-base" id="settings__general-titlebar-style">
<option value="default" {{#ifeq titlebarStyle 'default'}}selected{{/ifeq}}>{{res 'setGenTitlebarStyleDefault'}}</option>
<option value="hidden" {{#ifeq titlebarStyle 'hidden'}}selected{{/ifeq}}>{{res 'setGenTitlebarStyleHidden'}}</option>
{{#if supportsCustomTitleBarAndDraggableWindow}}
<option value="hidden-inset" {{#ifeq titlebarStyle 'hidden-inset'}}selected{{/ifeq}}>{{res 'setGenTitlebarStyleHiddenInset'}}</option>
{{/if}}
</select>
</div>
{{/if}}
<div>
<input type="checkbox" class="settings__input input-base settings__general-expand" id="settings__general-expand" {{#if expandGroups}}checked{{/if}} />
<label for="settings__general-expand">{{res 'setGenShowSubgroups'}}</label>
</div>
{{#if canSetTableView}}
<div>
<input type="checkbox" class="settings__input input-base settings__general-table-view" id="settings__general-table-view" {{#if tableView}}checked{{/if}} />
<label for="settings__general-table-view">{{res 'setGenTableView'}}</label>
</div>
{{/if}}
<div>
<input type="checkbox" class="settings__input input-base settings__general-colorful-icons" id="settings__general-colorful-icons" {{#if colorfulIcons}}checked{{/if}} />
<label for="settings__general-colorful-icons">{{res 'setGenColorfulIcons'}}</label>
</div>
<h2 id="function">{{res 'setGenFunction'}}</h2>
{{#if canAutoSaveOnClose}}
<div>
<input type="checkbox" class="settings__input input-base settings__general-auto-save" id="settings__general-auto-save"
{{#if autoSave}}checked{{/if}} />
<label for="settings__general-auto-save">{{res 'setGenAutoSyncOnClose'}}</label>
</div>
{{/if}}
<div>
<label for="settings__general-auto-save-interval">{{res 'setGenAutoSyncTimer'}}:</label>
<select class="settings__select input-base settings__general-auto-save-interval"
id="settings__general-auto-save-interval">
<option value="0" {{#ifeq autoSaveInterval 0}}selected{{/ifeq}}>{{res 'setGenAutoSyncTimerOff'}}</option>
<option value="-1" {{#ifeq autoSaveInterval -1}}selected{{/ifeq}}>{{res 'setGenAutoSyncTimerOnChange'}}</option>
<option value="1" {{#ifeq autoSaveInterval 1}}selected{{/ifeq}}>{{#res 'setGenAutoSyncTimerInterval'}}
1{{/res}}</option>
<option value="5" {{#ifeq autoSaveInterval 5}}selected{{/ifeq}}>{{#res 'setGenAutoSyncTimerInterval'}}
5{{/res}}</option>
<option value="15" {{#ifeq autoSaveInterval 15}}selected{{/ifeq}}>{{#res 'setGenAutoSyncTimerInterval'}}
15{{/res}}</option>
<option value="30" {{#ifeq autoSaveInterval 30}}selected{{/ifeq}}>{{#res 'setGenAutoSyncTimerInterval'}}
30{{/res}}</option>
<option value="60" {{#ifeq autoSaveInterval 60}}selected{{/ifeq}}>{{#res 'setGenAutoSyncTimerInterval'}}
60{{/res}}</option>
</select>
</div>
<div>
<label for="settings__general-remember-key-files">{{res 'setGenRememberKeyFiles'}}:</label>
<select class="settings__general-remember-key-files settings__select input-base" id="settings__general-remember-key-files">
<option value="" {{#unless rememberKeyFiles}}selected{{/unless}}>{{res 'setGenNoRememberKeyFiles'}}</option>
<option value="data" {{#ifeq rememberKeyFiles 'data'}}selected{{/ifeq}}>{{res 'setGenRememberKeyFilesData'}}</option>
{{#if supportFiles}}<option value="path" {{#ifeq rememberKeyFiles 'path'}}selected{{/ifeq}}>{{res 'setGenRememberKeyFilesPath'}}</option>{{/if}}
</select>
</div>
{{#if canClearClipboard}}
<div>
<label for="settings__general-clipboard">{{res 'setGenClearClip'}}:</label>
<select class="settings__general-clipboard settings__select input-base" id="settings__general-clipboard">
<option value="0" {{#unless clipboardSeconds}}selected{{/unless}}>{{res 'setGenNoClear'}}</option>
<option value="5" {{#ifeq clipboardSeconds 5}}selected{{/ifeq}}>{{#res 'setGenClearSeconds'}}5{{/res}}</option>
<option value="10" {{#ifeq clipboardSeconds 10}}selected{{/ifeq}}>{{#res 'setGenClearSeconds'}}10{{/res}}</option>
<option value="15" {{#ifeq clipboardSeconds 15}}selected{{/ifeq}}>{{#res 'setGenClearSeconds'}}15{{/res}}</option>
<option value="60" {{#ifeq clipboardSeconds 60}}selected{{/ifeq}}>{{res 'setGenClearMinute'}}</option>
</select>
</div>
{{/if}}
{{#if canMinimize}}
<div>
<input type="checkbox" class="settings__input input-base settings__general-minimize" id="settings__general-minimize"
{{#if minimizeOnClose}}checked{{/if}} />
<label for="settings__general-minimize">{{res 'setGenMinInstead'}}</label>
</div>
<div>
<input type="checkbox" class="settings__input input-base settings__general-minimize-on-field-copy" id="settings__general-minimize-on-field-copy"
{{#if minimizeOnFieldCopy}}checked{{/if}} />
<label for="settings__general-minimize-on-field-copy">{{res 'setGenMinOnFieldCopy'}}</label>
</div>
{{/if}}
{{#if canAutoType}}
<div>
<input type="checkbox" class="settings__input input-base settings__general-direct-autotype"
id="settings__general-direct-autotype" {{#if directAutotype}}checked{{/if}} />
<label for="settings__general-direct-autotype">{{res 'setGenDirectAutotype'}}</label>
</div>
<div>
<input type="checkbox" class="settings__input input-base settings__general-autotype-title-filter"
id="settings__general-autotype-title-filter" {{#if autoTypeTitleFilterEnabled}}checked{{/if}} />
<label for="settings__general-autotype-title-filter">{{res 'setGenAutoTypeTitleFilterEnabled'}}</label>
</div>
<div>
<input type="checkbox" class="settings__input input-base settings__general-field-label-dblclick-autotype"
id="settings__general-field-label-dblclick-autotype" {{#if fieldLabelDblClickAutoType}}checked{{/if}} />
<label for="settings__general-field-label-dblclick-autotype">{{res 'setGenFieldLabelDblClickAutoType'}}</label>
</div>
{{/if}}
<div>
<input type="checkbox" class="settings__input input-base settings__general-use-markdown" id="settings__general-use-markdown" {{#if useMarkdown}}checked{{/if}} />
<label for="settings__general-use-markdown">{{res 'setGenUseMarkdown'}}</label>
</div>
<div>
<input type="checkbox" class="settings__input input-base settings__general-use-group-icon-for-entries"
id="settings__general-use-group-icon-for-entries" {{#if useGroupIconForEntries}}checked{{/if}} />
<label for="settings__general-use-group-icon-for-entries">{{res 'setGenUseGroupIconForEntries'}}</label>
</div>
{{#if hasDeviceOwnerAuth}}
<div>
<label for="settings__general-device-owner-auth">{{res 'setGenTouchId'}}:</label>
<select class="settings__general-device-owner-auth settings__select input-base" id="settings__general-device-owner-auth">
<option value="" {{#unless deviceOwnerAuth}}selected{{/unless}}>{{res 'setGenTouchIdDisabled'}}</option>
<option value="memory" {{#ifeq deviceOwnerAuth 'memory'}}selected{{/ifeq}}>{{res 'setGenTouchIdMemory'}}</option>
<option value="file" {{#ifeq deviceOwnerAuth 'file'}}selected{{/ifeq}}>{{res 'setGenTouchIdFile'}}</option>
</select>
</div>
{{#if deviceOwnerAuth}}
<label for="settings__general-device-owner-auth-timeout">{{res 'setGenTouchIdPass'}}:</label>
<select class="settings__general-device-owner-auth-timeout settings__select input-base" id="settings__general-device-owner-auth-timeout">
<option value="1" {{#ifeq deviceOwnerAuthTimeout 1}}selected{{/ifeq}}>{{Res 'oneMinute'}}</option>
<option value="5" {{#ifeq deviceOwnerAuthTimeout 5}}selected{{/ifeq}}>{{#Res 'minutes'}}5{{/Res}}</option>
<option value="30" {{#ifeq deviceOwnerAuthTimeout 30}}selected{{/ifeq}}>{{#Res 'minutes'}}30{{/Res}}</option>
<option value="60" {{#ifeq deviceOwnerAuthTimeout 60}}selected{{/ifeq}}>{{Res 'oneHour'}}</option>
<option value="120" {{#ifeq deviceOwnerAuthTimeout 120}}selected{{/ifeq}}>{{#Res 'hours'}}2{{/Res}}</option>
<option value="480" {{#ifeq deviceOwnerAuthTimeout 480}}selected{{/ifeq}}>{{#Res 'hours'}}8{{/Res}}</option>
<option value="1440" {{#ifeq deviceOwnerAuthTimeout 1440}}selected{{/ifeq}}>{{Res 'oneDay'}}</option>
<option value="10080" {{#ifeq deviceOwnerAuthTimeout 10080}}selected{{/ifeq}}>{{Res 'oneWeek'}}</option>
{{#ifeq deviceOwnerAuth 'file'}}
<option value="43200" {{#ifeq deviceOwnerAuthTimeout 43200}}selected{{/ifeq}}>{{Res 'oneMonth'}}</option>
<option value="525600" {{#ifeq deviceOwnerAuthTimeout 525600}}selected{{/ifeq}}>{{Res 'oneYear'}}</option>
{{/ifeq}}
</select>
{{/if}}
{{/if}}
<h2 id="audit">{{res 'setGenAudit'}}</h2>
<div>
<input type="checkbox" class="settings__input input-base settings__general-audit-passwords"
id="settings__general-audit-passwords" {{#if auditPasswords}}checked{{/if}} />
<label for="settings__general-audit-passwords">{{res 'setGenAuditPasswords'}}</label>
</div>
<div>
<input type="checkbox" class="settings__input input-base settings__general-audit-password-entropy"
id="settings__general-audit-password-entropy" {{#if auditPasswordEntropy}}checked{{/if}} />
<label for="settings__general-audit-password-entropy">{{res 'setGenAuditPasswordEntropy'}}</label>
</div>
<div>
<input type="checkbox" class="settings__input input-base settings__general-exclude-pins-from-audit"
id="settings__general-exclude-pins-from-audit" {{#if excludePinsFromAudit}}checked{{/if}} />
<label for="settings__general-exclude-pins-from-audit">{{res 'setGenExcludePinsFromAudit'}}</label>
</div>
<div>
<input type="checkbox" class="settings__input input-base settings__general-check-passwords-on-hibp"
id="settings__general-check-passwords-on-hibp" {{#if checkPasswordsOnHIBP}}checked{{/if}} />
<label for="settings__general-check-passwords-on-hibp">
{{~#res 'setGenCheckPasswordsOnHIBP'~}}
<a href="{{hibpLink}}" rel="noreferrer noopener" target="_blank">Have I Been Pwned</a>
{{~/res~}}
</label>
<i class="fa fa-info-circle info-btn settings__general-toggle-help-hibp"></i>
<div class="settings__general-help-hibp hide">
{{~#res 'setGenHelpHIBP'~}}
<a href="{{hibpPrivacyLink}}" rel="noreferrer noopener" target="_blank">{{res 'setGenHelpHIBPLink'}}</a>
{{~/res~}}
</div>
</div>
<div>
<label for="settings__general-audit-password-age">{{res 'setGenAuditPasswordAge'}}:</label>
<select class="settings__select input-base settings__general-audit-password-age"
id="settings__general-audit-password-age">
<option value="0" {{#ifeq auditPasswordAge 0}}selected{{/ifeq}}>{{res 'setGenAuditPasswordAgeOff'}}</option>
<option value="1" {{#ifeq auditPasswordAge 1}}selected{{/ifeq}}>{{res 'setGenAuditPasswordAgeOneYear'}}</option>
<option value="2" {{#ifeq auditPasswordAge 2}}selected{{/ifeq}}>{{#res 'setGenAuditPasswordAgeYears'}}
2{{/res}}</option>
<option value="3" {{#ifeq auditPasswordAge 3}}selected{{/ifeq}}>{{#res 'setGenAuditPasswordAgeYears'}}
3{{/res}}</option>
<option value="5" {{#ifeq auditPasswordAge 5}}selected{{/ifeq}}>{{#res 'setGenAuditPasswordAgeYears'}}
5{{/res}}</option>
<option value="10" {{#ifeq auditPasswordAge 10}}selected{{/ifeq}}>{{#res 'setGenAuditPasswordAgeYears'}}
10{{/res}}</option>
</select>
</div>
<h2 id="lock">{{res 'setGenLock'}}</h2>
<div>
<label for="settings__general-idle-minutes">{{res 'setGenLockInactive'}}:</label>
<select class="settings__general-idle-minutes settings__select input-base" id="settings__general-idle-minutes">
<option value="0" {{#cmp idleMinutes 0 '<='}}selected{{/cmp}}>{{res 'setGenNoAutoLock'}}</option>
<option value="5" {{#ifeq idleMinutes 5}}selected{{/ifeq}}>{{#res 'setGenLockMinutes'}}5{{/res}}</option>
<option value="10" {{#ifeq idleMinutes 10}}selected{{/ifeq}}>{{#res 'setGenLockMinutes'}}10{{/res}}</option>
<option value="15" {{#ifeq idleMinutes 15}}selected{{/ifeq}}>{{#res 'setGenLockMinutes'}}15{{/res}}</option>
<option value="30" {{#ifeq idleMinutes 30}}selected{{/ifeq}}>{{#res 'setGenLockMinutes'}}30{{/res}}</option>
<option value="60" {{#ifeq idleMinutes 60}}selected{{/ifeq}}>{{res 'setGenLockHour'}}</option>
<option value="180" {{#ifeq idleMinutes 180}}selected{{/ifeq}}>{{#res 'setGenLockHours'}}3{{/res}}</option>
<option value="360" {{#ifeq idleMinutes 360}}selected{{/ifeq}}>{{#res 'setGenLockHours'}}6{{/res}}</option>
<option value="720" {{#ifeq idleMinutes 720}}selected{{/ifeq}}>{{#res 'setGenLockHours'}}12{{/res}}</option>
<option value="1440" {{#ifeq idleMinutes 1440}}selected{{/ifeq}}>{{res 'setGenLockDay'}}</option>
</select>
</div>
{{#if canDetectMinimize}}
<div>
<input type="checkbox" class="settings__input input-base settings__general-lock-on-minimize" id="settings__general-lock-on-minimize"
{{#if lockOnMinimize}}checked{{/if}} />
<label for="settings__general-lock-on-minimize">{{res 'setGenLockMinimize'}}</label>
</div>
{{/if}}
<div>
<input type="checkbox" class="settings__input input-base settings__general-lock-on-copy" id="settings__general-lock-on-copy"
{{#if lockOnCopy}}checked{{/if}} />
<label for="settings__general-lock-on-copy">{{res 'setGenLockCopy'}}</label>
</div>
{{#if canAutoType}}
<div>
<input type="checkbox" class="settings__input input-base settings__general-lock-on-auto-type" id="settings__general-lock-on-auto-type"
{{#if lockOnAutoType}}checked{{/if}} />
<label for="settings__general-lock-on-auto-type">{{res 'setGenLockAutoType'}}</label>
</div>
{{/if}}
{{#if canDetectOsSleep}}
<div>
<input type="checkbox" class="settings__input input-base settings__general-lock-on-os-lock" id="settings__general-lock-on-os-lock"
{{#if lockOnOsLock}}checked{{/if}} />
<label for="settings__general-lock-on-os-lock">{{res 'setGenLockOrSleep'}}</label>
</div>
{{/if}}
<h2 id="storage">{{res 'setGenStorage'}}</h2>
<div>
<input type="checkbox" class="settings__input input-base settings__general-disable-offline-storage" id="settings__general-disable-offline-storage"
{{#if disableOfflineStorage}}checked{{/if}} />
<label for="settings__general-disable-offline-storage">{{res 'setGenDisableOfflineStorage'}}</label>
</div>
<div>
<input type="checkbox" class="settings__input input-base settings__general-short-lived-storage-token" id="settings__general-short-lived-storage-token"
{{#if shortLivedStorageToken}}checked{{/if}} />
<label for="settings__general-short-lived-storage-token">{{res 'setGenShortLivedStorageToken'}}</label>
</div>
{{#each storageProviders as |prv|}}
<h4 class="settings__general-storage-header"><input
type="checkbox" id="settings__general-prv-check-{{prv.name}}" class="settings__general-prv-check"
data-storage="{{prv.name}}" {{#if prv.enabled}}checked{{/if}}
/><label for="settings__general-prv-check-{{prv.name}}">{{res prv.name}}</label></h4>
<div class="settings__general-prv-wrap settings__general-{{prv.name}} {{#ifeq prv.enabled false}}hide{{/ifeq}}"></div>
{{#if prv.loggedIn}}<button class="btn-silent settings__general-prv-logout"
data-storage="{{prv.name}}">{{res 'setGenStorageLogout'}}</button>{{/if}}
{{/each}}
<h2 id="advanced">{{res 'advanced'}}</h2>
<a class="settings__general-show-advanced">{{res 'setGenShowAdvanced'}}</a>
<div class="settings__general-advanced hide">
{{#if devTools}}
<button class="btn-silent settings__general-dev-tools-link">{{res 'setGenDevTools'}}</button>
<button class="btn-silent settings__general-try-beta-link">{{res 'setGenTryBeta'}}</button>
{{/if}}
{{#if showReloadApp}}
<button class="btn-silent settings__general-reload-app-link">{{res 'setGenReloadApp'}}</button>
{{/if}}
<button class="btn-silent settings__general-show-logs-link">{{res 'setGenShowAppLogs'}}</button>
</div>

View File

@ -1,43 +0,0 @@
<div class="settings__general-prv settings__general-prv-{{name}}">
{{#if desc}}<div class="settings__general-prv-desc">{{res desc}}</div>{{/if}}
<div class="settings__general-prv-fields">
{{#each fields as |field ix|}}
{{#ifeq type 'select'}}
<div>
<label for="settings__general-prv-field-sel-{{id}}">{{res title}}:</label>
<select
class="settings__select input-base settings__general-prv-field settings__general-prv-field-sel"
id="settings__general-prv-field-sel-{{id}}"
data-id="{{id}}">
{{#each options as |title val|}}
<option value="{{val}}" {{#ifeq ../value val}}selected{{/ifeq}}>{{res title}}</option>
{{/each}}
</select>
</div>
{{else ifeq type 'checkbox'}}
<input type="checkbox"
class="input-base settings__general-prv-field settings__input settings__general-prv-field-check"
id="settings__general-prv-field-check-{{id}}"
{{#if value}}checked{{/if}}
value="{{value}}"
data-id="{{id}}"
/>
<label for="settings__general-prv-field-check-{{id}}">{{res title}}</label>
{{#if desc}}<div class="settings__general-prv-field-desc muted-color">{{res desc}}</div>{{/if}}
{{else}}
<label for="settings__general-prv-field-txt-{{id}}">{{res title}}:</label>
{{#if desc}}<div class="settings__general-prv-field-desc muted-color">{{res desc}}</div>{{/if}}
<input type="{{type}}"
class="input-base settings__general-prv-field settings__input settings__general-prv-field-txt"
id="settings__general-prv-field-txt-{{id}}"
autocomplete="off"
value="{{value}}"
data-id="{{id}}"
{{#if placeholder}}placeholder="{{res placeholder}}"{{/if}}
{{#if required}}required{{/if}}
{{#if pattern}}pattern="{{pattern}}"{{/if}}
/>
{{/ifeq}}
{{/each}}
</div>
</div>

View File

@ -4,12 +4,12 @@ import { expect } from 'chai';
describe('Locale', () => {
it('returns simple locale strings', () => {
expect(Locale.name).to.eql('name');
expect(Locale.get('name')).to.eql('name');
// expect(Locale.get('name')).to.eql('name');
});
it('returns replaced locale strings', () => {
expect(Locale.minutes.with('3')).to.eql('3 minutes');
expect(Locale.get('minutes')).to.eql('{} minutes');
// expect(Locale.get('minutes')).to.eql('{} minutes');
});
it('sets a custom locale', () => {