Few fixes

This commit is contained in:
Benoit Schweblin 2017-07-28 21:04:12 +01:00
parent 8f743dd1b5
commit c07fc7135e
17 changed files with 376 additions and 298 deletions

View File

@ -20,93 +20,5 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
@import 'common/variables.scss'; @import 'common/app';
@import 'common/markdownHighlighting';
@import 'common/prism';
@import 'common/flex';
@import 'common/base';
body {
background-color: #f3f3f3;
top: 0;
right: 0;
bottom: 0;
left: 0;
position: fixed;
tab-size: 4;
text-rendering: auto;
}
* {
box-sizing: border-box;
}
:focus {
outline: none;
}
.icon {
* {
fill: currentColor;
}
}
button,
input,
select,
textarea {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
.text-input {
display: block;
width: 100%;
height: 36px;
padding: 3px 12px;
font-size: 22px;
line-height: 1.5;
color: inherit;
background-color: #fff;
background-image: none;
border: 0;
border-radius: $border-radius-base;
}
.button {
color: #333;
background-color: transparent;
display: inline-block;
height: 36px;
padding: 3px 12px;
margin-bottom: 0;
font-size: 22px;
font-weight: 400;
line-height: 1.4;
overflow: hidden;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-image: none;
border: 0;
border-radius: $border-radius-base;
&:focus {
color: #333;
background-color: transparent;
&:active,
& {
outline: 0;
}
}
}
</style> </style>

View File

@ -34,7 +34,6 @@ export default {
'toggleNavigationBar', 'toggleNavigationBar',
'toggleEditor', 'toggleEditor',
'toggleSidePreview', 'toggleSidePreview',
'toggleSideBar',
'toggleStatusBar', 'toggleStatusBar',
]), ]),
}; };

View File

