mirror of https://github.com/keeweb/keeweb.git
settings
This commit is contained in:
parent
aa11e1a65e
commit
d5a61acd01
|
@ -10,25 +10,6 @@ import { WindowClass } from 'comp/browser/window-class';
|
||||||
|
|
||||||
const logger = new Logger('settings-manager');
|
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 = {
|
const SettingsManager = {
|
||||||
activeTheme: null as string | null,
|
activeTheme: null as string | null,
|
||||||
|
|
||||||
|
@ -38,57 +19,68 @@ const SettingsManager = {
|
||||||
'fr-FR': 'Français'
|
'fr-FR': 'Français'
|
||||||
} as Record<string, string>,
|
} as Record<string, string>,
|
||||||
|
|
||||||
allThemes: {
|
get builtInThemes(): Record<string, string> {
|
||||||
dark: 'setGenThemeDark',
|
return {
|
||||||
light: 'setGenThemeLight',
|
dark: Locale.setGenThemeDark,
|
||||||
sd: 'setGenThemeSd',
|
light: Locale.setGenThemeLight,
|
||||||
sl: 'setGenThemeSl',
|
sd: Locale.setGenThemeSd,
|
||||||
fb: 'setGenThemeFb',
|
sl: Locale.setGenThemeSl,
|
||||||
bl: 'setGenThemeBl',
|
fb: Locale.setGenThemeFb,
|
||||||
db: 'setGenThemeDb',
|
bl: Locale.setGenThemeBl,
|
||||||
lb: 'setGenThemeLb',
|
db: Locale.setGenThemeDb,
|
||||||
te: 'setGenThemeTe',
|
lb: Locale.setGenThemeLb,
|
||||||
lt: 'setGenThemeLt',
|
te: Locale.setGenThemeTe,
|
||||||
dc: 'setGenThemeDc',
|
lt: Locale.setGenThemeLt,
|
||||||
hc: 'setGenThemeHc'
|
dc: Locale.setGenThemeDc,
|
||||||
} as Record<string, string>,
|
hc: Locale.setGenThemeHc
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
get allThemes(): Record<string, string> {
|
||||||
|
return {
|
||||||
|
...this.builtInThemes,
|
||||||
|
...this.customThemes
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
// changing something here? don't forget about desktop/app.js
|
// changing something here? don't forget about desktop/app.js
|
||||||
autoSwitchedThemes: [
|
get autoSwitchedThemes(): { name: string; dark: string; light: string }[] {
|
||||||
{
|
return [
|
||||||
name: 'setGenThemeDefault',
|
{
|
||||||
dark: 'dark',
|
name: Locale.setGenThemeDefault,
|
||||||
light: 'light'
|
dark: 'dark',
|
||||||
},
|
light: 'light'
|
||||||
{
|
},
|
||||||
name: 'setGenThemeSol',
|
{
|
||||||
dark: 'sd',
|
name: Locale.setGenThemeSol,
|
||||||
light: 'sl'
|
dark: 'sd',
|
||||||
},
|
light: 'sl'
|
||||||
{
|
},
|
||||||
name: 'setGenThemeBlue',
|
{
|
||||||
dark: 'fb',
|
name: Locale.setGenThemeBlue,
|
||||||
light: 'bl'
|
dark: 'fb',
|
||||||
},
|
light: 'bl'
|
||||||
{
|
},
|
||||||
name: 'setGenThemeBrown',
|
{
|
||||||
dark: 'db',
|
name: Locale.setGenThemeBrown,
|
||||||
light: 'lb'
|
dark: 'db',
|
||||||
},
|
light: 'lb'
|
||||||
{
|
},
|
||||||
name: 'setGenThemeTerminal',
|
{
|
||||||
dark: 'te',
|
name: Locale.setGenThemeTerminal,
|
||||||
light: 'lt'
|
dark: 'te',
|
||||||
},
|
light: 'lt'
|
||||||
{
|
},
|
||||||
name: 'setGenThemeHighContrast',
|
{
|
||||||
dark: 'dc',
|
name: Locale.setGenThemeHighContrast,
|
||||||
light: 'hc'
|
dark: 'dc',
|
||||||
}
|
light: 'hc'
|
||||||
],
|
}
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
customLocales: new Map<string, Record<string, string>>(),
|
customLocales: new Map<string, Record<string, string>>(),
|
||||||
customThemeNames: new Map<string, string>(),
|
customThemes: {} as Record<string, string>,
|
||||||
|
|
||||||
init(): void {
|
init(): void {
|
||||||
Events.on('dark-mode-changed', () => this.darkModeChanged());
|
Events.on('dark-mode-changed', () => this.darkModeChanged());
|
||||||
|
@ -183,10 +175,7 @@ const SettingsManager = {
|
||||||
Events.emit('locale-changed', loc);
|
Events.emit('locale-changed', loc);
|
||||||
|
|
||||||
if (Launcher) {
|
if (Launcher) {
|
||||||
const localeValuesForDesktopApp: Record<string, string> = {};
|
const localeValuesForDesktopApp = this.getDesktopAppLocaleValues();
|
||||||
for (const key of DesktopLocaleKeys) {
|
|
||||||
localeValuesForDesktopApp[key] = Locale.get(key);
|
|
||||||
}
|
|
||||||
Launcher.ipcRenderer.invoke('set-locale', loc, localeValuesForDesktopApp).catch(noop);
|
Launcher.ipcRenderer.invoke('set-locale', loc, localeValuesForDesktopApp).catch(noop);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -197,6 +186,27 @@ const SettingsManager = {
|
||||||
return 'en-US';
|
return 'en-US';
|
||||||
}
|
}
|
||||||
return language;
|
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
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ export type AppSettingsAutoUpdate = 'install' | 'check';
|
||||||
export type AppSettingsRememberKeyFiles = 'path' | 'data';
|
export type AppSettingsRememberKeyFiles = 'path' | 'data';
|
||||||
export type AppSettingsTitlebarStyle = 'default' | 'hidden' | 'hidden-inset';
|
export type AppSettingsTitlebarStyle = 'default' | 'hidden' | 'hidden-inset';
|
||||||
export type AppSettingsDeviceOwnerAuth = 'memory' | 'file';
|
export type AppSettingsDeviceOwnerAuth = 'memory' | 'file';
|
||||||
|
export type AppSettingsFontSize = 0 | 1 | 2;
|
||||||
|
|
||||||
class AppSettings extends Model {
|
class AppSettings extends Model {
|
||||||
theme: string | null = null; // UI theme
|
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
|
clipboardSeconds = 0; // number of seconds after which the clipboard will be cleared
|
||||||
autoSave = true; // auto-save open files
|
autoSave = true; // auto-save open files
|
||||||
autoSaveInterval = 0; // interval between performing automatic sync, minutes, -1: on every change
|
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
|
idleMinutes = 15; // app lock timeout after inactivity, minutes
|
||||||
minimizeOnClose = false; // minimise the app instead of closing
|
minimizeOnClose = false; // minimise the app instead of closing
|
||||||
minimizeOnFieldCopy = false; // minimise the app on copy
|
minimizeOnFieldCopy = false; // minimise the app on copy
|
||||||
|
@ -51,7 +52,7 @@ class AppSettings extends Model {
|
||||||
hideEmptyFields = false; // hide empty fields in entries
|
hideEmptyFields = false; // hide empty fields in entries
|
||||||
skipHttpsWarning = false; // disable the non-HTTPS warning
|
skipHttpsWarning = false; // disable the non-HTTPS warning
|
||||||
demoOpened = false; // hide the demo button inside the More... menu
|
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
|
tableViewColumns: string[] | null = null; // columns displayed in the table view
|
||||||
generatorPresets: PasswordGeneratorAppSetting | null = null; // presets used in the password generator
|
generatorPresets: PasswordGeneratorAppSetting | null = null; // presets used in the password generator
|
||||||
generatorHidePassword = false; // hide password in the generator
|
generatorHidePassword = false; // hide password in the generator
|
||||||
|
@ -478,8 +479,13 @@ class AppSettings extends Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
private setRememberKeyFiles(value: unknown) {
|
private setRememberKeyFiles(value: unknown) {
|
||||||
if (value === 'path' || value === 'data') {
|
if (value) {
|
||||||
this.rememberKeyFiles = value;
|
if (value === 'path' || value === 'data') {
|
||||||
|
this.rememberKeyFiles = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.rememberKeyFiles = null;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -41,7 +41,7 @@ export type MenuItemFilter =
|
||||||
class MenuItem extends Model {
|
class MenuItem extends Model {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
locTitle?: LocaleKey;
|
locTitle?: () => string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
customIcon?: string;
|
customIcon?: string;
|
||||||
active = false;
|
active = false;
|
||||||
|
|
|
@ -44,7 +44,7 @@ class Menu extends Model {
|
||||||
this.sections = [];
|
this.sections = [];
|
||||||
|
|
||||||
this.allItemsItem = new MenuItem({
|
this.allItemsItem = new MenuItem({
|
||||||
locTitle: 'menuAllItems',
|
locTitle: () => Locale.menuAllItems,
|
||||||
icon: 'th-large',
|
icon: 'th-large',
|
||||||
active: true,
|
active: true,
|
||||||
shortcut: Keys.DOM_VK_A,
|
shortcut: Keys.DOM_VK_A,
|
||||||
|
@ -58,7 +58,7 @@ class Menu extends Model {
|
||||||
this.groupsSection.grow = true;
|
this.groupsSection.grow = true;
|
||||||
|
|
||||||
this.colorsItem = new MenuItem({
|
this.colorsItem = new MenuItem({
|
||||||
locTitle: 'menuColors',
|
locTitle: () => Locale.menuColors,
|
||||||
icon: 'bookmark',
|
icon: 'bookmark',
|
||||||
shortcut: Keys.DOM_VK_C,
|
shortcut: Keys.DOM_VK_C,
|
||||||
cls: 'menu__item-colors',
|
cls: 'menu__item-colors',
|
||||||
|
@ -75,7 +75,7 @@ class Menu extends Model {
|
||||||
|
|
||||||
this.trashSection = new MenuSection(
|
this.trashSection = new MenuSection(
|
||||||
new MenuItem({
|
new MenuItem({
|
||||||
locTitle: 'menuTrash',
|
locTitle: () => Locale.menuTrash,
|
||||||
icon: 'trash-alt',
|
icon: 'trash-alt',
|
||||||
shortcut: Keys.DOM_VK_D,
|
shortcut: Keys.DOM_VK_D,
|
||||||
filter: { type: 'trash' },
|
filter: { type: 'trash' },
|
||||||
|
@ -103,44 +103,44 @@ class Menu extends Model {
|
||||||
|
|
||||||
this.generalSection = new MenuSection(
|
this.generalSection = new MenuSection(
|
||||||
new MenuItem({
|
new MenuItem({
|
||||||
locTitle: 'menuSetGeneral',
|
locTitle: () => Locale.menuSetGeneral,
|
||||||
icon: 'cog',
|
icon: 'cog',
|
||||||
page: 'general',
|
page: 'general',
|
||||||
anchor: undefined,
|
anchor: undefined,
|
||||||
active: true
|
active: true
|
||||||
}),
|
}),
|
||||||
new MenuItem({
|
new MenuItem({
|
||||||
locTitle: 'setGenAppearance',
|
locTitle: () => Locale.setGenAppearance,
|
||||||
icon: '0',
|
icon: '0',
|
||||||
page: 'general',
|
page: 'general',
|
||||||
anchor: 'appearance'
|
anchor: 'appearance'
|
||||||
}),
|
}),
|
||||||
new MenuItem({
|
new MenuItem({
|
||||||
locTitle: 'setGenFunction',
|
locTitle: () => Locale.setGenFunction,
|
||||||
icon: '0',
|
icon: '0',
|
||||||
page: 'general',
|
page: 'general',
|
||||||
anchor: 'function'
|
anchor: 'function'
|
||||||
}),
|
}),
|
||||||
new MenuItem({
|
new MenuItem({
|
||||||
locTitle: 'setGenAudit',
|
locTitle: () => Locale.setGenAudit,
|
||||||
icon: '0',
|
icon: '0',
|
||||||
page: 'general',
|
page: 'general',
|
||||||
anchor: 'audit'
|
anchor: 'audit'
|
||||||
}),
|
}),
|
||||||
new MenuItem({
|
new MenuItem({
|
||||||
locTitle: 'setGenLock',
|
locTitle: () => Locale.setGenLock,
|
||||||
icon: '0',
|
icon: '0',
|
||||||
page: 'general',
|
page: 'general',
|
||||||
anchor: 'lock'
|
anchor: 'lock'
|
||||||
}),
|
}),
|
||||||
new MenuItem({
|
new MenuItem({
|
||||||
locTitle: 'setGenStorage',
|
locTitle: () => Locale.setGenStorage,
|
||||||
icon: '0',
|
icon: '0',
|
||||||
page: 'general',
|
page: 'general',
|
||||||
anchor: 'storage'
|
anchor: 'storage'
|
||||||
}),
|
}),
|
||||||
new MenuItem({
|
new MenuItem({
|
||||||
locTitle: 'advanced',
|
locTitle: () => StringFormat.capFirst(Locale.advanced),
|
||||||
icon: '0',
|
icon: '0',
|
||||||
page: 'general',
|
page: 'general',
|
||||||
anchor: 'advanced'
|
anchor: 'advanced'
|
||||||
|
@ -148,30 +148,46 @@ class Menu extends Model {
|
||||||
);
|
);
|
||||||
|
|
||||||
this.shortcutsSection = new MenuSection(
|
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) {
|
if (Features.supportsBrowserExtensions) {
|
||||||
this.browserSection = new MenuSection(
|
this.browserSection = new MenuSection(
|
||||||
new MenuItem({
|
new MenuItem({
|
||||||
locTitle: 'menuSetBrowser',
|
locTitle: () => Locale.menuSetBrowser,
|
||||||
icon: Features.browserIcon,
|
icon: Features.browserIcon,
|
||||||
page: 'browser'
|
page: 'browser'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.pluginsSection = new MenuSection(
|
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) {
|
if (Launcher) {
|
||||||
this.devicesSection = new MenuSection(
|
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(
|
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(
|
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 = new MenuSection();
|
||||||
this.filesSection.scrollable = true;
|
this.filesSection.scrollable = true;
|
||||||
|
@ -299,7 +315,7 @@ class Menu extends Model {
|
||||||
for (const section of menu) {
|
for (const section of menu) {
|
||||||
for (const item of section.items) {
|
for (const item of section.items) {
|
||||||
if (item.locTitle) {
|
if (item.locTitle) {
|
||||||
item.title = StringFormat.capFirst(Locale.get(item.locTitle));
|
item.title = item.locTitle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -342,9 +342,7 @@ class Plugin extends Model {
|
||||||
el.addEventListener('load', () => {
|
el.addEventListener('load', () => {
|
||||||
URL.revokeObjectURL(objectUrl);
|
URL.revokeObjectURL(objectUrl);
|
||||||
if (theme) {
|
if (theme) {
|
||||||
const locKey = this.getThemeLocaleKey(theme.name);
|
SettingsManager.customThemes[theme.name] = theme.title;
|
||||||
SettingsManager.allThemes[theme.name] = locKey;
|
|
||||||
SettingsManager.customThemeNames.set(locKey, theme.title);
|
|
||||||
for (const styleSheet of Array.from(document.styleSheets)) {
|
for (const styleSheet of Array.from(document.styleSheets)) {
|
||||||
const node = styleSheet.ownerNode as HTMLElement;
|
const node = styleSheet.ownerNode as HTMLElement;
|
||||||
if (node?.id === id) {
|
if (node?.id === id) {
|
||||||
|
@ -470,16 +468,12 @@ class Plugin extends Model {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getThemeLocaleKey(name: string): string {
|
|
||||||
return `setGenThemeCustom_${name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
removeTheme(theme: PluginManifestTheme): void {
|
removeTheme(theme: PluginManifestTheme): void {
|
||||||
delete SettingsManager.allThemes[theme.name];
|
delete SettingsManager.allThemes[theme.name];
|
||||||
if (AppSettings.theme === theme.name) {
|
if (AppSettings.theme === theme.name) {
|
||||||
AppSettings.theme = SettingsManager.getDefaultTheme();
|
AppSettings.theme = SettingsManager.getDefaultTheme();
|
||||||
}
|
}
|
||||||
SettingsManager.customThemeNames.delete(this.getThemeLocaleKey(theme.name));
|
delete SettingsManager.customThemes[theme.name];
|
||||||
}
|
}
|
||||||
|
|
||||||
loadPluginSettings(): void {
|
loadPluginSettings(): void {
|
||||||
|
|
|
@ -15,6 +15,10 @@ class StorageCache extends StorageBase {
|
||||||
return !Launcher;
|
return !Launcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get locName(): string {
|
||||||
|
return 'Cache';
|
||||||
|
}
|
||||||
|
|
||||||
async save(id: string, data: ArrayBuffer): Promise<StorageSaveResult> {
|
async save(id: string, data: ArrayBuffer): Promise<StorageSaveResult> {
|
||||||
await this._io.save(id, data);
|
await this._io.save(id, data);
|
||||||
return {};
|
return {};
|
||||||
|
@ -38,6 +42,10 @@ class StorageCache extends StorageBase {
|
||||||
watch: undefined;
|
watch: undefined;
|
||||||
unwatch: undefined;
|
unwatch: undefined;
|
||||||
getPathForName: undefined;
|
getPathForName: undefined;
|
||||||
|
getOpenConfig: undefined;
|
||||||
|
getSettingsConfig: undefined;
|
||||||
|
applyConfig: undefined;
|
||||||
|
applySetting: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { StorageCache };
|
export { StorageCache };
|
||||||
|
|
|
@ -38,6 +38,10 @@ class StorageDropbox extends StorageBase {
|
||||||
return AppSettings.dropbox;
|
return AppSettings.dropbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get locName(): string {
|
||||||
|
return Locale.dropbox;
|
||||||
|
}
|
||||||
|
|
||||||
private toFullPath(path: string) {
|
private toFullPath(path: string) {
|
||||||
const rootFolder = AppSettings.dropboxFolder;
|
const rootFolder = AppSettings.dropboxFolder;
|
||||||
if (rootFolder) {
|
if (rootFolder) {
|
||||||
|
@ -114,29 +118,29 @@ class StorageDropbox extends StorageBase {
|
||||||
getOpenConfig(): StorageOpenConfig {
|
getOpenConfig(): StorageOpenConfig {
|
||||||
const keyField: StorageConfigFieldText = {
|
const keyField: StorageConfigFieldText = {
|
||||||
id: 'key',
|
id: 'key',
|
||||||
title: 'dropboxAppKey',
|
title: Locale.dropboxAppKey,
|
||||||
desc: 'dropboxAppKeyDesc',
|
desc: Locale.dropboxAppKeyDesc,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
required: true,
|
required: true,
|
||||||
pattern: '\\w+'
|
pattern: '\\w+'
|
||||||
};
|
};
|
||||||
const secretField: StorageConfigFieldPassword = {
|
const secretField: StorageConfigFieldPassword = {
|
||||||
id: 'secret',
|
id: 'secret',
|
||||||
title: 'dropboxAppSecret',
|
title: Locale.dropboxAppSecret,
|
||||||
desc: 'dropboxAppSecretDesc',
|
desc: Locale.dropboxAppSecretDesc,
|
||||||
type: 'password',
|
type: 'password',
|
||||||
required: true,
|
required: true,
|
||||||
pattern: '\\w+'
|
pattern: '\\w+'
|
||||||
};
|
};
|
||||||
const folderField: StorageConfigFieldText = {
|
const folderField: StorageConfigFieldText = {
|
||||||
id: 'folder',
|
id: 'folder',
|
||||||
title: 'dropboxFolder',
|
title: Locale.dropboxFolder,
|
||||||
desc: 'dropboxFolderDesc',
|
desc: Locale.dropboxFolderDesc,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
placeholder: 'dropboxFolderPlaceholder'
|
placeholder: Locale.dropboxFolderPlaceholder
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
desc: 'dropboxSetupDesc',
|
desc: Locale.dropboxSetupDesc,
|
||||||
fields: [keyField, secretField, folderField]
|
fields: [keyField, secretField, folderField]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -146,15 +150,19 @@ class StorageDropbox extends StorageBase {
|
||||||
const appKey = this.getKey();
|
const appKey = this.getKey();
|
||||||
const linkField: StorageConfigFieldSelect = {
|
const linkField: StorageConfigFieldSelect = {
|
||||||
id: 'link',
|
id: 'link',
|
||||||
title: 'dropboxLink',
|
title: Locale.dropboxLink,
|
||||||
type: 'select',
|
type: 'select',
|
||||||
value: 'custom',
|
value: 'custom',
|
||||||
options: { app: 'dropboxLinkApp', full: 'dropboxLinkFull', custom: 'dropboxLinkCustom' }
|
options: {
|
||||||
|
app: Locale.dropboxLinkApp,
|
||||||
|
full: Locale.dropboxLinkFull,
|
||||||
|
custom: Locale.dropboxLinkCustom
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const keyField: StorageConfigFieldText = {
|
const keyField: StorageConfigFieldText = {
|
||||||
id: 'key',
|
id: 'key',
|
||||||
title: 'dropboxAppKey',
|
title: Locale.dropboxAppKey,
|
||||||
desc: 'dropboxAppKeyDesc',
|
desc: Locale.dropboxAppKeyDesc,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
required: true,
|
required: true,
|
||||||
pattern: '\\w+',
|
pattern: '\\w+',
|
||||||
|
@ -162,8 +170,8 @@ class StorageDropbox extends StorageBase {
|
||||||
};
|
};
|
||||||
const secretField: StorageConfigFieldPassword = {
|
const secretField: StorageConfigFieldPassword = {
|
||||||
id: 'secret',
|
id: 'secret',
|
||||||
title: 'dropboxAppSecret',
|
title: Locale.dropboxAppSecret,
|
||||||
desc: 'dropboxAppSecretDesc',
|
desc: Locale.dropboxAppSecretDesc,
|
||||||
type: 'password',
|
type: 'password',
|
||||||
required: true,
|
required: true,
|
||||||
pattern: '\\w+',
|
pattern: '\\w+',
|
||||||
|
@ -171,8 +179,8 @@ class StorageDropbox extends StorageBase {
|
||||||
};
|
};
|
||||||
const folderField: StorageConfigFieldText = {
|
const folderField: StorageConfigFieldText = {
|
||||||
id: 'folder',
|
id: 'folder',
|
||||||
title: 'dropboxFolder',
|
title: Locale.dropboxFolder,
|
||||||
desc: 'dropboxFolderSettingsDesc',
|
desc: Locale.dropboxFolderSettingsDesc,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: AppSettings.dropboxFolder ?? ''
|
value: AppSettings.dropboxFolder ?? ''
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,6 +18,10 @@ class StorageFileCache extends StorageBase {
|
||||||
return !!Launcher;
|
return !!Launcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get locName(): string {
|
||||||
|
return 'File cache';
|
||||||
|
}
|
||||||
|
|
||||||
private async init(): Promise<string> {
|
private async init(): Promise<string> {
|
||||||
if (this._path) {
|
if (this._path) {
|
||||||
return Promise.resolve(this._path);
|
return Promise.resolve(this._path);
|
||||||
|
@ -108,6 +112,10 @@ class StorageFileCache extends StorageBase {
|
||||||
unwatch: undefined;
|
unwatch: undefined;
|
||||||
watch: undefined;
|
watch: undefined;
|
||||||
getPathForName: undefined;
|
getPathForName: undefined;
|
||||||
|
getOpenConfig: undefined;
|
||||||
|
getSettingsConfig: undefined;
|
||||||
|
applyConfig: undefined;
|
||||||
|
applySetting: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { StorageFileCache };
|
export { StorageFileCache };
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
} from 'storage/types';
|
} from 'storage/types';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as NodePath from 'path';
|
import * as NodePath from 'path';
|
||||||
|
import { Locale } from 'util/locale';
|
||||||
|
|
||||||
interface StorageFileWatcherCallbackItem {
|
interface StorageFileWatcherCallbackItem {
|
||||||
file: string;
|
file: string;
|
||||||
|
@ -40,6 +41,10 @@ class StorageFile extends StorageBase {
|
||||||
return !!Launcher;
|
return !!Launcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get locName(): string {
|
||||||
|
return Locale.file;
|
||||||
|
}
|
||||||
|
|
||||||
async load(path: string): Promise<StorageFileData> {
|
async load(path: string): Promise<StorageFileData> {
|
||||||
this._logger.info('Load', path);
|
this._logger.info('Load', path);
|
||||||
const ts = this._logger.ts();
|
const ts = this._logger.ts();
|
||||||
|
@ -185,6 +190,10 @@ class StorageFile extends StorageBase {
|
||||||
list: undefined;
|
list: undefined;
|
||||||
remove: undefined;
|
remove: undefined;
|
||||||
getPathForName: undefined;
|
getPathForName: undefined;
|
||||||
|
getOpenConfig: undefined;
|
||||||
|
getSettingsConfig: undefined;
|
||||||
|
applyConfig: undefined;
|
||||||
|
applySetting: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { StorageFile };
|
export { StorageFile };
|
||||||
|
|
|
@ -35,6 +35,10 @@ class StorageGDrive extends StorageBase {
|
||||||
return AppSettings.gdrive;
|
return AppSettings.gdrive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get locName(): string {
|
||||||
|
return Locale.gdrive;
|
||||||
|
}
|
||||||
|
|
||||||
getPathForName(fileName: string): string {
|
getPathForName(fileName: string): string {
|
||||||
return NewFileIdPrefix + fileName;
|
return NewFileIdPrefix + fileName;
|
||||||
}
|
}
|
||||||
|
@ -398,6 +402,10 @@ class StorageGDrive extends StorageBase {
|
||||||
mkdir: undefined;
|
mkdir: undefined;
|
||||||
watch: undefined;
|
watch: undefined;
|
||||||
unwatch: undefined;
|
unwatch: undefined;
|
||||||
|
getOpenConfig: undefined;
|
||||||
|
getSettingsConfig: undefined;
|
||||||
|
applyConfig: undefined;
|
||||||
|
applySetting: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { StorageGDrive };
|
export { StorageGDrive };
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
StorageRevConflictError,
|
StorageRevConflictError,
|
||||||
StorageSaveResult
|
StorageSaveResult
|
||||||
} from 'storage/types';
|
} from 'storage/types';
|
||||||
|
import { Locale } from 'util/locale';
|
||||||
|
|
||||||
// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
|
// 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;
|
return AppSettings.onedrive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get locName(): string {
|
||||||
|
return Locale.onedrive;
|
||||||
|
}
|
||||||
|
|
||||||
getPathForName(fileName: string): string {
|
getPathForName(fileName: string): string {
|
||||||
return '/drive/root:/' + fileName + '.kdbx';
|
return '/drive/root:/' + fileName + '.kdbx';
|
||||||
}
|
}
|
||||||
|
@ -323,6 +328,10 @@ class StorageOneDrive extends StorageBase {
|
||||||
|
|
||||||
watch: undefined;
|
watch: undefined;
|
||||||
unwatch: undefined;
|
unwatch: undefined;
|
||||||
|
getOpenConfig: undefined;
|
||||||
|
getSettingsConfig: undefined;
|
||||||
|
applyConfig: undefined;
|
||||||
|
applySetting: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { StorageOneDrive };
|
export { StorageOneDrive };
|
||||||
|
|
|
@ -32,6 +32,10 @@ class StorageWebDav extends StorageBase {
|
||||||
return AppSettings.webdav;
|
return AppSettings.webdav;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get locName(): string {
|
||||||
|
return Locale.webdav;
|
||||||
|
}
|
||||||
|
|
||||||
get needShowOpenConfig(): boolean {
|
get needShowOpenConfig(): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -39,24 +43,24 @@ class StorageWebDav extends StorageBase {
|
||||||
getOpenConfig(): StorageOpenConfig {
|
getOpenConfig(): StorageOpenConfig {
|
||||||
const pathField: StorageConfigFieldText = {
|
const pathField: StorageConfigFieldText = {
|
||||||
id: 'path',
|
id: 'path',
|
||||||
title: 'openUrl',
|
title: Locale.openUrl,
|
||||||
desc: 'openUrlDesc',
|
desc: Locale.openUrlDesc,
|
||||||
type: 'text',
|
type: 'text',
|
||||||
required: true,
|
required: true,
|
||||||
pattern: '^https://.+'
|
pattern: '^https://.+'
|
||||||
};
|
};
|
||||||
const userField: StorageConfigFieldText = {
|
const userField: StorageConfigFieldText = {
|
||||||
id: 'user',
|
id: 'user',
|
||||||
title: 'openUser',
|
title: Locale.openUser,
|
||||||
desc: 'openUserDesc',
|
desc: Locale.openUserDesc,
|
||||||
placeholder: 'openUserPlaceholder',
|
placeholder: Locale.openUserPlaceholder,
|
||||||
type: 'text'
|
type: 'text'
|
||||||
};
|
};
|
||||||
const passwordField: StorageConfigFieldPassword = {
|
const passwordField: StorageConfigFieldPassword = {
|
||||||
id: 'password',
|
id: 'password',
|
||||||
title: 'openPass',
|
title: Locale.openPass,
|
||||||
desc: 'openPassDesc',
|
desc: Locale.openPassDesc,
|
||||||
placeholder: 'openPassPlaceholder',
|
placeholder: Locale.openPassPlaceholder,
|
||||||
type: 'password'
|
type: 'password'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -68,14 +72,14 @@ class StorageWebDav extends StorageBase {
|
||||||
getSettingsConfig(): StorageSettingsConfig {
|
getSettingsConfig(): StorageSettingsConfig {
|
||||||
const methodField: StorageConfigFieldSelect = {
|
const methodField: StorageConfigFieldSelect = {
|
||||||
id: 'webdavSaveMethod',
|
id: 'webdavSaveMethod',
|
||||||
title: 'webdavSaveMethod',
|
title: Locale.webdavSaveMethod,
|
||||||
type: 'select',
|
type: 'select',
|
||||||
value: AppSettings.webdavSaveMethod,
|
value: AppSettings.webdavSaveMethod,
|
||||||
options: { default: 'webdavSaveMove', put: 'webdavSavePut' }
|
options: { move: Locale.webdavSaveMove, put: Locale.webdavSavePut }
|
||||||
};
|
};
|
||||||
const reloadField: StorageConfigFieldCheckbox = {
|
const reloadField: StorageConfigFieldCheckbox = {
|
||||||
id: 'webdavStatReload',
|
id: 'webdavStatReload',
|
||||||
title: 'webdavStatReload',
|
title: Locale.webdavStatReload,
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
value: AppSettings.webdavStatReload ? 'true' : null
|
value: AppSettings.webdavStatReload ? 'true' : null
|
||||||
};
|
};
|
||||||
|
@ -408,6 +412,7 @@ class StorageWebDav extends StorageBase {
|
||||||
watch: undefined;
|
watch: undefined;
|
||||||
unwatch: undefined;
|
unwatch: undefined;
|
||||||
getPathForName: undefined;
|
getPathForName: undefined;
|
||||||
|
applyConfig: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { StorageWebDav };
|
export { StorageWebDav };
|
||||||
|
|
|
@ -85,9 +85,7 @@ abstract class StorageBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract load(id: string, opts?: StorageFileOptions): Promise<StorageFileData>;
|
abstract load(id: string, opts?: StorageFileOptions): Promise<StorageFileData>;
|
||||||
|
|
||||||
abstract stat(id: string, opts?: StorageFileOptions): Promise<StorageFileStat>;
|
abstract stat(id: string, opts?: StorageFileOptions): Promise<StorageFileStat>;
|
||||||
|
|
||||||
abstract save(
|
abstract save(
|
||||||
id: string,
|
id: string,
|
||||||
data: ArrayBuffer,
|
data: ArrayBuffer,
|
||||||
|
@ -96,22 +94,22 @@ abstract class StorageBase {
|
||||||
): Promise<StorageSaveResult>;
|
): Promise<StorageSaveResult>;
|
||||||
|
|
||||||
abstract remove?(id: string, opts?: StorageFileOptions): Promise<void>;
|
abstract remove?(id: string, opts?: StorageFileOptions): Promise<void>;
|
||||||
|
|
||||||
abstract list?(dir?: string): Promise<StorageListItem[]>;
|
abstract list?(dir?: string): Promise<StorageListItem[]>;
|
||||||
|
|
||||||
abstract mkdir?(path: string): Promise<void>;
|
abstract mkdir?(path: string): Promise<void>;
|
||||||
|
|
||||||
abstract watch?(path: string, callback: StorageFileWatcherCallback): void;
|
abstract watch?(path: string, callback: StorageFileWatcherCallback): void;
|
||||||
|
|
||||||
abstract unwatch?(path: string, callback: StorageFileWatcherCallback): void;
|
abstract unwatch?(path: string, callback: StorageFileWatcherCallback): void;
|
||||||
|
|
||||||
abstract getPathForName?(fileName: string): string;
|
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 {
|
protected getOAuthConfig(): StorageOAuthConfig {
|
||||||
throw new Error('OAuth is not supported');
|
throw new Error('OAuth is not supported');
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract get enabled(): boolean;
|
abstract get enabled(): boolean;
|
||||||
|
abstract get locName(): string;
|
||||||
|
|
||||||
get loggedIn(): boolean {
|
get loggedIn(): boolean {
|
||||||
return !!this.getStoredOAuthToken();
|
return !!this.getStoredOAuthToken();
|
||||||
|
@ -494,6 +492,8 @@ abstract class StorageBase {
|
||||||
protected async oauthRevokeToken(url?: string, usePost?: boolean): Promise<void> {
|
protected async oauthRevokeToken(url?: string, usePost?: boolean): Promise<void> {
|
||||||
const token = this.getStoredOAuthToken();
|
const token = this.getStoredOAuthToken();
|
||||||
if (token) {
|
if (token) {
|
||||||
|
this.deleteStoredToken();
|
||||||
|
this._oauthToken = undefined;
|
||||||
if (url) {
|
if (url) {
|
||||||
await this.xhr({
|
await this.xhr({
|
||||||
url: url.replace('{token}', token.accessToken),
|
url: url.replace('{token}', token.accessToken),
|
||||||
|
@ -501,8 +501,6 @@ abstract class StorageBase {
|
||||||
method: usePost ? 'POST' : 'GET'
|
method: usePost ? 'POST' : 'GET'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.deleteStoredToken();
|
|
||||||
this._oauthToken = undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,7 +566,7 @@ abstract class StorageBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
this._logger.info('OAuth code exchanged', response);
|
this._logger.info('OAuth code exchanged', response);
|
||||||
this.oauthProcessReturn(response);
|
this.oauthProcessReturn(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async oauthExchangeRefreshToken(): Promise<void> {
|
private async oauthExchangeRefreshToken(): Promise<void> {
|
||||||
|
@ -617,24 +615,6 @@ abstract class StorageBase {
|
||||||
get needShowOpenConfig(): boolean {
|
get needShowOpenConfig(): boolean {
|
||||||
return false;
|
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 };
|
export { StorageBase };
|
||||||
|
|
|
@ -74,8 +74,7 @@ export interface StorageSaveResult {
|
||||||
path?: string;
|
path?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StorageConfigField {
|
export interface StorageConfigFieldBase {
|
||||||
type: string;
|
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
desc?: string;
|
desc?: string;
|
||||||
|
@ -83,27 +82,33 @@ export interface StorageConfigField {
|
||||||
value?: string | null;
|
value?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StorageConfigFieldText extends StorageConfigField {
|
export interface StorageConfigFieldText extends StorageConfigFieldBase {
|
||||||
type: 'text';
|
type: 'text';
|
||||||
pattern?: string;
|
pattern?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StorageConfigFieldPassword extends StorageConfigField {
|
export interface StorageConfigFieldPassword extends StorageConfigFieldBase {
|
||||||
type: 'password';
|
type: 'password';
|
||||||
pattern?: string;
|
pattern?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StorageConfigFieldSelect extends StorageConfigField {
|
export interface StorageConfigFieldSelect extends StorageConfigFieldBase {
|
||||||
type: 'select';
|
type: 'select';
|
||||||
options: Record<string, string>;
|
options: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StorageConfigFieldCheckbox extends StorageConfigField {
|
export interface StorageConfigFieldCheckbox extends StorageConfigFieldBase {
|
||||||
type: 'checkbox';
|
type: 'checkbox';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type StorageConfigField =
|
||||||
|
| StorageConfigFieldText
|
||||||
|
| StorageConfigFieldPassword
|
||||||
|
| StorageConfigFieldSelect
|
||||||
|
| StorageConfigFieldCheckbox;
|
||||||
|
|
||||||
export interface StorageOpenConfig {
|
export interface StorageOpenConfig {
|
||||||
desc?: string;
|
desc?: string;
|
||||||
fields: StorageConfigField[];
|
fields: StorageConfigField[];
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import { Events } from 'framework/events';
|
import { Events } from 'framework/events';
|
||||||
import { View } from 'framework/views/view';
|
import { View } from 'framework/views/view';
|
||||||
import { AutoType } from 'auto-type';
|
|
||||||
import { Storage } from 'storage';
|
import { Storage } from 'storage';
|
||||||
import { RuntimeInfo } from 'const/runtime-info';
|
|
||||||
import { Updater } from 'comp/app/updater';
|
import { Updater } from 'comp/app/updater';
|
||||||
import { Launcher } from 'comp/launcher';
|
import { Launcher } from 'comp/launcher';
|
||||||
import { SettingsManager } from 'comp/settings/settings-manager';
|
import { SettingsManager } from 'comp/settings/settings-manager';
|
||||||
|
@ -10,14 +8,9 @@ import { Alerts } from 'comp/ui/alerts';
|
||||||
import { Links } from 'const/links';
|
import { Links } from 'const/links';
|
||||||
import { AppSettingsModel } from 'models/app-settings-model';
|
import { AppSettingsModel } from 'models/app-settings-model';
|
||||||
import { UpdateModel } from 'models/update-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 { Locale } from 'util/locale';
|
||||||
import { SettingsLogsView } from 'views/settings/settings-logs-view';
|
import { SettingsLogsView } from 'views/settings/settings-logs-view';
|
||||||
import { SettingsPrvView } from 'views/settings/settings-prv-view';
|
import { minmax } from 'util/fn';
|
||||||
import { mapObject, minmax } from 'util/fn';
|
|
||||||
import { ThemeWatcher } from 'comp/browser/theme-watcher';
|
|
||||||
import { NativeModules } from 'comp/launcher/native-modules';
|
import { NativeModules } from 'comp/launcher/native-modules';
|
||||||
import template from 'templates/settings/settings-general.hbs';
|
import template from 'templates/settings/settings-general.hbs';
|
||||||
|
|
||||||
|
@ -80,184 +73,6 @@ class SettingsGeneralView extends View {
|
||||||
this.listenTo(Events, 'theme-applied', this.render);
|
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) {
|
changeTheme(e) {
|
||||||
const theme = e.target.closest('.settings__general-theme').dataset.theme;
|
const theme = e.target.closest('.settings__general-theme').dataset.theme;
|
||||||
if (theme === '...') {
|
if (theme === '...') {
|
||||||
|
|
|
@ -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 };
|
|
|
@ -11,6 +11,7 @@ import { OpenController } from 'comp/app/open-controller';
|
||||||
import { OpenState } from 'models/ui/open-state';
|
import { OpenState } from 'models/ui/open-state';
|
||||||
import { GeneratorState } from 'models/ui/generator-state';
|
import { GeneratorState } from 'models/ui/generator-state';
|
||||||
import { Alerts } from 'comp/ui/alerts';
|
import { Alerts } from 'comp/ui/alerts';
|
||||||
|
import { StorageBase } from 'storage/storage-base';
|
||||||
|
|
||||||
const logger = new Logger('open');
|
const logger = new Logger('open');
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ export const OpenButtons: FunctionComponent = () => {
|
||||||
const secondRowVisible = useModelField(OpenState, 'secondRowVisible');
|
const secondRowVisible = useModelField(OpenState, 'secondRowVisible');
|
||||||
const storageInProgress = useModelField(OpenState, 'storageInProgress');
|
const storageInProgress = useModelField(OpenState, 'storageInProgress');
|
||||||
|
|
||||||
const storageProviders = [];
|
const storageProviders: StorageBase[] = [];
|
||||||
|
|
||||||
if (AppSettings.canOpenStorage) {
|
if (AppSettings.canOpenStorage) {
|
||||||
for (const prv of Object.values(Storage.getAll())) {
|
for (const prv of Object.values(Storage.getAll())) {
|
||||||
|
|
|
@ -10,7 +10,16 @@ import {
|
||||||
PasswordGeneratorPreset
|
PasswordGeneratorPreset
|
||||||
} from 'util/generators/password-generator';
|
} from 'util/generators/password-generator';
|
||||||
import { Locale } from 'util/locale';
|
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 = () => {
|
export const GeneratorPresetsPanel: FunctionComponent = () => {
|
||||||
const backClicked = () => Workspace.showList();
|
const backClicked = () => Workspace.showList();
|
||||||
|
@ -58,7 +67,7 @@ export const GeneratorPresetsPanel: FunctionComponent = () => {
|
||||||
const range = key as CharRange;
|
const range = key as CharRange;
|
||||||
return {
|
return {
|
||||||
name: range,
|
name: range,
|
||||||
title: Locale.get(`genPs${StringFormat.capFirst(range)}`) ?? '',
|
title: CharRangeTitles[range] ?? '',
|
||||||
enabled: !!selected[range],
|
enabled: !!selected[range],
|
||||||
sample: rangeOverride[range] || CharRanges[range]
|
sample: rangeOverride[range] || CharRanges[range]
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import { FunctionComponent, h } from 'preact';
|
import { FunctionComponent, h } from 'preact';
|
||||||
import { SettingsGeneralAdvancedView } from 'views/settings/general/settings-general-advanced-view';
|
import { SettingsGeneralAdvancedView } from 'views/settings/general/settings-general-advanced-view';
|
||||||
|
import { Launcher } from 'comp/launcher';
|
||||||
|
import { Features } from 'util/features';
|
||||||
|
|
||||||
export const SettingsGeneralAdvanced: FunctionComponent = () => {
|
export const SettingsGeneralAdvanced: FunctionComponent = () => {
|
||||||
return h(SettingsGeneralAdvancedView, null);
|
return h(SettingsGeneralAdvancedView, {
|
||||||
|
devTools: !!Launcher,
|
||||||
|
showReloadApp: Features.isStandalone
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,48 @@
|
||||||
import { FunctionComponent, h } from 'preact';
|
import { FunctionComponent, h } from 'preact';
|
||||||
import { SettingsGeneralAppearanceView } from 'views/settings/general/settings-general-appearance-view';
|
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 = () => {
|
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
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
import { FunctionComponent, h } from 'preact';
|
import { FunctionComponent, h } from 'preact';
|
||||||
import { SettingsGeneralAuditView } from 'views/settings/general/settings-general-audit-view';
|
import { SettingsGeneralAuditView } from 'views/settings/general/settings-general-audit-view';
|
||||||
|
import { AppSettings } from 'models/app-settings';
|
||||||
|
|
||||||
export const SettingsGeneralAudit: FunctionComponent = () => {
|
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
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,29 @@
|
||||||
import { FunctionComponent, h } from 'preact';
|
import { FunctionComponent, h } from 'preact';
|
||||||
import { SettingsGeneralFunctionView } from 'views/settings/general/settings-general-function-view';
|
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 = () => {
|
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
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,17 @@
|
||||||
import { FunctionComponent, h } from 'preact';
|
import { FunctionComponent, h } from 'preact';
|
||||||
import { SettingsGeneralLockView } from 'views/settings/general/settings-general-lock-view';
|
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 = () => {
|
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
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,6 +1,24 @@
|
||||||
import { FunctionComponent, h } from 'preact';
|
import { FunctionComponent, h } from 'preact';
|
||||||
import { SettingsGeneralStorageView } from 'views/settings/general/settings-general-storage-view';
|
import { SettingsGeneralStorageView } from 'views/settings/general/settings-general-storage-view';
|
||||||
|
import { AppSettings } from 'models/app-settings';
|
||||||
|
import { Storage } from 'storage';
|
||||||
|
|
||||||
export const SettingsGeneralStorage: FunctionComponent = () => {
|
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()
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,85 @@
|
||||||
import { FunctionComponent, h } from 'preact';
|
import { FunctionComponent, h } from 'preact';
|
||||||
import { SettingsGeneralUpdateView } from 'views/settings/general/settings-general-update-view';
|
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 = () => {
|
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'
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { FunctionComponent, h } from 'preact';
|
import { FunctionComponent, h } from 'preact';
|
||||||
import { SettingsGeneralView } from 'views/settings/settings-general-view';
|
import { SettingsGeneralView } from 'views/settings/settings-general-view';
|
||||||
|
import { useModelWatcher } from 'util/ui/hooks';
|
||||||
|
import { AppSettings } from 'models/app-settings';
|
||||||
|
|
||||||
export const SettingsGeneral: FunctionComponent = () => {
|
export const SettingsGeneral: FunctionComponent = () => {
|
||||||
|
useModelWatcher(AppSettings);
|
||||||
|
|
||||||
return h(SettingsGeneralView, null);
|
return h(SettingsGeneralView, null);
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,7 @@ function withReplace(name: string): LocWithReplace {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
export const Locale = {
|
export const Locale = {
|
||||||
set,
|
set,
|
||||||
get,
|
// get,
|
||||||
get localeName(): string { return activeLocaleName; },
|
get localeName(): string { return activeLocaleName; },
|
||||||
|
|
||||||
// this code is generated using npm run generate-locale
|
// this code is generated using npm run generate-locale
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
import { LocWithReplace } from 'util/locale';
|
import { LocWithReplace } from 'util/locale';
|
||||||
|
import { StringFormat } from 'util/formatting/string-format';
|
||||||
|
|
||||||
export const LocalizedWith: FunctionComponent<{ str: LocWithReplace }> = ({ str, children }) => {
|
export const LocalizedWith: FunctionComponent<{ str: LocWithReplace; capitalize?: boolean }> = ({
|
||||||
const [first, ...rest] = str.with('{}').split('{}');
|
str,
|
||||||
|
capitalize,
|
||||||
|
children
|
||||||
|
}) => {
|
||||||
|
let val = str.with('{}');
|
||||||
|
if (capitalize) {
|
||||||
|
val = StringFormat.capFirst(val);
|
||||||
|
}
|
||||||
|
const [first, ...rest] = val.split('{}');
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{first}
|
{first}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { classes } from 'util/ui/classes';
|
||||||
|
|
||||||
interface StorageProvider {
|
interface StorageProvider {
|
||||||
name: string;
|
name: string;
|
||||||
|
locName: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +127,7 @@ export const OpenButtonsView: FunctionComponent<{
|
||||||
onClick={() => storageClicked(prv.name)}
|
onClick={() => storageClicked(prv.name)}
|
||||||
>
|
>
|
||||||
{prv.icon ? <i class={`fa fa-${prv.icon} open__icon-i`} /> : null}
|
{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>
|
</div>
|
||||||
))}
|
))}
|
||||||
{showDemoInSecondRow ? (
|
{showDemoInSecondRow ? (
|
||||||
|
|
|
@ -1,5 +1,34 @@
|
||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
|
import { Locale } from 'util/locale';
|
||||||
|
|
||||||
export const SettingsGeneralAdvancedView: FunctionComponent = () => {
|
export const SettingsGeneralAdvancedView: FunctionComponent<{
|
||||||
return <></>;
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,172 @@
|
||||||
import { FunctionComponent } from 'preact';
|
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 = () => {
|
export const SettingsGeneralAppearanceView: FunctionComponent<{
|
||||||
return <></>;
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,111 @@
|
||||||
import { FunctionComponent } from 'preact';
|
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 = () => {
|
export const SettingsGeneralAuditView: FunctionComponent<{
|
||||||
return <></>;
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,288 @@
|
||||||
import { FunctionComponent } from 'preact';
|
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 = () => {
|
export const SettingsGeneralFunctionView: FunctionComponent<{
|
||||||
return <></>;
|
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}
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,114 @@
|
||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
|
import { Locale } from 'util/locale';
|
||||||
|
import { LocalizedWith } from 'views/components/localized-with';
|
||||||
|
|
||||||
export const SettingsGeneralLockView: FunctionComponent = () => {
|
export const SettingsGeneralLockView: FunctionComponent<{
|
||||||
return <></>;
|
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}
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,5 +1,73 @@
|
||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
|
import { Locale } from 'util/locale';
|
||||||
|
import { SettingsGeneralStorageProvider } from 'ui/settings/general/settings-general-storage-provider';
|
||||||
|
|
||||||
export const SettingsGeneralStorageView: FunctionComponent = () => {
|
interface SettingsGeneralStorageProviderItem {
|
||||||
return <></>;
|
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>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,86 @@
|
||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
|
import { Locale } from 'util/locale';
|
||||||
|
import { AppSettingsAutoUpdate } from 'models/app-settings';
|
||||||
|
import { Links } from 'const/links';
|
||||||
|
|
||||||
export const SettingsGeneralUpdateView: FunctionComponent = () => {
|
export const SettingsGeneralUpdateView: FunctionComponent<{
|
||||||
return <></>;
|
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}
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -4,12 +4,12 @@ import { expect } from 'chai';
|
||||||
describe('Locale', () => {
|
describe('Locale', () => {
|
||||||
it('returns simple locale strings', () => {
|
it('returns simple locale strings', () => {
|
||||||
expect(Locale.name).to.eql('name');
|
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', () => {
|
it('returns replaced locale strings', () => {
|
||||||
expect(Locale.minutes.with('3')).to.eql('3 minutes');
|
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', () => {
|
it('sets a custom locale', () => {
|
||||||
|
|
Loading…
Reference in New Issue