SettingsPage.vue 18 KB

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