@ -23,7 +23,6 @@ export default {
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
background-color: #fff;
} }
.editor__inner { .editor__inner {

View File

@ -0,0 +1,18 @@
<template>
<div class="explorer">
<div class="side-title">
<div class="side-title__text">
Explorer
</div>
</div>
</div>
</template>
<script>
export default {
};
</script>
<style lang="scss">
@import 'common/variables.scss';
</style>

View File

@ -0,0 +1,13 @@
<template>
<div class="explorer-item">
</div>
</template>
<script>
export default {
};
</script>
<style lang="scss">
@import 'common/variables.scss';
</style>

View File

@ -1,38 +1,42 @@
<template> <template>
<div class="layout"> <div class="layout">
<div class="layout__panel layout__panel--inner-1" :style="{ top: inner1Y + 'px', height: inner1Height + 'px' }"> <div class="layout__panel flex flex--row">
<div class="layout__panel layout__panel--inner-2" :style="{ height: inner2Height + 'px' }"> <div class="layout__panel layout__panel--explorer" v-show="showExplorer" :style="{ width: explorerWidth + 'px' }">
<div class="layout__panel layout__panel--inner-3" :style="{ left: inner3X + 'px', width: inner3Width + 'px' }"> <explorer></explorer>
<div class="layout__panel layout__panel--button-bar"> </div>
<div class="layout__panel flex flex--column" :style="{ width: innerWidth + 'px' }">
<div class="layout__panel layout__panel--navigation-bar" v-show="showNavigationBar || !showEditor" :style="{ height: navigationBarHeight + 'px' }">
<navigation-bar></navigation-bar>
</div>
<div class="layout__panel flex flex--row" :style="{ height: innerHeight + 'px' }">
<div class="layout__panel layout__panel--editor" v-show="showEditor" :style="{ width: editorWidth + 'px', 'font-size': fontSize + 'px' }">
<editor></editor>
</div>
<div class="layout__panel layout__panel--button-bar" v-show="showEditor" :style="{ width: buttonBarWidth + 'px' }">
<button-bar></button-bar> <button-bar></button-bar>
</div> </div>
<div class="layout__panel layout__panel--preview" v-show="showSidePreview || !showEditor" :style="{ width: previewWidth + 'px', 'font-size': fontSize + 'px' }"> <div class="layout__panel layout__panel--preview" v-show="showSidePreview || !showEditor" :style="{ width: previewWidth + 'px', 'font-size': fontSize + 'px' }">
<preview></preview> <preview></preview>
</div> </div>
</div> </div>
<div class="layout__panel layout__panel--editor" v-show="showEditor" :style="{ width: editorWidth + 'px', 'font-size': fontSize + 'px' }"> <div class="layout__panel layout__panel--status-bar" v-show="showStatusBar" :style="{ height: statusBarHeight + 'px' }">
<editor></editor>
</div>
</div>
<div class="layout__panel layout__panel--status-bar" :style="{ top: statusBarY + 'px' }">
<status-bar></status-bar> <status-bar></status-bar>
</div> </div>
</div> </div>
<div class="layout__panel layout__panel--navigation-bar" :style="{ top: navigationBarY + 'px' }"> <div class="layout__panel layout__panel--side-bar" v-show="showSideBar" :style="{ width: sideBarWidth + 'px' }">
<navigation-bar></navigation-bar>
</div>
<div class="layout__panel layout__panel--side-bar" :style="{ left: sideBarX + 'px' }">
<side-bar></side-bar> <side-bar></side-bar>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import NavigationBar from './NavigationBar'; import NavigationBar from './NavigationBar';
import SideBar from './SideBar';
import ButtonBar from './ButtonBar'; import ButtonBar from './ButtonBar';
import StatusBar from './StatusBar'; import StatusBar from './StatusBar';
import Explorer from './Explorer';
import SideBar from './SideBar';
import Editor from './Editor'; import Editor from './Editor';
import Preview from './Preview'; import Preview from './Preview';
import editorSvc from '../services/editorSvc'; import editorSvc from '../services/editorSvc';
@ -41,24 +45,28 @@ import constants from '../services/constants';
export default { export default {
components: { components: {
NavigationBar, NavigationBar,
SideBar,
ButtonBar, ButtonBar,
StatusBar, StatusBar,
Explorer,
SideBar,
Editor, Editor,
Preview, Preview,
}, },
computed: mapState('layout', { computed: mapState('layout', {
explorerWidth: 'explorerWidth',
sideBarWidth: 'sideBarWidth',
navigationBarHeight: 'navigationBarHeight',
buttonBarWidth: 'buttonBarWidth',
statusBarHeight: 'statusBarHeight',
showEditor: 'showEditor', showEditor: 'showEditor',
showSidePreview: 'showSidePreview', showSidePreview: 'showSidePreview',
showNavigationBar: 'showNavigationBar',
showStatusBar: 'showStatusBar',
showSideBar: 'showSideBar',
showExplorer: 'showExplorer',
fontSize: 'fontSize', fontSize: 'fontSize',
inner1Y: 'inner1Y', innerWidth: 'innerWidth',
inner1Height: 'inner1Height', innerHeight: 'innerHeight',
inner2Height: 'inner2Height',
inner3X: 'inner3X',
inner3Width: 'inner3Width',
navigationBarY: 'navigationBarY',
sideBarX: 'sideBarX',
statusBarY: 'statusBarY',
previewWidth: 'previewWidth', previewWidth: 'previewWidth',
editorWidth: 'editorWidth', editorWidth: 'editorWidth',
}), }),
@ -127,49 +135,45 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.layout__panel { .layout {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.layout__panel--inner-1 { .layout__panel {
right: 0; position: relative;
} width: 100%;
height: 100%;
.layout__panel--button-bar { -webkit-flex: none;
/* buttonBarWidth */ flex: none;
width: 30px;
}
.layout__panel--preview {
/* buttonBarWidth */
left: 30px;
}
.layout__panel--status-bar {
/* statusBarHeight */
height: 20px;
background-color: #007acc;
}
.layout__panel--side-bar {
/* sideBarWidth */
width: 280px;
} }
.layout__panel--navigation-bar { .layout__panel--navigation-bar {
/* navigationBarHeight */
height: 44px;
background-color: #2c2c2c; background-color: #2c2c2c;
} }
.layout__panel--status-bar {
background-color: #007acc;
}
.layout__panel--editor {
background-color: #fff;
}
.layout__panel--explorer {
background-color: #ddd;
}
.layout__panel--button-bar, .layout__panel--button-bar,
.layout__panel--status-bar, .layout__panel--status-bar,
.layout__panel--side-bar, .layout__panel--side-bar,
.layout__panel--navigation-bar { .layout__panel--navigation-bar {
.app--loading & > * { .app--loading & > * {
display: none !important; opacity: 0.5;
/* Hack to disable mouse focus */
pointer-events: none;
} }
} }
</style> </style>

View File

@ -1,13 +1,24 @@
<template> <template>
<div class="navigation-bar" v-bind:class="{'navigation-bar--editor': showEditor}"> <div class="navigation-bar" v-bind:class="{'navigation-bar--editor': showEditor}">
<div class="navigation-bar__inner navigation-bar__inner--left navigation-bar__inner--button">
<button class="navigation-bar__button button" @click="toggleExplorer()">
<icon-menu></icon-menu>
</button>
</div>
<div class="navigation-bar__inner navigation-bar__inner--right navigation-bar__inner--button">
<button class="navigation-bar__button button" @click="toggleExplorer()">
<icon-settings></icon-settings>
</button>
</div>
<div class="navigation-bar__inner navigation-bar__inner--right flex flex--row"> <div class="navigation-bar__inner navigation-bar__inner--right flex flex--row">
<div class="navigation-bar__spinner"> <div class="navigation-bar__spinner">
<div class="spinner"></div> <div class="spinner"></div>
</div> </div>
<div class="navigation-bar__title navigation-bar__title--text text-input" v-bind:style="{maxWidth: titleMaxWidth + 'px'}"></div> <div class="navigation-bar__title navigation-bar__title--fake text-input"></div>
<input class="navigation-bar__title navigation-bar__title--input text-input" v-bind:class="{'navigation-bar__title--focus': titleFocus, 'navigation-bar__title--scrolling': titleScrolling}" v-bind:style="{maxWidth: titleMaxWidth + 'px'}" @focus="editTitle(true)" @blur="editTitle(false)" @keyup.enter="submitTitle()" @keyup.esc="submitTitle(true)" v-model.lazy.trim="title"> <div class="navigation-bar__title navigation-bar__title--text text-input" v-bind:style="{maxWidth: titleMaxWidth + 'px'}">{{title}}</div>
<input class="navigation-bar__title navigation-bar__title--input text-input" v-bind:class="{'navigation-bar__title--focus': titleFocus, 'navigation-bar__title--scrolling': titleScrolling}" v-bind:style="{width: titleWidth + 'px'}" @focus="editTitle(true)" @blur="editTitle(false)" @keyup.enter="submitTitle()" @keyup.esc="submitTitle(true)" v-on:mouseenter="titleHover = true" v-on:mouseleave="titleHover = false" v-model="title">
</div> </div>
<div class="navigation-bar__inner navigation-bar__inner--left"> <div class="navigation-bar__inner navigation-bar__inner--edit-buttons">
<button class="navigation-bar__button button" @click="pagedownClick('bold')"> <button class="navigation-bar__button button" @click="pagedownClick('bold')">
<icon-format-bold></icon-format-bold> <icon-format-bold></icon-format-bold>
</button> </button>
@ -49,12 +60,14 @@
</template> </template>
<script> <script>
import { mapState } from 'vuex'; import { mapState, mapActions } from 'vuex';
import editorSvc from '../services/editorSvc'; import editorSvc from '../services/editorSvc';
import animationSvc from '../services/animationSvc'; import animationSvc from '../services/animationSvc';
export default { export default {
data: () => ({ data: () => ({
mounted: false,
title: '',
titleFocus: false, titleFocus: false,
titleHover: false, titleHover: false,
}), }),
@ -63,13 +76,15 @@ export default {
showEditor: 'showEditor', showEditor: 'showEditor',
titleMaxWidth: 'titleMaxWidth', titleMaxWidth: 'titleMaxWidth',
}), }),
title: { titleWidth() {
get() { if (!this.mounted) {
return this.$store.getters['files/current'].name; return 0;
}, }
set(name) { this.titleFakeElt.textContent = this.title;
this.$store.dispatch('files/patchCurrent', { name }); const width = this.titleFakeElt.getBoundingClientRect().width + 1; // 1px for the caret
}, return width < this.titleMaxWidth
? width
: this.titleMaxWidth;
}, },
titleScrolling() { titleScrolling() {
const result = this.titleHover && !this.titleFocus; const result = this.titleHover && !this.titleFocus;
@ -91,6 +106,10 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions('layout', [
'toggleExplorer',
'toggleSideBar',
]),
pagedownClick(name) { pagedownClick(name) {
editorSvc.pagedownEditor.uiManager.doClick(name); editorSvc.pagedownEditor.uiManager.doClick(name);
}, },
@ -98,39 +117,33 @@ export default {
this.titleFocus = toggle; this.titleFocus = toggle;
if (toggle) { if (toggle) {
this.titleInputElt.setSelectionRange(0, this.titleInputElt.value.length); this.titleInputElt.setSelectionRange(0, this.titleInputElt.value.length);
} else {
const title = this.title.trim();
if (title) {
this.$store.dispatch('files/patchCurrent', { name: title });
} else {
this.title = this.$store.getters['files/current'].name;
}
} }
}, },
submitTitle(reset) { submitTitle(reset) {
if (reset) { if (reset) {
this.titleInputElt.value = ''; this.title = '';
} }
this.titleInputElt.blur(); this.titleInputElt.blur();
}, },
}, },
mounted() { created() {
this.titleInputElt = this.$el.querySelector('.navigation-bar__title--input');
const titleTextElt = this.$el.querySelector('.navigation-bar__title--text');
const adjustWidth = () => {
titleTextElt.textContent = this.titleInputElt.value;
const width = titleTextElt.getBoundingClientRect().width + 1; // 1px for the caret
this.titleInputElt.style.width = `${width}px`;
};
this.titleInputElt.addEventListener('keyup', adjustWidth);
this.titleInputElt.addEventListener('input', adjustWidth);
this.$store.watch( this.$store.watch(
() => this.$store.getters['files/current'].name, () => this.$store.getters['files/current'].name,
adjustWidth, { (name) => {
immediate: true, this.title = name;
}); }, { immediate: true });
},
this.titleInputElt.addEventListener('mouseenter', () => { mounted() {
this.titleHover = true; this.titleFakeElt = this.$el.querySelector('.navigation-bar__title--fake');
}); this.titleInputElt = this.$el.querySelector('.navigation-bar__title--input');
this.titleInputElt.addEventListener('mouseleave', () => { this.mounted = true;
this.titleHover = false;
});
}, },
}; };
</script> </script>
@ -142,14 +155,26 @@ export default {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 4px 15px 0; padding-top: 4px;
overflow: hidden; overflow: hidden;
} }
.navigation-bar__inner--left {
float: left;
}
.navigation-bar__inner--right { .navigation-bar__inner--right {
float: right; float: right;
} }
.navigation-bar__inner--button {
margin: 0 4px;
}
.navigation-bar__inner--edit-buttons {
margin-left: 15px;
}
.navigation-bar__button { .navigation-bar__button {
display: inline-block; display: inline-block;
width: 34px; width: 34px;
@ -174,26 +199,29 @@ export default {
} }
} }
.navigation-bar__title--fake {
position: absolute;
left: -9999px;
width: auto;
white-space: pre-wrap;
}
.navigation-bar__title--text { .navigation-bar__title--text {
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
}
.navigation-bar__title--input, .navigation-bar--editor & {
.navigation-bar__inner--left {
display: none; display: none;
} }
.navigation-bar--editor {
.navigation-bar__title--text {
position: absolute;
left: -9999px;
width: auto;
} }
.navigation-bar__title--input, .navigation-bar__title--input,
.navigation-bar__inner--left { .navigation-bar__inner--edit-buttons,
.navigation-bar__inner--button {
display: none;
.navigation-bar--editor & {
display: block; display: block;
} }
} }

