SettingsPage.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. <template>
  2. <Window ref="window" width="600px" @close="close">
  3. <template #header>
  4. Настройки
  5. </template>
  6. <div class="col row">
  7. <a ref="download" style="display: none;" target="_blank"></a>
  8. <div class="full-height">
  9. <q-tabs
  10. ref="tabs"
  11. v-model="selectedTab"
  12. class="bg-grey-3 text-black"
  13. left-icon="la la-caret-up"
  14. right-icon="la la-caret-down"
  15. active-color="white"
  16. active-bg-color="primary"
  17. indicator-color="black"
  18. vertical
  19. no-caps
  20. stretch
  21. inline-label
  22. >
  23. <div v-show="tabsScrollable" class="q-pt-lg" />
  24. <q-tab class="tab" name="profiles" icon="la la-users" label="Профили" />
  25. <q-tab class="tab" name="view" icon="la la-eye" label="Вид" />
  26. <q-tab class="tab" name="toolbar" icon="la la-grip-horizontal" label="Панель" />
  27. <q-tab class="tab" name="keys" icon="la la-gamepad" label="Управление" />
  28. <q-tab class="tab" name="pagemove" icon="la la-school" label="Листание" />
  29. <q-tab class="tab" name="convert" icon="la la-magic" label="Конвертир." />
  30. <q-tab class="tab" name="update" icon="la la-sync" label="Обновление" />
  31. <q-tab class="tab" name="others" icon="la la-list-ul" label="Прочее" />
  32. <q-tab class="tab" name="reset" icon="la la-broom" label="Сброс" />
  33. <div v-show="tabsScrollable" class="q-pt-lg" />
  34. </q-tabs>
  35. </div>
  36. <div class="col fit">
  37. <!-- Профили --------------------------------------------------------------------->
  38. <div v-if="selectedTab == 'profiles'" class="fit tab-panel">
  39. <ProfilesTab :form="form" />
  40. </div>
  41. <!-- Вид ------------------------------------------------------------------------->
  42. <!--div v-if="selectedTab == 'view'" class="fit column">
  43. <q-tabs
  44. v-model="selectedViewTab"
  45. active-color="black"
  46. active-bg-color="white"
  47. indicator-color="white"
  48. dense
  49. no-caps
  50. class="no-mp bg-grey-4 text-grey-7"
  51. >
  52. <q-tab name="mode" label="Режим" />
  53. <q-tab name="color" label="Цвет" />
  54. <q-tab name="font" label="Шрифт" />
  55. <q-tab name="text" label="Текст" />
  56. <q-tab name="status" label="Строка статуса" />
  57. </q-tabs>
  58. <div class="q-mb-sm" />
  59. <div class="col tab-panel">
  60. <div v-if="selectedViewTab == 'mode'">
  61. @@include('./ViewTab/Mode.inc');
  62. </div>
  63. <div v-if="selectedViewTab == 'color'">
  64. @@include('./ViewTab/Color.inc');
  65. </div>
  66. <div v-if="selectedViewTab == 'font'">
  67. @@include('./ViewTab/Font.inc');
  68. </div>
  69. <div v-if="selectedViewTab == 'text'">
  70. @@include('./ViewTab/Text.inc');
  71. </div>
  72. <div v-if="selectedViewTab == 'status'">
  73. @@include('./ViewTab/Status.inc');
  74. </div>
  75. </div>
  76. </div-->
  77. <!-- Кнопки ---------------------------------------------------------------------->
  78. <div v-if="selectedTab == 'toolbar'" class="fit tab-panel">
  79. <ToolBarTab :form="form" />
  80. </div>
  81. <!-- Управление ------------------------------------------------------------------>
  82. <!--div v-if="selectedTab == 'keys'" class="fit column">
  83. @@include('./KeysTab.inc');
  84. </div-->
  85. <!-- Листание -------------------------------------------------------------------->
  86. <!--div v-if="selectedTab == 'pagemove'" class="fit tab-panel">
  87. @@include('./PageMoveTab.inc');
  88. </div-->
  89. <!-- Конвертирование ------------------------------------------------------------->
  90. <!--div v-if="selectedTab == 'convert'" class="fit tab-panel">
  91. @@include('./ConvertTab.inc');
  92. </div-->
  93. <!-- Обновление ------------------------------------------------------------------>
  94. <!--div v-if="selectedTab == 'update'" class="fit tab-panel">
  95. @@include('./UpdateTab.inc');
  96. </div-->
  97. <!-- Прочее ---------------------------------------------------------------------->
  98. <!--div v-if="selectedTab == 'others'" class="fit tab-panel">
  99. @@include('./OthersTab.inc');
  100. </div-->
  101. <!-- Сброс ----------------------------------------------------------------------->
  102. <!--div v-if="selectedTab == 'reset'" class="fit tab-panel">
  103. @@include('./ResetTab.inc');
  104. </div-->
  105. </div>
  106. </div>
  107. </Window>
  108. </template>
  109. <script>
  110. //-----------------------------------------------------------------------------
  111. import vueComponent from '../../vueComponent.js';
  112. import _ from 'lodash';
  113. //stuff
  114. import * as utils from '../../../share/utils';
  115. import * as cryptoUtils from '../../../share/cryptoUtils';
  116. import Window from '../../share/Window.vue';
  117. import NumInput from '../../share/NumInput.vue';
  118. import UserHotKeys from './UserHotKeys/UserHotKeys.vue';
  119. import wallpaperStorage from '../share/wallpaperStorage';
  120. import readerApi from '../../../api/reader';
  121. import rstore from '../../../store/modules/reader';
  122. import defPalette from './defPalette';
  123. //pages
  124. import ProfilesTab from './ProfilesTab/ProfilesTab.vue';
  125. import ToolBarTab from './ToolBarTab/ToolBarTab.vue';
  126. const hex = /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/;
  127. const componentOptions = {
  128. components: {
  129. Window,
  130. NumInput,
  131. UserHotKeys,
  132. //pages
  133. ProfilesTab,
  134. ToolBarTab,
  135. },
  136. watch: {
  137. settings: function() {
  138. this.settingsChanged();//no await
  139. },
  140. form: {
  141. handler(newValue) {
  142. if (this.inited && !this.setsChanged) {
  143. this.commit('reader/setSettings', _.cloneDeep(newValue));
  144. }
  145. },
  146. deep: true,
  147. },
  148. fontBold: function(newValue) {
  149. this.fontWeight = (newValue ? 'bold' : '');
  150. },
  151. fontItalic: function(newValue) {
  152. this.fontStyle = (newValue ? 'italic' : '');
  153. },
  154. vertShift: function(newValue) {
  155. const font = (this.webFontName ? this.webFontName : this.fontName);
  156. if (this.fontShifts[font] != newValue || this.fontVertShift != newValue) {
  157. this.fontShifts = Object.assign({}, this.fontShifts, {[font]: newValue});
  158. this.fontVertShift = newValue;
  159. }
  160. },
  161. fontName: function(newValue) {
  162. const font = (this.webFontName ? this.webFontName : newValue);
  163. this.vertShift = this.fontShifts[font] || 0;
  164. },
  165. webFontName: function(newValue) {
  166. const font = (newValue ? newValue : this.fontName);
  167. this.vertShift = this.fontShifts[font] || 0;
  168. },
  169. wallpaper: function(newValue) {
  170. if (newValue != '' && this.pageChangeAnimation == 'flip')
  171. this.pageChangeAnimation = '';
  172. },
  173. dualPageMode(newValue) {
  174. if (newValue && this.pageChangeAnimation == 'flip' || this.pageChangeAnimation == 'rightShift')
  175. this.pageChangeAnimation = '';
  176. },
  177. textColor: function(newValue) {
  178. this.textColorFiltered = newValue;
  179. },
  180. textColorFiltered: function(newValue) {
  181. if (hex.test(newValue))
  182. this.textColor = newValue;
  183. },
  184. backgroundColor: function(newValue) {
  185. this.bgColorFiltered = newValue;
  186. },
  187. bgColorFiltered: function(newValue) {
  188. if (hex.test(newValue))
  189. this.backgroundColor = newValue;
  190. },
  191. dualDivColor(newValue) {
  192. this.dualDivColorFiltered = newValue;
  193. },
  194. dualDivColorFiltered(newValue) {
  195. if (hex.test(newValue))
  196. this.dualDivColor = newValue;
  197. },
  198. statusBarColor(newValue) {
  199. this.statusBarColorFiltered = newValue;
  200. },
  201. statusBarColorFiltered(newValue) {
  202. if (hex.test(newValue))
  203. this.statusBarColor = newValue;
  204. },
  205. },
  206. };
  207. class SettingsPage {
  208. _options = componentOptions;
  209. form = {};
  210. selectedTab = 'profiles';
  211. selectedViewTab = 'mode';
  212. selectedKeysTab = 'mouse';
  213. fontBold = false;
  214. fontItalic = false;
  215. vertShift = 0;
  216. tabsScrollable = false;
  217. textColorFiltered = '';
  218. bgColorFiltered = '';
  219. dualDivColorFiltered = '';
  220. statusBarColorFiltered = '';
  221. webFonts = [];
  222. fonts = [];
  223. setsChanged = false;
  224. created() {
  225. this.commit = this.$store.commit;
  226. this.reader = this.$store.state.reader;
  227. this.settingsChanged();//no await
  228. }
  229. mounted() {
  230. this.$watch(
  231. '$refs.tabs.scrollable',
  232. (newValue) => {
  233. this.tabsScrollable = newValue && !this.$root.isMobileDevice;
  234. }
  235. );
  236. }
  237. init() {
  238. this.$refs.window.init();
  239. this.inited = true;
  240. }
  241. async settingsChanged() {
  242. if (_.isEqual(this.form, this.settings))
  243. return;
  244. this.setsChanged = true;
  245. try {
  246. this.form = _.cloneDeep(this.settings);
  247. const form = this.form;
  248. this.fontBold = (form.fontWeight == 'bold');
  249. this.fontItalic = (form.fontStyle == 'italic');
  250. this.fonts = rstore.fonts;
  251. this.webFonts = rstore.webFonts;
  252. const font = (form.webFontName ? form.webFontName : form.fontName);
  253. this.vertShift = form.fontShifts[font] || 0;
  254. this.textColorFiltered = form.textColor;
  255. this.bgColorFiltered = form.backgroundColor;
  256. this.dualDivColorFiltered = form.dualDivColor;
  257. this.statusBarColorFiltered = form.statusBarColor;
  258. } finally {
  259. await this.$nextTick();
  260. this.setsChanged = false;
  261. }
  262. }
  263. get mode() {
  264. return this.$store.state.config.mode;
  265. }
  266. get isExternalConverter() {
  267. return this.$store.state.config.useExternalBookConverter;
  268. }
  269. get settings() {
  270. return this.$store.state.reader.settings;
  271. }
  272. get configBucEnabled() {
  273. return this.$store.state.config.bucEnabled;
  274. }
  275. get wallpaperOptions() {
  276. let result = [{label: 'Нет', value: ''}];
  277. const userWallpapers = _.cloneDeep(this.userWallpapers);
  278. userWallpapers.sort((a, b) => a.label.localeCompare(b.label));
  279. for (const wp of userWallpapers) {
  280. if (wallpaperStorage.keyExists(wp.cssClass))
  281. result.push({label: wp.label, value: wp.cssClass});
  282. }
  283. for (let i = 1; i <= 17; i++) {
  284. result.push({label: i, value: `paper${i}`});
  285. }
  286. return result;
  287. }
  288. get fontsOptions() {
  289. let result = [];
  290. this.fonts.forEach(font => {
  291. result.push({label: (font.label ? font.label : font.name), value: font.name});
  292. });
  293. return result;
  294. }
  295. get webFontsOptions() {
  296. let result = [{label: 'Нет', value: ''}];
  297. this.webFonts.forEach(font => {
  298. result.push({label: font.name, value: font.name});
  299. });
  300. return result;
  301. }
  302. get pageChangeAnimationOptions() {
  303. let result = [
  304. {label: 'Нет', value: ''},
  305. {label: 'Вверх-вниз', value: 'downShift'},
  306. (!this.dualPageMode ? {label: 'Вправо-влево', value: 'rightShift'} : null),
  307. {label: 'Протаивание', value: 'thaw'},
  308. {label: 'Мерцание', value: 'blink'},
  309. {label: 'Вращение', value: 'rotate'},
  310. (this.wallpaper == '' && !this.dualPageMode ? {label: 'Листание', value: 'flip'} : null),
  311. ];
  312. result = result.filter(v => v);
  313. return result;
  314. }
  315. get predefineTextColors() {
  316. return defPalette.concat([
  317. '#ffffff',
  318. '#000000',
  319. '#202020',
  320. '#323232',
  321. '#aaaaaa',
  322. '#00c0c0',
  323. '#ebe2c9',
  324. '#cfdc99',
  325. '#478355',
  326. '#909080',
  327. ]);
  328. }
  329. get predefineBackgroundColors() {
  330. return defPalette.concat([
  331. '#ffffff',
  332. '#000000',
  333. '#202020',
  334. '#ebe2c9',
  335. '#cfdc99',
  336. '#478355',
  337. '#a6caf0',
  338. '#909080',
  339. '#808080',
  340. '#c8c8c8',
  341. ]);
  342. }
  343. colorPanStyle(type) {
  344. let result = 'width: 30px; height: 30px; border: 1px solid black; border-radius: 4px;';
  345. switch (type) {
  346. case 'text':
  347. result += `background-color: ${this.textColor};`
  348. break;
  349. case 'bg':
  350. result += `background-color: ${this.backgroundColor};`
  351. break;
  352. case 'div':
  353. result += `background-color: ${this.dualDivColor};`
  354. break;
  355. case 'statusbar':
  356. result += `background-color: ${this.statusBarColor};`
  357. break;
  358. }
  359. return result;
  360. }
  361. needReload() {
  362. this.$root.notify.warning('Необходимо обновить страницу (F5), чтобы изменения возымели эффект');
  363. }
  364. needTextReload() {
  365. this.$root.notify.warning('Необходимо обновить книгу в обход кэша, чтобы изменения возымели эффект');
  366. }
  367. close() {
  368. this.$emit('do-action', {action: 'settings'});
  369. }
  370. async setDefaults() {
  371. try {
  372. if (await this.$root.stdDialog.confirm('Подтвердите установку настроек по умолчанию:', ' ')) {
  373. this.form = Object.assign({}, rstore.settingDefaults);
  374. for (let prop in rstore.settingDefaults) {
  375. this[prop] = this.form[prop];
  376. }
  377. }
  378. } catch (e) {
  379. //
  380. }
  381. }
  382. loadWallpaperFileClick() {
  383. this.$refs.file.click();
  384. }
  385. loadWallpaperFile() {
  386. const file = this.$refs.file.files[0];
  387. if (file.size > 10*1024*1024) {
  388. this.$root.stdDialog.alert('Файл обоев не должен превышать в размере 10Mb', 'Ошибка');
  389. return;
  390. }
  391. if (file.type != 'image/png' && file.type != 'image/jpeg') {
  392. this.$root.stdDialog.alert('Файл обоев должен иметь тип PNG или JPEG', 'Ошибка');
  393. return;
  394. }
  395. if (this.userWallpapers.length >= 100) {
  396. this.$root.stdDialog.alert('Превышено максимальное количество пользовательских обоев.', 'Ошибка');
  397. return;
  398. }
  399. this.$refs.file.value = '';
  400. if (file) {
  401. const reader = new FileReader();
  402. reader.onload = (e) => {
  403. (async() => {
  404. const data = e.target.result;
  405. const key = utils.toHex(cryptoUtils.sha256(data));
  406. const label = `#${key.substring(0, 4)}`;
  407. const cssClass = `user-paper${key}`;
  408. const newUserWallpapers = _.cloneDeep(this.userWallpapers);
  409. const index = _.findIndex(newUserWallpapers, (item) => (item.cssClass == cssClass));
  410. if (index < 0)
  411. newUserWallpapers.push({label, cssClass});
  412. if (!wallpaperStorage.keyExists(cssClass)) {
  413. await wallpaperStorage.setData(cssClass, data);
  414. //отправим data на сервер в файл `/upload/${key}`
  415. try {
  416. //const res =
  417. await readerApi.uploadFileBuf(data);
  418. //console.log(res);
  419. } catch (e) {
  420. console.error(e);
  421. }
  422. }
  423. this.userWallpapers = newUserWallpapers;
  424. this.wallpaper = cssClass;
  425. })();
  426. }
  427. reader.readAsDataURL(file);
  428. }
  429. }
  430. async delWallpaper() {
  431. if (this.wallpaper.indexOf('user-paper') == 0) {
  432. const newUserWallpapers = [];
  433. for (const wp of this.userWallpapers) {
  434. if (wp.cssClass != this.wallpaper) {
  435. newUserWallpapers.push(wp);
  436. }
  437. }
  438. await wallpaperStorage.removeData(this.wallpaper);
  439. this.userWallpapers = newUserWallpapers;
  440. this.wallpaper = '';
  441. }
  442. }
  443. async downloadWallpaper() {
  444. if (this.wallpaper.indexOf('user-paper') != 0)
  445. return;
  446. try {
  447. const d = this.$refs.download;
  448. const dataUrl = await wallpaperStorage.getData(this.wallpaper);
  449. if (!dataUrl)
  450. throw new Error('Файл обоев не найден');
  451. d.href = dataUrl;
  452. d.download = `wallpaper-#${this.wallpaper.replace('user-paper', '').substring(0, 4)}`;
  453. d.click();
  454. } catch (e) {
  455. this.$root.stdDialog.alert(e.message, 'Ошибка', {color: 'negative'});
  456. }
  457. }
  458. keyHook(event) {
  459. if (!this.$root.stdDialog.active && event.type == 'keydown' && event.key == 'Escape') {
  460. this.close();
  461. }
  462. return true;
  463. }
  464. }
  465. export default vueComponent(SettingsPage);
  466. //-----------------------------------------------------------------------------
  467. </script>
  468. <style scoped>
  469. .tab {
  470. justify-content: initial;
  471. }
  472. .tab-panel {
  473. overflow-x: hidden;
  474. overflow-y: auto;
  475. font-size: 90%;
  476. padding: 0 10px 15px 10px;
  477. }
  478. .label-7 {
  479. width: 75px;
  480. }
  481. .label-2, .label-4, .label-5 {
  482. width: 110px;
  483. }
  484. .label-6 {
  485. width: 100px;
  486. }
  487. .input {
  488. max-width: 150px;
  489. }
  490. .no-mp {
  491. margin: 0;
  492. padding: 0;
  493. }
  494. .col-left {
  495. width: 150px;
  496. }
  497. </style>
  498. <style>
  499. .sets-part-header {
  500. border-top: 2px solid #bbbbbb;
  501. font-weight: bold;
  502. font-size: 110%;
  503. margin-top: 15px;
  504. margin-bottom: 5px;
  505. }
  506. .sets-label {
  507. display: flex;
  508. flex-direction: column;
  509. justify-content: center;
  510. text-align: right;
  511. margin-right: 10px;
  512. overflow: hidden;
  513. }
  514. .sets-item {
  515. width: 100%;
  516. margin-top: 5px;
  517. margin-bottom: 5px;
  518. }
  519. .sets-button {
  520. margin: 3px 15px 3px 0;
  521. padding: 0 5px 0 5px;
  522. }
  523. </style>