Add text resize feature, and change font of console screen to Hack.

This commit is contained in:
NI
2019-12-12 16:45:55 +08:00
parent aeb339ce49
commit 19473ae161
11 changed files with 250 additions and 41 deletions

View File

@@ -13,10 +13,12 @@ Major dependencies includes:
- [Babel](https://babeljs.io/), Licensed under MIT license - [Babel](https://babeljs.io/), Licensed under MIT license
- [XTerm.js](https://xtermjs.org/), Licensed under MIT license - [XTerm.js](https://xtermjs.org/), Licensed under MIT license
- [normalize.css](https://github.com/necolas/normalize.css), Licensed under MIT license - [normalize.css](https://github.com/necolas/normalize.css), Licensed under MIT license
- [Roboto font](https://en.wikipedia.org/wiki/Roboto), Licensed under Apache license. - [Roboto font](https://en.wikipedia.org/wiki/Roboto), Licensed under Apache license
Packaged by [Christian Hoffmeister](https://github.com/choffmeister/roboto-fontface-bower), Licensed under Apache 2.0 Packaged by [Christian Hoffmeister](https://github.com/choffmeister/roboto-fontface-bower), Licensed under Apache 2.0
- [iconv-lite](https://github.com/ashtuchkin/iconv-lite), Licensed under MIT license - [iconv-lite](https://github.com/ashtuchkin/iconv-lite), Licensed under MIT license
- [buffer](https://github.com/feross/buffer), Licensed under MIT license - [buffer](https://github.com/feross/buffer), Licensed under MIT license
- [Hack font](https://github.com/source-foundry/Hack), [View license](https://github.com/source-foundry/Hack/blob/master/LICENSE.md)
- [fontfaceobserver](https://github.com/bramstein/fontfaceobserver), [View license](https://github.com/bramstein/fontfaceobserver/blob/master/LICENSE)
## For back-end application ## For back-end application

12
package-lock.json generated
View File

@@ -6522,6 +6522,12 @@
} }
} }
}, },
"fontfaceobserver": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.1.0.tgz",
"integrity": "sha512-ReOsO2F66jUa0jmv2nlM/s1MiutJx/srhAe2+TE8dJCMi02ZZOcCTxTCQFr3Yet+uODUtnr4Mewg+tNQ+4V1Ng==",
"dev": true
},
"for-each": { "for-each": {
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@@ -7505,6 +7511,12 @@
"integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
"dev": true "dev": true
}, },
"hack-font": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/hack-font/-/hack-font-3.3.0.tgz",
"integrity": "sha512-RohrcAr3UaKiIoxDlOytCjObcUAucfFc6V5fKu6gBrvmvTfIXeBqZwR0Q5kb9qpbluThJWt326LClLKIGiFyug==",
"dev": true
},
"har-schema": { "har-schema": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",

View File

@@ -22,6 +22,8 @@
"eslint-plugin-prettier": "^3.1.1", "eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-vue": "^5.2.3", "eslint-plugin-vue": "^5.2.3",
"file-loader": "^4.3.0", "file-loader": "^4.3.0",
"fontfaceobserver": "^2.1.0",
"hack-font": "^3.3.0",
"html-loader": "^0.5.5", "html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"iconv-lite": "^0.5.0", "iconv-lite": "^0.5.0",

View File

@@ -285,6 +285,14 @@ body {
box-shadow: 1px 1px 0 2px #0003; box-shadow: 1px 1px 0 2px #0003;
} }
.icon.icon-keyboardkey2 {
background: #fff;
color: #999;
padding: 4px 6px;
display: inline-block;
border-radius: 3px;
}
/* Windows */ /* Windows */
.window { .window {
position: absolute; position: absolute;

View File

@@ -369,6 +369,16 @@
flex-direction: column; flex-direction: column;
color: #fff; color: #fff;
font-size: 1.2em; font-size: 1.2em;
position: relative;
}
#home-content-preload-drop {
height: 0px;
width: 0px;
overflow: hidden;
position: absolute;
bottom: 0;
right: 0;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@@ -377,26 +387,6 @@
} }
} }
#home-content > .screen {
display: flex;
justify-content: top;
flex-direction: column;
font-size: 1em;
}
#home-content > .screen > .screen-error {
display: block;
padding: 10px;
background: #b44;
color: #fff;
font-size: 0.75em;
flex: 0 0;
}
#home-content > .screen > .screen-screen {
flex: auto;
}
#home-content-wrap { #home-content-wrap {
max-width: 520px; max-width: 520px;
margin: 50px auto; margin: 50px auto;