View File

@ -0,0 +1,118 @@
@import './variables.scss';
@import './base';
@import './markdownHighlighting';
body {
background-color: #f3f3f3;
top: 0;
right: 0;
bottom: 0;
left: 0;
position: fixed;
tab-size: 4;
text-rendering: auto;
}
* {
box-sizing: border-box;
}
:focus {
outline: none;
}
.icon {
* {
fill: currentColor;
}
}
button,
input,
select,
textarea {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
.text-input {
display: block;
width: 100%;
height: 36px;
padding: 3px 12px;
font-size: 22px;
line-height: 1.5;
color: inherit;
background-color: #fff;
background-image: none;
border: 0;
border-radius: $border-radius-base;
}
.button {
color: #333;
background-color: transparent;
display: inline-block;
height: 36px;
padding: 3px 12px;
margin-bottom: 0;
font-size: 22px;
font-weight: 400;
line-height: 1.4;
overflow: hidden;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-image: none;
border: 0;
border-radius: $border-radius-base;
&:focus {
color: #333;
background-color: transparent;
&:active,
& {
outline: 0;
}
}
}
.flex {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex;
}
.flex--row {
-webkit-box-orient: horizontal;
-webkit-flex-direction: row;
flex-direction: row;
}
.flex--column {
-webkit-box-orient: vertical;
-webkit-flex-direction: column;
flex-direction: column;
}
.side-title {
height: 44px;
line-height: 44px;
padding: 0 10px;
background-color: #ccc;
}
.side-title__text {
text-transform: uppercase;
}

View File

@ -2,6 +2,7 @@
@import '../../node_modules/katex/dist/katex.css'; @import '../../node_modules/katex/dist/katex.css';
@import './variables.scss'; @import './variables.scss';
@import './fonts.scss'; @import './fonts.scss';
@import './prism';
@include normalize(); @include normalize();

View File

@ -1,19 +0,0 @@
.flex {
display: -webkit-box;
display: -webkit-flex;
display: -moz-box;
display: -ms-flexbox;
display: flex;
}
.flex--row {
-webkit-box-orient: horizontal;
-webkit-flex-direction: row;
flex-direction: row;
}
.flex--column {
-webkit-box-orient: vertical;
-webkit-flex-direction: column;
flex-direction: column;
}

View File

@ -127,6 +127,7 @@
.h6 { .h6 {
font-weight: $editor-font-weight-bold; font-weight: $editor-font-weight-bold;
&,
* { * {
line-height: $line-height-title; line-height: $line-height-title;
} }

5
src/icons/Menu.vue Normal file
View File

@ -0,0 +1,5 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" class="icon" width="100%" height="100%" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 3,6L 21,6L 21,8L 3,8L 3,6 Z M 3,11L 21,11L 21,13L 3,13L 3,11 Z M 3,16L 21,16L 21,18L 3,18L 3,16 Z "/>
</svg>
</template>

5
src/icons/Settings.vue Normal file
View File

@ -0,0 +1,5 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" class="icon" width="100%" height="100%" viewBox="0 0 24.00 24.00" enable-background="new 0 0 24.00 24.00" xml:space="preserve">
<path fill="#000000" fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 11.9994,15.498C 10.0664,15.498 8.49939,13.931 8.49939,11.998C 8.49939,10.0651 10.0664,8.49805 11.9994,8.49805C 13.9324,8.49805 15.4994,10.0651 15.4994,11.998C 15.4994,13.931 13.9324,15.498 11.9994,15.498 Z M 19.4284,12.9741C 19.4704,12.6531 19.4984,12.329 19.4984,11.998C 19.4984,11.6671 19.4704,11.343 19.4284,11.022L 21.5414,9.36804C 21.7294,9.21606 21.7844,8.94604 21.6594,8.73004L 19.6594,5.26605C 19.5354,5.05005 19.2734,4.96204 19.0474,5.04907L 16.5584,6.05206C 16.0424,5.65607 15.4774,5.32104 14.8684,5.06903L 14.4934,2.41907C 14.4554,2.18103 14.2484,1.99805 13.9994,1.99805L 9.99939,1.99805C 9.74939,1.99805 9.5434,2.18103 9.5054,2.41907L 9.1304,5.06805C 8.52039,5.32104 7.95538,5.65607 7.43939,6.05206L 4.95139,5.04907C 4.7254,4.96204 4.46338,5.05005 4.33939,5.26605L 2.33939,8.73004C 2.21439,8.94604 2.26938,9.21606 2.4574,9.36804L 4.5694,11.022C 4.5274,11.342 4.49939,11.6671 4.49939,11.998C 4.49939,12.329 4.5274,12.6541 4.5694,12.9741L 2.4574,14.6271C 2.26938,14.78 2.21439,15.05 2.33939,15.2661L 4.33939,18.73C 4.46338,18.946 4.7254,19.0341 4.95139,18.947L 7.4404,17.944C 7.95639,18.34 8.52139,18.675 9.1304,18.9271L 9.5054,21.577C 9.5434,21.8151 9.74939,21.998 9.99939,21.998L 13.9994,21.998C 14.2484,21.998 14.4554,21.8151 14.4934,21.577L 14.8684,18.9271C 15.4764,18.6741 16.0414,18.34 16.5574,17.9431L 19.0474,18.947C 19.2734,19.0341 19.5354,18.946 19.6594,18.73L 21.6594,15.2661C 21.7844,15.05 21.7294,14.78 21.5414,14.6271L 19.4284,12.9741 Z "/>
</svg>
</template>

View File

@ -15,6 +15,8 @@ import StatusBar from './StatusBar';
import NavigationBar from './NavigationBar'; import NavigationBar from './NavigationBar';
import SidePreview from './SidePreview'; import SidePreview from './SidePreview';
import Eye from './Eye'; import Eye from './Eye';
import Menu from './Menu';
import Settings from './Settings';
Vue.component('iconFormatBold', FormatBold); Vue.component('iconFormatBold', FormatBold);
Vue.component('iconFormatItalic', FormatItalic); Vue.component('iconFormatItalic', FormatItalic);
@ -32,3 +34,5 @@ Vue.component('iconStatusBar', StatusBar);
Vue.component('iconNavigationBar', NavigationBar); Vue.component('iconNavigationBar', NavigationBar);
Vue.component('iconSidePreview', SidePreview); Vue.component('iconSidePreview', SidePreview);
Vue.component('iconEye', Eye); Vue.component('iconEye', Eye);
Vue.component('iconMenu', Menu);
Vue.component('iconSettings', Settings);

View File

@ -21,7 +21,7 @@ function getStorePrefixFromType(type) {
return store.state[prefix] && prefix; return store.state[prefix] && prefix;
} }
const deletedMarkerMaxAge = 1000; const deleteMarkerMaxAge = 1000;
class Connection { class Connection {
constructor() { constructor() {
@ -67,6 +67,9 @@ class Connection {
}; };
} }
/**
* Create a connection asynchronously.
*/
createTx(cb) { createTx(cb) {
if (!this.db) { if (!this.db) {
this.getTxCbs.push(cb); this.getTxCbs.push(cb);
@ -78,10 +81,13 @@ class Connection {
window.location.reload(); window.location.reload();
return; return;
} }
// Open transaction in read/write will prevent conflict with other tabs
const tx = this.db.transaction(this.db.objectStoreNames, 'readwrite'); const tx = this.db.transaction(this.db.objectStoreNames, 'readwrite');
tx.onerror = (evt) => { tx.onerror = (evt) => {
dbg('Rollback transaction', evt); dbg('Rollback transaction', evt);
}; };
// Read the current txCounter
const dbStore = tx.objectStore(dbStoreName); const dbStore = tx.objectStore(dbStoreName);
const request = dbStore.get('txCounter'); const request = dbStore.get('txCounter');
request.onsuccess = () => { request.onsuccess = () => {
@ -101,6 +107,11 @@ export default {
updatedMap: Object.create(null), updatedMap: Object.create(null),
connection: new Connection(), connection: new Connection(),
/**
* Return a promise that is resolved once the synchronization between the store and the localDb
* is finished. Effectively, open a transaction, then read and apply all changes from the DB
* since previous transaction, then write all changes from the store.
*/
sync() { sync() {
return new Promise((resolve) => { return new Promise((resolve) => {
const storeItemMap = {}; const storeItemMap = {};
@ -117,11 +128,14 @@ export default {
}); });
}, },
/**
* Read and apply all changes from the DB since previous transaction.
*/
readAll(storeItemMap, tx, cb) { readAll(storeItemMap, tx, cb) {
let resetMap; let resetMap;
// We may have missed some deleted markers // We may have missed some delete markers
if (this.lastTx && tx.txCounter - this.lastTx > deletedMarkerMaxAge) { if (this.lastTx && tx.txCounter - this.lastTx > deleteMarkerMaxAge) {
// Delete all dirty store items (user was asleep anyway...) // Delete all dirty store items (user was asleep anyway...)
resetMap = true; resetMap = true;
// And retrieve everything from DB // And retrieve everything from DB
@ -138,8 +152,8 @@ export default {
if (cursor) { if (cursor) {
const item = cursor.value; const item = cursor.value;
items.push(item); items.push(item);
// Remove old deleted markers // Remove old delete markers
if (!item.updated && tx.txCounter - item.tx > deletedMarkerMaxAge) { if (!item.updated && tx.txCounter - item.tx > deleteMarkerMaxAge) {
itemsToDelete.push(item); itemsToDelete.push(item);
} }
cursor.continue(); cursor.continue();
@ -162,30 +176,28 @@ export default {
}; };
}, },
/**
* Write all changes from the store since previous transaction.
*/
writeAll(storeItemMap, tx) { writeAll(storeItemMap, tx) {
this.lastTx = tx.txCounter; this.lastTx = tx.txCounter;
const dbStore = tx.objectStore(dbStoreName); const dbStore = tx.objectStore(dbStoreName);
// Remove deleted store items // Remove deleted store items
const storedIds = Object.keys(this.updatedMap); Object.keys(this.updatedMap).forEach((id) => {
const storedIdsLen = storedIds.length;
for (let i = 0; i < storedIdsLen; i += 1) {
const id = storedIds[i];
if (!storeItemMap[id]) { if (!storeItemMap[id]) {
// Put a deleted marker to notify other tabs // Put a delete marker to notify other tabs
dbStore.put({ dbStore.put({
id, id,
tx: this.lastTx, tx: this.lastTx,
}); });
delete this.updatedMap[id]; delete this.updatedMap[id];
} }
} });
// Put changes // Put changes
const storeItemIds = Object.keys(storeItemMap); Object.keys(storeItemMap).forEach((id) => {
const storeItemIdsLen = storeItemIds.length; const storeItem = storeItemMap[id];
for (let i = 0; i < storeItemIdsLen; i += 1) {
const storeItem = storeItemMap[storeItemIds[i]];
// Store object has changed // Store object has changed
if (this.updatedMap[storeItem.id] !== storeItem.updated) { if (this.updatedMap[storeItem.id] !== storeItem.updated) {
const item = { const item = {
@ -196,9 +208,12 @@ export default {
dbStore.put(item); dbStore.put(item);
this.updatedMap[item.id] = item.updated; this.updatedMap[item.id] = item.updated;
} }
} });
}, },
/**
* Read and apply one DB change.
*/
readDbItem(dbItem, storeItemMap) { readDbItem(dbItem, storeItemMap) {
const existingStoreItem = storeItemMap[dbItem.id]; const existingStoreItem = storeItemMap[dbItem.id];
if (!dbItem.updated) { if (!dbItem.updated) {

View File

@ -38,8 +38,8 @@ store.watch(
return store.state.files.itemMap[fileId]; return store.state.files.itemMap[fileId];
})) }))
.then((currentFile) => { .then((currentFile) => {
store.commit('files/patchItem', { id: currentFile.id });
store.commit('files/setCurrentId', currentFile.id); store.commit('files/setCurrentId', currentFile.id);
store.dispatch('files/patchCurrent', {}); // Update `updated` field to make it the mostRecent
}), }),
{ {
immediate: true, immediate: true,

View File

@ -1,12 +1,7 @@
const navigationBarHeight = 44;
const sideBarWidth = 280;
const editorMinWidth = 280; const editorMinWidth = 280;
const buttonBarWidth = 30;
const statusBarHeight = 20;
const outOfScreenMargin = 50;
const minPadding = 20; const minPadding = 20;
const navigationBarSpaceWidth = 30; const navigationBarSpaceWidth = 30;
const navigationBarLeftWidth = 500; const navigationBarLeftWidth = 570;
const maxTitleMaxWidth = 800; const maxTitleMaxWidth = 800;
const minTitleMaxWidth = 200; const minTitleMaxWidth = 200;
@ -22,24 +17,25 @@ const toggler = (propertyName, setterName) => ({ state, commit, dispatch }, show
export default { export default {
namespaced: true, namespaced: true,
state: { state: {
// Constants
explorerWidth: 280,
sideBarWidth: 280,
navigationBarHeight: 44,
buttonBarWidth: 30,
statusBarHeight: 20,
// Configuration // Configuration
showNavigationBar: true, showNavigationBar: true,
showEditor: true, showEditor: true,
showSidePreview: true, showSidePreview: true,
showSideBar: false,
showStatusBar: true, showStatusBar: true,
showSideBar: false,
showExplorer: false,
editorWidthFactor: 1, editorWidthFactor: 1,
fontSizeFactor: 1, fontSizeFactor: 1,
// Style // Style
fontSize: 0, fontSize: 0,
inner1Y: 0, innerWidth: 0,
inner1Height: 0, innerHeight: 0,
inner2Height: 0,
inner3X: 0,
inner3Width: 0,
navigationBarY: 0,
sideBarX: 0,
statusBarY: 0,
editorWidth: 0, editorWidth: 0,
editorPadding: 0, editorPadding: 0,
previewWidth: 0, previewWidth: 0,
@ -50,19 +46,14 @@ export default {
setShowNavigationBar: setter('showNavigationBar'), setShowNavigationBar: setter('showNavigationBar'),
setShowEditor: setter('showEditor'), setShowEditor: setter('showEditor'),
setShowSidePreview: setter('showSidePreview'), setShowSidePreview: setter('showSidePreview'),
setShowSideBar: setter('showSideBar'),
setShowStatusBar: setter('showStatusBar'), setShowStatusBar: setter('showStatusBar'),
setShowSideBar: setter('showSideBar'),
setShowExplorer: setter('showExplorer'),
setEditorWidthFactor: setter('editorWidthFactor'), setEditorWidthFactor: setter('editorWidthFactor'),
setFontSizeFactor: setter('fontSizeFactor'), setFontSizeFactor: setter('fontSizeFactor'),
setFontSize: setter('fontSize'), setFontSize: setter('fontSize'),
setInner1Y: setter('inner1Y'), setInnerWidth: setter('innerWidth'),
setInner1Height: setter('inner1Height'), setInnerHeight: setter('innerHeight'),
setInner2Height: setter('inner2Height'),
setInner3X: setter('inner3X'),
setInner3Width: setter('inner3Width'),
setNavigationBarY: setter('navigationBarY'),
setSideBarX: setter('sideBarX'),
setStatusBarY: setter('statusBarY'),
setEditorWidth: setter('editorWidth'), setEditorWidth: setter('editorWidth'),
setEditorPadding: setter('editorPadding'), setEditorPadding: setter('editorPadding'),
setPreviewWidth: setter('previewWidth'), setPreviewWidth: setter('previewWidth'),
@ -73,35 +64,39 @@ export default {
toggleNavigationBar: toggler('showNavigationBar', 'setShowNavigationBar'), toggleNavigationBar: toggler('showNavigationBar', 'setShowNavigationBar'),
toggleEditor: toggler('showEditor', 'setShowEditor'), toggleEditor: toggler('showEditor', 'setShowEditor'),
toggleSidePreview: toggler('showSidePreview', 'setShowSidePreview'), toggleSidePreview: toggler('showSidePreview', 'setShowSidePreview'),
toggleSideBar: toggler('showSideBar', 'setShowSideBar'),
toggleStatusBar: toggler('showStatusBar', 'setShowStatusBar'), toggleStatusBar: toggler('showStatusBar', 'setShowStatusBar'),
toggleSideBar: toggler('showSideBar', 'setShowSideBar'),
toggleExplorer: toggler('showExplorer', 'setShowExplorer'),
updateStyle({ state, commit, dispatch }) { updateStyle({ state, commit, dispatch }) {
const bodyWidth = document.body.clientWidth; const bodyWidth = document.body.clientWidth;
const bodyHeight = document.body.clientHeight; const bodyHeight = document.body.clientHeight;
const showNavigationBar = !state.showEditor || state.showNavigationBar; const showNavigationBar = !state.showEditor || state.showNavigationBar;
const inner1Y = showNavigationBar let innerHeight = bodyHeight;
? navigationBarHeight if (showNavigationBar) {
: 0; innerHeight -= state.navigationBarHeight;
const inner1Height = bodyHeight - inner1Y;
const inner2Height = state.showStatusBar
? inner1Height - statusBarHeight
: inner1Height;
const navigationBarY = showNavigationBar
? 0
: -navigationBarHeight - outOfScreenMargin;
const sideBarX = state.showSideBar
? bodyWidth - sideBarWidth
: bodyWidth + outOfScreenMargin;
const statusBarY = state.showStatusBar
? inner2Height
: inner2Height + outOfScreenMargin;
let doublePanelWidth = bodyWidth - buttonBarWidth;
if (state.showSideBar) {
doublePanelWidth -= sideBarWidth;
} }
if (state.showStatusBar) {
innerHeight -= state.statusBarHeight;
}
let innerWidth = bodyWidth;
if (state.showSideBar) {
innerWidth -= state.sideBarWidth;
}
if (state.showExplorer) {
innerWidth -= state.explorerWidth;
}
let doublePanelWidth = innerWidth - state.buttonBarWidth;
if (doublePanelWidth < editorMinWidth) { if (doublePanelWidth < editorMinWidth) {
if (state.showSideBar) {
dispatch('toggleSideBar', false);
return;
}
if (state.showExplorer) {
dispatch('toggleExplorer', false);
return;
}
doublePanelWidth = editorMinWidth; doublePanelWidth = editorMinWidth;
} }
const splitPanel = state.showEditor && state.showSidePreview; const splitPanel = state.showEditor && state.showSidePreview;
@ -109,10 +104,6 @@ export default {
dispatch('toggleSidePreview', false); dispatch('toggleSidePreview', false);
return; return;
} }
if (state.showSideBar && bodyWidth < editorMinWidth + sideBarWidth) {
dispatch('toggleSideBar', false);
return;
}
let fontSize = 18; let fontSize = 18;
let textWidth = 990; let textWidth = 990;
@ -130,32 +121,22 @@ export default {
fontSize *= state.fontSizeFactor; fontSize *= state.fontSizeFactor;
const panelWidth = doublePanelWidth / 2; const panelWidth = doublePanelWidth / 2;
let inner3X = panelWidth; const previewWidth = splitPanel ?
if (!splitPanel) { panelWidth :
inner3X = state.showEditor innerWidth;
? doublePanelWidth
: -buttonBarWidth;
}
const inner3Width = splitPanel
? panelWidth + buttonBarWidth
: doublePanelWidth + buttonBarWidth;
const previewWidth = splitPanel
? panelWidth
: bodyWidth;
let previewPadding = (previewWidth - textWidth) / 2; let previewPadding = (previewWidth - textWidth) / 2;
if (previewPadding < minPadding) { if (previewPadding < minPadding) {
previewPadding = minPadding; previewPadding = minPadding;
} }
const editorWidth = splitPanel const editorWidth = splitPanel ?
? panelWidth panelWidth :
: doublePanelWidth; doublePanelWidth;
let editorPadding = (editorWidth - textWidth) / 2; let editorPadding = (editorWidth - textWidth) / 2;
if (editorPadding < minPadding) { if (editorPadding < minPadding) {
editorPadding = minPadding; editorPadding = minPadding;
} }
let titleMaxWidth = bodyWidth - navigationBarSpaceWidth; let titleMaxWidth = innerWidth - navigationBarSpaceWidth;
if (state.showEditor) { if (state.showEditor) {
titleMaxWidth -= navigationBarLeftWidth; titleMaxWidth -= navigationBarLeftWidth;
} }
@ -163,14 +144,8 @@ export default {
titleMaxWidth = Math.max(titleMaxWidth, minTitleMaxWidth); titleMaxWidth = Math.max(titleMaxWidth, minTitleMaxWidth);
commit('setFontSize', fontSize); commit('setFontSize', fontSize);
commit('setInner1Y', inner1Y); commit('setInnerWidth', innerWidth);
commit('setInner1Height', inner1Height); commit('setInnerHeight', innerHeight);
commit('setInner2Height', inner2Height);
commit('setInner3X', inner3X);
commit('setInner3Width', inner3Width);
commit('setNavigationBarY', navigationBarY);
commit('setSideBarX', sideBarX);
commit('setStatusBarY', statusBarY);
commit('setPreviewWidth', previewWidth); commit('setPreviewWidth', previewWidth);
commit('setPreviewPadding', previewPadding); commit('setPreviewPadding', previewPadding);
commit('setEditorWidth', editorWidth); commit('setEditorWidth', editorWidth);