718 lines
25 KiB
Vue
718 lines
25 KiB
Vue
<template>
|
||
<Window ref="window" height="95%" width="600px" @close="close">
|
||
<template #header>
|
||
Настройки
|
||
</template>
|
||
|
||
<div class="col row">
|
||
<div class="full-height">
|
||
<q-tabs
|
||
ref="tabs"
|
||
v-model="selectedTab"
|
||
class="bg-grey-3 text-black"
|
||
|
||
left-icon="la la-caret-up"
|
||
right-icon="la la-caret-down"
|
||
active-color="white"
|
||
active-bg-color="primary"
|
||
indicator-color="black"
|
||
vertical
|
||
no-caps
|
||
stretch
|
||
inline-label
|
||
>
|
||
<div v-show="tabsScrollable" class="q-pt-lg" />
|
||
<q-tab class="tab" name="profiles" icon="la la-users" label="Профили" />
|
||
<q-tab class="tab" name="view" icon="la la-eye" label="Вид" />
|
||
<q-tab class="tab" name="buttons" icon="la la-grip-horizontal" label="Кнопки" />
|
||
<q-tab class="tab" name="keys" icon="la la-gamepad" label="Управление" />
|
||
<q-tab class="tab" name="pagemove" icon="la la-school" label="Листание" />
|
||
<q-tab class="tab" name="convert" icon="la la-magic" label="Конвертир." />
|
||
<q-tab class="tab" name="others" icon="la la-list-ul" label="Прочее" />
|
||
<q-tab class="tab" name="reset" icon="la la-broom" label="Сброс" />
|
||
<div v-show="tabsScrollable" class="q-pt-lg" />
|
||
</q-tabs>
|
||
</div>
|
||
|
||
<div class="col fit">
|
||
<!-- Профили --------------------------------------------------------------------->
|
||
<div v-if="selectedTab == 'profiles'" class="fit tab-panel">
|
||
@@include('./include/ProfilesTab.inc');
|
||
</div>
|
||
<!-- Вид ------------------------------------------------------------------------->
|
||
<div v-if="selectedTab == 'view'" class="fit column">
|
||
@@include('./include/ViewTab.inc');
|
||
</div>
|
||
<!-- Кнопки ---------------------------------------------------------------------->
|
||
<div v-if="selectedTab == 'buttons'" class="fit tab-panel">
|
||
@@include('./include/ButtonsTab.inc');
|
||
</div>
|
||
<!-- Управление ------------------------------------------------------------------>
|
||
<div v-if="selectedTab == 'keys'" class="fit column">
|
||
@@include('./include/KeysTab.inc');
|
||
</div>
|
||
<!-- Листание -------------------------------------------------------------------->
|
||
<div v-if="selectedTab == 'pagemove'" class="fit tab-panel">
|
||
@@include('./include/PageMoveTab.inc');
|
||
</div>
|
||
<!-- Конвертирование ------------------------------------------------------------->
|
||
<div v-if="selectedTab == 'convert'" class="fit tab-panel">
|
||
@@include('./include/ConvertTab.inc');
|
||
</div>
|
||
<!-- Прочее ---------------------------------------------------------------------->
|
||
<div v-if="selectedTab == 'others'" class="fit tab-panel">
|
||
@@include('./include/OthersTab.inc');
|
||
</div>
|
||
<!-- Сброс ----------------------------------------------------------------------->
|
||
<div v-if="selectedTab == 'reset'" class="fit tab-panel">
|
||
@@include('./include/ResetTab.inc');
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Window>
|
||
</template>
|
||
|
||
<script>
|
||
//-----------------------------------------------------------------------------
|
||
import { ref, watch } from 'vue';
|
||
import vueComponent from '../../vueComponent.js';
|
||
|
||
import _ from 'lodash';
|
||
|
||
import * as utils from '../../../share/utils';
|
||
import * as cryptoUtils from '../../../share/cryptoUtils';
|
||
import Window from '../../share/Window.vue';
|
||
import NumInput from '../../share/NumInput.vue';
|
||
import UserHotKeys from './UserHotKeys/UserHotKeys.vue';
|
||
import wallpaperStorage from '../share/wallpaperStorage';
|
||
|
||
import rstore from '../../../store/modules/reader';
|
||
import defPalette from './defPalette';
|
||
|
||
const hex = /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/;
|
||
|
||
const componentOptions = {
|
||
components: {
|
||
Window,
|
||
NumInput,
|
||
UserHotKeys,
|
||
},
|
||
data: function() {
|
||
return Object.assign({}, rstore.settingDefaults);
|
||
},
|
||
watch: {
|
||
settings: function() {
|
||
this.settingsChanged();
|
||
},
|
||
form: function(newValue) {
|
||
if (this.inited) {
|
||
this.commit('reader/setSettings', _.cloneDeep(newValue));
|
||
}
|
||
},
|
||
fontBold: function(newValue) {
|
||
this.fontWeight = (newValue ? 'bold' : '');
|
||
},
|
||
fontItalic: function(newValue) {
|
||
this.fontStyle = (newValue ? 'italic' : '');
|
||
},
|
||
vertShift: function(newValue) {
|
||
const font = (this.webFontName ? this.webFontName : this.fontName);
|
||
if (this.fontShifts[font] != newValue || this.fontVertShift != newValue) {
|
||
this.fontShifts = Object.assign({}, this.fontShifts, {[font]: newValue});
|
||
this.fontVertShift = newValue;
|
||
}
|
||
},
|
||
fontName: function(newValue) {
|
||
const font = (this.webFontName ? this.webFontName : newValue);
|
||
this.vertShift = this.fontShifts[font] || 0;
|
||
},
|
||
webFontName: function(newValue) {
|
||
const font = (newValue ? newValue : this.fontName);
|
||
this.vertShift = this.fontShifts[font] || 0;
|
||
},
|
||
wallpaper: function(newValue) {
|
||
if (newValue != '' && this.pageChangeAnimation == 'flip')
|
||
this.pageChangeAnimation = '';
|
||
},
|
||
dualPageMode(newValue) {
|
||
if (newValue && this.pageChangeAnimation == 'flip' || this.pageChangeAnimation == 'rightShift')
|
||
this.pageChangeAnimation = '';
|
||
},
|
||
textColor: function(newValue) {
|
||
this.textColorFiltered = newValue;
|
||
},
|
||
textColorFiltered: function(newValue) {
|
||
if (hex.test(newValue))
|
||
this.textColor = newValue;
|
||
},
|
||
backgroundColor: function(newValue) {
|
||
this.bgColorFiltered = newValue;
|
||
},
|
||
bgColorFiltered: function(newValue) {
|
||
if (hex.test(newValue))
|
||
this.backgroundColor = newValue;
|
||
},
|
||
dualDivColor(newValue) {
|
||
this.dualDivColorFiltered = newValue;
|
||
},
|
||
dualDivColorFiltered(newValue) {
|
||
if (hex.test(newValue))
|
||
this.dualDivColor = newValue;
|
||
},
|
||
statusBarColor(newValue) {
|
||
this.statusBarColorFiltered = newValue;
|
||
},
|
||
statusBarColorFiltered(newValue) {
|
||
if (hex.test(newValue))
|
||
this.statusBarColor = newValue;
|
||
},
|
||
},
|
||
};
|
||
class SettingsPage {
|
||
_options = componentOptions;
|
||
|
||
selectedTab = 'profiles';
|
||
selectedViewTab = 'mode';
|
||
selectedKeysTab = 'mouse';
|
||
fontBold = false;
|
||
fontItalic = false;
|
||
vertShift = 0;
|
||
tabsScrollable = false;
|
||
textColorFiltered = '';
|
||
bgColorFiltered = '';
|
||
dualDivColorFiltered = '';
|
||
|
||
webFonts = [];
|
||
fonts = [];
|
||
|
||
serverStorageKeyVisible = false;
|
||
toolButtons = [];
|
||
rstore = {};
|
||
|
||
setup() {
|
||
const settingsProps = { form: ref({}) };
|
||
|
||
for (let prop in rstore.settingDefaults) {
|
||
settingsProps[prop] = ref(_.cloneDeep(rstore.settingDefaults[prop]));
|
||
watch(settingsProps[prop], (newValue) => {
|
||
settingsProps.form.value = Object.assign({}, settingsProps.form.value, {[prop]: newValue});
|
||
}, {deep: true});
|
||
}
|
||
|
||
return settingsProps;
|
||
}
|
||
|
||
created() {
|
||
this.commit = this.$store.commit;
|
||
this.reader = this.$store.state.reader;
|
||
|
||
this.form = {};
|
||
this.rstore = rstore;
|
||
this.toolButtons = rstore.toolButtons;
|
||
this.settingsChanged();
|
||
}
|
||
|
||
mounted() {
|
||
this.$watch(
|
||
'$refs.tabs.scrollable',
|
||
(newValue) => {
|
||
this.tabsScrollable = newValue && !this.$root.isMobileDevice;
|
||
}
|
||
);
|
||
}
|
||
|
||
init() {
|
||
this.$refs.window.init();
|
||
this.inited = true;
|
||
}
|
||
|
||
settingsChanged() {
|
||
if (_.isEqual(this.form, this.settings))
|
||
return;
|
||
|
||
this.form = Object.assign({}, this.settings);
|
||
for (const prop in rstore.settingDefaults) {
|
||
this[prop] = _.cloneDeep(this.form[prop]);
|
||
}
|
||
|
||
this.fontBold = (this.fontWeight == 'bold');
|
||
this.fontItalic = (this.fontStyle == 'italic');
|
||
|
||
this.fonts = rstore.fonts;
|
||
this.webFonts = rstore.webFonts;
|
||
const font = (this.webFontName ? this.webFontName : this.fontName);
|
||
this.vertShift = this.fontShifts[font] || 0;
|
||
this.textColorFiltered = this.textColor;
|
||
this.bgColorFiltered = this.backgroundColor;
|
||
this.dualDivColorFiltered = this.dualDivColor;
|
||
this.statusBarColorFiltered = this.statusBarColor;
|
||
}
|
||
|
||
get mode() {
|
||
return this.$store.state.config.mode;
|
||
}
|
||
|
||
get isExternalConverter() {
|
||
return this.$store.state.config.useExternalBookConverter;
|
||
}
|
||
|
||
get settings() {
|
||
return this.$store.state.reader.settings;
|
||
}
|
||
|
||
get serverSyncEnabled() {
|
||
return this.$store.state.reader.serverSyncEnabled;
|
||
}
|
||
|
||
set serverSyncEnabled(newValue) {
|
||
this.commit('reader/setServerSyncEnabled', newValue);
|
||
}
|
||
|
||
get profiles() {
|
||
return this.$store.state.reader.profiles;
|
||
}
|
||
|
||
get currentProfileOptions() {
|
||
const profNames = Object.keys(this.profiles)
|
||
profNames.sort();
|
||
|
||
let result = [{label: 'Нет', value: ''}];
|
||
profNames.forEach(name => {
|
||
result.push({label: name, value: name});
|
||
});
|
||
return result;
|
||
}
|
||
|
||
get wallpaperOptions() {
|
||
let result = [{label: 'Нет', value: ''}];
|
||
|
||
const userWallpapers = _.cloneDeep(this.userWallpapers);
|
||
userWallpapers.sort((a, b) => a.label.localeCompare(b.label));
|
||
|
||
for (const wp of userWallpapers) {
|
||
if (wallpaperStorage.keyExists(wp.cssClass))
|
||
result.push({label: wp.label, value: wp.cssClass});
|
||
}
|
||
|
||
for (let i = 1; i <= 17; i++) {
|
||
result.push({label: i, value: `paper${i}`});
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
get fontsOptions() {
|
||
let result = [];
|
||
this.fonts.forEach(font => {
|
||
result.push({label: (font.label ? font.label : font.name), value: font.name});
|
||
});
|
||
return result;
|
||
}
|
||
|
||
get webFontsOptions() {
|
||
let result = [{label: 'Нет', value: ''}];
|
||
this.webFonts.forEach(font => {
|
||
result.push({label: font.name, value: font.name});
|
||
});
|
||
return result;
|
||
}
|
||
|
||
get pageChangeAnimationOptions() {
|
||
let result = [
|
||
{label: 'Нет', value: ''},
|
||
{label: 'Вверх-вниз', value: 'downShift'},
|
||
(!this.dualPageMode ? {label: 'Вправо-влево', value: 'rightShift'} : null),
|
||
{label: 'Протаивание', value: 'thaw'},
|
||
{label: 'Мерцание', value: 'blink'},
|
||
{label: 'Вращение', value: 'rotate'},
|
||
(this.wallpaper == '' && !this.dualPageMode ? {label: 'Листание', value: 'flip'} : null),
|
||
];
|
||
|
||
result = result.filter(v => v);
|
||
|
||
return result;
|
||
}
|
||
|
||
get currentProfile() {
|
||
return this.$store.state.reader.currentProfile;
|
||
}
|
||
|
||
set currentProfile(newValue) {
|
||
this.commit('reader/setCurrentProfile', newValue);
|
||
}
|
||
|
||
get partialStorageKey() {
|
||
return this.serverStorageKey.substr(0, 7) + '***';
|
||
}
|
||
|
||
get serverStorageKey() {
|
||
return this.$store.state.reader.serverStorageKey;
|
||
}
|
||
|
||
get setStorageKeyLink() {
|
||
return `https://${window.location.host}/#/reader?setStorageAccessKey=${utils.toBase58(this.serverStorageKey)}`;
|
||
}
|
||
|
||
get predefineTextColors() {
|
||
return defPalette.concat([
|
||
'#ffffff',
|
||
'#000000',
|
||
'#202020',
|
||
'#323232',
|
||
'#aaaaaa',
|
||
'#00c0c0',
|
||
'#ebe2c9',
|
||
'#cfdc99',
|
||
'#478355',
|
||
'#909080',
|
||
]);
|
||
}
|
||
|
||
get predefineBackgroundColors() {
|
||
return defPalette.concat([
|
||
'#ffffff',
|
||
'#000000',
|
||
'#202020',
|
||
'#ebe2c9',
|
||
'#cfdc99',
|
||
'#478355',
|
||
'#a6caf0',
|
||
'#909080',
|
||
'#808080',
|
||
'#c8c8c8',
|
||
]);
|
||
}
|
||
|
||
colorPanStyle(type) {
|
||
let result = 'width: 30px; height: 30px; border: 1px solid black; border-radius: 4px;';
|
||
switch (type) {
|
||
case 'text':
|
||
result += `background-color: ${this.textColor};`
|
||
break;
|
||
case 'bg':
|
||
result += `background-color: ${this.backgroundColor};`
|
||
break;
|
||
case 'div':
|
||
result += `background-color: ${this.dualDivColor};`
|
||
break;
|
||
case 'statusbar':
|
||
result += `background-color: ${this.statusBarColor};`
|
||
break;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
needReload() {
|
||
this.$root.notify.warning('Необходимо обновить страницу (F5), чтобы изменения возымели эффект');
|
||
}
|
||
|
||
needTextReload() {
|
||
this.$root.notify.warning('Необходимо обновить книгу в обход кэша, чтобы изменения возымели эффект');
|
||
}
|
||
|
||
close() {
|
||
this.$emit('do-action', {action: 'settings'});
|
||
}
|
||
|
||
async setDefaults() {
|
||
try {
|
||
if (await this.$root.stdDialog.confirm('Подтвердите установку настроек по умолчанию:', ' ')) {
|
||
this.form = Object.assign({}, rstore.settingDefaults);
|
||
for (let prop in rstore.settingDefaults) {
|
||
this[prop] = this.form[prop];
|
||
}
|
||
}
|
||
} catch (e) {
|
||
//
|
||
}
|
||
}
|
||
|
||
async addProfile() {
|
||
try {
|
||
if (Object.keys(this.profiles).length >= 100) {
|
||
this.$root.stdDialog.alert('Достигнут предел количества профилей', 'Ошибка');
|
||
return;
|
||
}
|
||
const result = await this.$root.stdDialog.prompt('Введите произвольное название для профиля устройства:', ' ', {
|
||
inputValidator: (str) => { if (!str) return 'Название не должно быть пустым'; else if (str.length > 50) return 'Слишком длинное название'; else return true; },
|
||
});
|
||
if (result && result.value) {
|
||
if (this.profiles[result.value]) {
|
||
this.$root.stdDialog.alert('Такой профиль уже существует', 'Ошибка');
|
||
} else {
|
||
const newProfiles = Object.assign({}, this.profiles, {[result.value]: 1});
|
||
this.commit('reader/setAllowProfilesSave', true);
|
||
await this.$nextTick();//ждем обработчики watch
|
||
this.commit('reader/setProfiles', newProfiles);
|
||
await this.$nextTick();//ждем обработчики watch
|
||
this.commit('reader/setAllowProfilesSave', false);
|
||
this.currentProfile = result.value;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
//
|
||
}
|
||
}
|
||
|
||
async delProfile() {
|
||
if (!this.currentProfile)
|
||
return;
|
||
|
||
try {
|
||
const result = await this.$root.stdDialog.prompt(`<b>Предупреждение!</b> Удаление профиля '${this.$root.sanitize(this.currentProfile)}' необратимо.` +
|
||
`<br>Все настройки профиля будут потеряны, однако список читаемых книг сохранится.` +
|
||
`<br><br>Введите 'да' для подтверждения удаления:`, ' ', {
|
||
inputValidator: (str) => { if (str && str.toLowerCase() === 'да') return true; else return 'Удаление не подтверждено'; },
|
||
});
|
||
|
||
if (result && result.value && result.value.toLowerCase() == 'да') {
|
||
if (this.profiles[this.currentProfile]) {
|
||
const newProfiles = Object.assign({}, this.profiles);
|
||
delete newProfiles[this.currentProfile];
|
||
this.commit('reader/setAllowProfilesSave', true);
|
||
await this.$nextTick();//ждем обработчики watch
|
||
this.commit('reader/setProfiles', newProfiles);
|
||
await this.$nextTick();//ждем обработчики watch
|
||
this.commit('reader/setAllowProfilesSave', false);
|
||
this.currentProfile = '';
|
||
}
|
||
}
|
||
} catch (e) {
|
||
//
|
||
}
|
||
}
|
||
|
||
async delAllProfiles() {
|
||
if (!Object.keys(this.profiles).length)
|
||
return;
|
||
|
||
try {
|
||
const result = await this.$root.stdDialog.prompt(`<b>Предупреждение!</b> Удаление ВСЕХ профилей с настройками необратимо.` +
|
||
`<br><br>Введите 'да' для подтверждения удаления:`, ' ', {
|
||
inputValidator: (str) => { if (str && str.toLowerCase() === 'да') return true; else return 'Удаление не подтверждено'; },
|
||
});
|
||
|
||
if (result && result.value && result.value.toLowerCase() == 'да') {
|
||
this.commit('reader/setAllowProfilesSave', true);
|
||
await this.$nextTick();//ждем обработчики watch
|
||
this.commit('reader/setProfiles', {});
|
||
await this.$nextTick();//ждем обработчики watch
|
||
this.commit('reader/setAllowProfilesSave', false);
|
||
this.currentProfile = '';
|
||
}
|
||
} catch (e) {
|
||
//
|
||
}
|
||
}
|
||
|
||
async copyToClip(text, prefix) {
|
||
const result = await utils.copyTextToClipboard(text);
|
||
const suf = (prefix.substr(-1) == 'а' ? 'а' : '');
|
||
const msg = (result ? `${prefix} успешно скопирован${suf} в буфер обмена` : 'Копирование не удалось');
|
||
if (result)
|
||
this.$root.notify.success(msg);
|
||
else
|
||
this.$root.notify.error(msg);
|
||
}
|
||
|
||
async showServerStorageKey() {
|
||
this.serverStorageKeyVisible = !this.serverStorageKeyVisible;
|
||
}
|
||
|
||
async enterServerStorageKey(key) {
|
||
try {
|
||
const result = await this.$root.stdDialog.prompt(`<b>Предупреждение!</b> Изменение ключа доступа приведет к замене всех профилей и читаемых книг в читалке.` +
|
||
`<br><br>Введите новый ключ доступа:`, ' ', {
|
||
inputValidator: (str) => {
|
||
try {
|
||
if (str && utils.fromBase58(str).length == 32) {
|
||
return true;
|
||
}
|
||
} catch (e) {
|
||
//
|
||
}
|
||
return 'Неверный формат ключа';
|
||
},
|
||
inputValue: (key && _.isString(key) ? key : null),
|
||
});
|
||
|
||
if (result && result.value && utils.fromBase58(result.value).length == 32) {
|
||
this.commit('reader/setServerStorageKey', result.value);
|
||
}
|
||
} catch (e) {
|
||
//
|
||
}
|
||
}
|
||
|
||
async generateServerStorageKey() {
|
||
try {
|
||
const result = await this.$root.stdDialog.prompt(`<b>Предупреждение!</b> Генерация нового ключа доступа приведет к удалению всех профилей и читаемых книг в читалке.` +
|
||
`<br><br>Введите 'да' для подтверждения генерации нового ключа:`, ' ', {
|
||
inputValidator: (str) => { if (str && str.toLowerCase() === 'да') return true; else return 'Генерация не подтверждена'; },
|
||
});
|
||
|
||
if (result && result.value && result.value.toLowerCase() == 'да') {
|
||
if (this.$root.generateNewServerStorageKey)
|
||
this.$root.generateNewServerStorageKey();
|
||
}
|
||
} catch (e) {
|
||
//
|
||
}
|
||
|
||
}
|
||
|
||
loadWallpaperFileClick() {
|
||
this.$refs.file.click();
|
||
}
|
||
|
||
loadWallpaperFile() {
|
||
const file = this.$refs.file.files[0];
|
||
if (file.size > 10*1024*1024) {
|
||
this.$root.stdDialog.alert('Файл обоев не должен превышать в размере 10Mb', 'Ошибка');
|
||
return;
|
||
}
|
||
|
||
if (file.type != 'image/png' && file.type != 'image/jpeg') {
|
||
this.$root.stdDialog.alert('Файл обоев должен иметь тип PNG или JPEG', 'Ошибка');
|
||
return;
|
||
}
|
||
|
||
if (this.userWallpapers.length >= 100) {
|
||
this.$root.stdDialog.alert('Превышено максимальное количество пользовательских обоев.', 'Ошибка');
|
||
return;
|
||
}
|
||
|
||
this.$refs.file.value = '';
|
||
if (file) {
|
||
const reader = new FileReader();
|
||
|
||
reader.onload = (e) => {
|
||
(async() => {
|
||
const data = e.target.result;
|
||
const key = utils.toHex(cryptoUtils.sha256(data));
|
||
const label = `#${key.substring(0, 4)}`;
|
||
const cssClass = `user-paper${key}`;
|
||
|
||
const newUserWallpapers = _.cloneDeep(this.userWallpapers);
|
||
const index = _.findIndex(newUserWallpapers, (item) => (item.cssClass == cssClass));
|
||
|
||
if (index < 0)
|
||
newUserWallpapers.push({label, cssClass});
|
||
if (!wallpaperStorage.keyExists(cssClass))
|
||
await wallpaperStorage.setData(cssClass, data);
|
||
|
||
this.userWallpapers = newUserWallpapers;
|
||
this.wallpaper = cssClass;
|
||
})();
|
||
}
|
||
|
||
reader.readAsDataURL(file);
|
||
}
|
||
}
|
||
|
||
async delWallpaper() {
|
||
if (this.wallpaper.indexOf('user-paper') == 0) {
|
||
const newUserWallpapers = [];
|
||
for (const wp of this.userWallpapers) {
|
||
if (wp.cssClass != this.wallpaper) {
|
||
newUserWallpapers.push(wp);
|
||
}
|
||
}
|
||
|
||
await wallpaperStorage.removeData(this.wallpaper);
|
||
|
||
this.userWallpapers = newUserWallpapers;
|
||
this.wallpaper = '';
|
||
}
|
||
}
|
||
|
||
keyHook(event) {
|
||
if (!this.$root.stdDialog.active && event.type == 'keydown' && event.key == 'Escape') {
|
||
this.close();
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
|
||
export default vueComponent(SettingsPage);
|
||
//-----------------------------------------------------------------------------
|
||
</script>
|
||
|
||
<style scoped>
|
||
.tab {
|
||
justify-content: initial;
|
||
}
|
||
|
||
.tab-panel {
|
||
overflow-x: hidden;
|
||
overflow-y: auto;
|
||
font-size: 90%;
|
||
padding: 0 10px 15px 10px;
|
||
}
|
||
|
||
.part-header {
|
||
border-top: 2px solid #bbbbbb;
|
||
font-weight: bold;
|
||
font-size: 110%;
|
||
margin-top: 15px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.item {
|
||
width: 100%;
|
||
margin-top: 5px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.label-1, .label-7 {
|
||
width: 75px;
|
||
}
|
||
|
||
.label-2, .label-3, .label-4, .label-5 {
|
||
width: 110px;
|
||
}
|
||
|
||
.label-6 {
|
||
width: 100px;
|
||
}
|
||
|
||
.label-1, .label-2, .label-3, .label-4, .label-5, .label-6, .label-7 {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
text-align: right;
|
||
margin-right: 10px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.text {
|
||
font-size: 90%;
|
||
line-height: 130%;
|
||
}
|
||
|
||
.button {
|
||
margin: 3px 15px 3px 0;
|
||
padding: 0 5px 0 5px;
|
||
}
|
||
|
||
.copy-icon {
|
||
margin-left: 5px;
|
||
cursor: pointer;
|
||
font-size: 120%;
|
||
color: blue;
|
||
}
|
||
|
||
.input {
|
||
max-width: 150px;
|
||
}
|
||
|
||
.no-mp {
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
.col-left {
|
||
width: 150px;
|
||
}
|
||
</style>
|