View File

@@ -62,6 +62,8 @@
:screen="tab.current" :screen="tab.current"
:screens="tab.tabs" :screens="tab.tabs"
@stopped="tabStopped" @stopped="tabStopped"
@warning="tabWarning"
@info="tabInfo"
@updated="tabUpdated" @updated="tabUpdated"
> >
<div id="home-content-wrap"> <div id="home-content-wrap">
@@ -102,6 +104,8 @@
>. >.
</p> </p>
</div> </div>
<div id="home-content-preload-drop"></div>
</screens> </screens>
<connect-widget <connect-widget
@@ -468,7 +472,8 @@ export default {
control: data.control, control: data.control,
toolbar: false, toolbar: false,
indicator: { indicator: {
error: "", level: "",
message: "",
updated: false updated: false
}, },
status: { status: {
@@ -520,10 +525,30 @@ export default {
this.$emit("tab-closed", this.tab.tabs); this.$emit("tab-closed", this.tab.tabs);
}, },
tabStopped(index, reason) { tabStopped(index, reason) {
if (reason === null) { if (reason !== null) {
this.tab.tabs[index].indicator.error = ""; this.tab.tabs[index].indicator.message = "" + reason;
this.tab.tabs[index].indicator.level = "error";
} else { } else {
this.tab.tabs[index].indicator.error = "" + reason; this.tab.tabs[index].indicator.message = "";
this.tab.tabs[index].indicator.level = "";
}
},
tabWarning(index, msg) {
if (msg.length > 0) {
this.tab.tabs[index].indicator.message = msg;
this.tab.tabs[index].indicator.level = "warning";
} else {
this.tab.tabs[index].indicator.message = "";
this.tab.tabs[index].indicator.level = "";
}
},
tabInfo(index, msg) {
if (msg.length > 0) {
this.tab.tabs[index].indicator.message = msg;
this.tab.tabs[index].indicator.level = "info";
} else {
this.tab.tabs[index].indicator.message = "";
this.tab.tabs[index].indicator.level = "";
} }
}, },
tabUpdated(index) { tabUpdated(index) {

View File

@@ -19,6 +19,18 @@
@charset "utf-8"; @charset "utf-8";
@import "~hack-font/build/web/hack.css";
#home-content > #home-content-preload-drop::before {
content: " ";
font-family: Hack, monospace;
font-weight: bold;
}
#home-content > #home-content-preload-drop::after {
content: " ";
font-family: Hack, monospace;
}
#home-content > .screen > .screen-screen > .screen-console { #home-content > .screen > .screen-screen > .screen-console {
position: relative; position: relative;
} }
@@ -132,4 +144,5 @@
} }
#home-content > .screen > .screen-screen > .screen-console > .console-console { #home-content > .screen > .screen-screen > .screen-console > .console-console {
font-family: Hack, monospace;
} }

View File

@@ -39,6 +39,23 @@
> >
<h2 style="display:none;">Tool bar</h2> <h2 style="display:none;">Tool bar</h2>
<div class="console-toolbar-item">
<h3 class="tb-title">Text size</h3>
<ul class="hlst lst-nostyle">
<li>
<a class="tb-item" href="javascript:;" @click="fontSizeUp">
<span class="tb-key-icon icon icon-keyboardkey2">Increase +</span>
</a>
</li>
<li>
<a class="tb-item" href="javascript:;" @click="fontSizeDown">
<span class="tb-key-icon icon icon-keyboardkey2">Decrease -</span>
</a>
</li>
</ul>
</div>
<div <div
v-for="(keyType, keyTypeIdx) in screenKeys" v-for="(keyType, keyTypeIdx) in screenKeys"
:key="keyTypeIdx" :key="keyTypeIdx"
@@ -62,6 +79,7 @@
</template> </template>
<script> <script>
import FontFaceObserver from "fontfaceobserver";
import { Terminal } from "xterm"; import { Terminal } from "xterm";
import { WebLinksAddon } from "xterm-addon-web-links"; import { WebLinksAddon } from "xterm-addon-web-links";
import { FitAddon } from "xterm-addon-fit"; import { FitAddon } from "xterm-addon-fit";
@@ -71,15 +89,25 @@ import { consoleScreenKeys } from "./screen_console_keys.js";
import "./screen_console.css"; import "./screen_console.css";
import "xterm/css/xterm.css"; import "xterm/css/xterm.css";
const termTypeFace = "Hack";
const termFallbackTypeFace = "monospace";
const termTypeFaceLoadTimeout = 10000;
const termDefaultFontSize = 16;
const termMinFontSize = 14;
const termMaxFontSize = 36;
class Term { class Term {
constructor(control) { constructor(control) {
const resizeDelayInterval = 500; const resizeDelayInterval = 500;
this.closed = false; this.closed = false;
this.fontSize = termDefaultFontSize;
this.term = new Terminal({ this.term = new Terminal({
allowTransparency: false, allowTransparency: false,
cursorBlink: true, cursorBlink: true,
cursorStyle: "block", cursorStyle: "block",
fontFamily: termTypeFace + ", " + termFallbackTypeFace,
fontSize: this.fontSize,
logLevel: process.env.NODE_ENV === "development" ? "info" : "off" logLevel: process.env.NODE_ENV === "development" ? "info" : "off"
}); });
this.fit = new FitAddon(); this.fit = new FitAddon();
@@ -177,7 +205,7 @@ class Term {
}); });
} }
init(root, callbacks) { open(root, callbacks) {
this.term.open(root); this.term.open(root);
this.term.textarea.addEventListener("focus", callbacks.focus); this.term.textarea.addEventListener("focus", callbacks.focus);
@@ -220,6 +248,32 @@ class Term {
this.refit(); this.refit();
} }
init(root, callbacks) {
const self = this;
return Promise.all([
new FontFaceObserver(termTypeFace).load(null, termTypeFaceLoadTimeout),
new FontFaceObserver(termTypeFace, { weight: "bold" }).load(
null,
termTypeFaceLoadTimeout
)
])
.then(() => {
self.open(root, callbacks);
})
.catch(() => {
callbacks.warn(
"Unable to load remote font, using " +
termFallbackTypeFace +
" instead"
);
self.term.setOption("fontFamily", termFallbackTypeFace);
self.open(root, callbacks);
});
}
dispatch(event) { dispatch(event) {
try { try {
this.term.textarea.dispatchEvent(event); this.term.textarea.dispatchEvent(event);
@@ -244,6 +298,26 @@ class Term {
} }
} }
fontSizeUp() {
if (this.fontSize >= termMaxFontSize) {
return;
}
this.fontSize += 2;
this.term.setOption("fontSize", this.fontSize);
this.refit();
}
fontSizeDown() {
if (this.fontSize <= termMinFontSize) {
return;
}
this.fontSize -= 2;
this.term.setOption("fontSize", this.fontSize);
this.refit();
}
focus() { focus() {
try { try {
this.term.focus(); this.term.focus();
@@ -332,8 +406,8 @@ export default {
deep: true deep: true
} }
}, },
mounted() { async mounted() {
this.init(); await this.init();
}, },
beforeDestroy() { beforeDestroy() {
this.deinit(); this.deinit();
@@ -342,10 +416,12 @@ export default {
triggerActive() { triggerActive() {
this.active ? this.activate() : this.deactivate(); this.active ? this.activate() : this.deactivate();
}, },
init() { async init() {
let self = this; let self = this;
this.term.init(this.$el.getElementsByClassName("console-console")[0], { await this.term.init(
this.$el.getElementsByClassName("console-console")[0],
{
focus(e) { focus(e) {
document.addEventListener("keyup", self.localKeypress); document.addEventListener("keyup", self.localKeypress);
document.addEventListener("keydown", self.localKeypress); document.addEventListener("keydown", self.localKeypress);
@@ -353,8 +429,15 @@ export default {
blur(e) { blur(e) {
document.removeEventListener("keyup", self.localKeypress); document.removeEventListener("keyup", self.localKeypress);
document.removeEventListener("keydown", self.localKeypress); document.removeEventListener("keydown", self.localKeypress);
},
warn(msg) {
self.$emit("warning", msg);
},
info(msg) {
self.$emit("info", msg);
} }
}); }
);
this.triggerActive(); this.triggerActive();
this.runRunner(); this.runRunner();
@@ -421,6 +504,12 @@ export default {
this.term.dispatch(new KeyboardEvent("keydown", key)); this.term.dispatch(new KeyboardEvent("keydown", key));
this.term.dispatch(new KeyboardEvent("keyup", key)); this.term.dispatch(new KeyboardEvent("keyup", key));
},
fontSizeUp() {
this.term.fontSizeUp();
},
fontSizeDown() {
this.term.fontSizeDown();
} }
} }
}; };

52
ui/widgets/screens.css Normal file
View File

@@ -0,0 +1,52 @@
/*
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
@charset "utf-8";
#home-content > .screen {
display: flex;
justify-content: top;
flex-direction: column;
font-size: 1em;
}
#home-content > .screen > .screen-error {
display: block;
padding: 10px;
background: #b44;
color: #fff;
font-size: 0.75em;
flex: 0 0;
}
#home-content > .screen > .screen-error.screen-error-level-error {
background: #b44;
}
#home-content > .screen > .screen-error.screen-error-level-warning {
background: #b82;
}
#home-content > .screen > .screen-error.screen-error-level-info {
background: #28b;
}
#home-content > .screen > .screen-screen {
flex: auto;
}

View File

@@ -30,8 +30,12 @@
> >
<h1 style="display:none;">Main Interface</h1> <h1 style="display:none;">Main Interface</h1>
<div v-if="screenInfo.indicator.error.length > 0" class="screen-error"> <div
{{ screenInfo.indicator.error }} v-if="screenInfo.indicator.message.length > 0"
class="screen-error"
:class="'screen-error-level-' + screenInfo.indicator.level"
>
{{ screenInfo.indicator.message }}
</div> </div>
<div class="screen-screen" style="position: relative"> <div class="screen-screen" style="position: relative">
@@ -44,6 +48,8 @@
:style="'background-color: ' + screenInfo.control.activeColor()" :style="'background-color: ' + screenInfo.control.activeColor()"
style="top: 0; right: 0; left: 0; bottom: 0; padding 0; margin: 0; position: absolute; overflow: hidden" style="top: 0; right: 0; left: 0; bottom: 0; padding 0; margin: 0; position: absolute; overflow: hidden"
@stopped="stopped(idx, $event)" @stopped="stopped(idx, $event)"
@warning="warning(idx, $event)"
@info="info(idx, $event)"
@updated="updated(idx)" @updated="updated(idx)"
></component> ></component>
</div> </div>
@@ -54,6 +60,8 @@
<script> <script>
import ConsoleScreen from "./screen_console.vue"; import ConsoleScreen from "./screen_console.vue";
import "./screens.css";
export default { export default {
components: { components: {
ConsoleScreen ConsoleScreen
@@ -81,6 +89,12 @@ export default {
stopped(index, stopErr) { stopped(index, stopErr) {
this.$emit("stopped", index, stopErr); this.$emit("stopped", index, stopErr);
}, },
warning(index, msg) {
this.$emit("warning", index, msg);
},
info(index, msg) {
this.$emit("info", index, msg);
},
updated(index) { updated(index) {
this.$emit("updated", index); this.$emit("updated", index);
} }

View File

@@ -24,7 +24,9 @@
:key="tabInfo.id" :key="tabInfo.id"
:class="{ :class="{
active: tab === idx, active: tab === idx,
error: tabInfo.indicator.error.length > 0, error:
tabInfo.indicator.message.length > 0 &&
tabInfo.indicator.level === 'error',
updated: tabInfo.indicator.updated && tab !== idx updated: tabInfo.indicator.updated && tab !== idx
}" }"
:style=" :style="