mirror of
https://github.com/Eugeny/tabby.git
synced 2025-08-26 19:21:52 +00:00
Compare commits
319 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8b307be92d | ||
![]() |
45516c4013 | ||
![]() |
5d8f7a4e34 | ||
![]() |
792c9279e2 | ||
![]() |
8825a8163e | ||
![]() |
7e42328c6f | ||
![]() |
2dd99d43ed | ||
![]() |
991b4fc965 | ||
![]() |
e9d7af5320 | ||
![]() |
bff23fe825 | ||
![]() |
79bd94ee6f | ||
![]() |
fdd833ef7c | ||
![]() |
c386504296 | ||
![]() |
9cc8649422 | ||
![]() |
625a9179c5 | ||
![]() |
66ed73f7c9 | ||
![]() |
95bd48d6c6 | ||
![]() |
328490a85e | ||
![]() |
d08413aeab | ||
![]() |
922f1fbade | ||
![]() |
cb96c8d470 | ||
![]() |
f3ebc43667 | ||
![]() |
e7696dcdf3 | ||
![]() |
4abf7d7738 | ||
![]() |
be206161d6 | ||
![]() |
dcd17f886e | ||
![]() |
2c94dea941 | ||
![]() |
7a2291f7db | ||
![]() |
af0d9142ed | ||
![]() |
6ba7d5b78f | ||
![]() |
bbf035d5fd | ||
![]() |
84bc4369cb | ||
![]() |
e01f77998c | ||
![]() |
08acd4df46 | ||
![]() |
2c9d968aa8 | ||
![]() |
19a3996861 | ||
![]() |
7cbec63039 | ||
![]() |
91320f1cd7 | ||
![]() |
5518ce5e0c | ||
![]() |
d59c52d7a5 | ||
![]() |
dd3a4cb289 | ||
![]() |
fc6ded4b1a | ||
![]() |
d4f61b3846 | ||
![]() |
ffad7b6ba7 | ||
![]() |
499f541328 | ||
![]() |
7c893a3c4b | ||
![]() |
1f5c55826b | ||
![]() |
197824004e | ||
![]() |
ee1d465bf6 | ||
![]() |
12bb070def | ||
![]() |
13ab29bcab | ||
![]() |
8cf0445b6d | ||
![]() |
90cc06c3fd | ||
![]() |
7d7b2cbcfd | ||
![]() |
3a76e0bb2e | ||
![]() |
9e2d070ed4 | ||
![]() |
f796718cae | ||
![]() |
a4c2ccdb93 | ||
![]() |
ccbaf1c2c2 | ||
![]() |
7ccd97eb49 | ||
![]() |
6b320e9cf3 | ||
![]() |
a6a5f2b132 | ||
![]() |
6fd7e97ef2 | ||
![]() |
fc821b5abd | ||
![]() |
f69a9b38fe | ||
![]() |
710b9d79ab | ||
![]() |
fddae662c8 | ||
![]() |
c3b3a3cea6 | ||
![]() |
2201cfe142 | ||
![]() |
a9ae3b2475 | ||
![]() |
3238706b25 | ||
![]() |
a5aec13b7f | ||
![]() |
454887ad21 | ||
![]() |
0ad585d647 | ||
![]() |
5aa3b889f5 | ||
![]() |
d371857fa8 | ||
![]() |
e116a42f8b | ||
![]() |
76acbc6c9f | ||
![]() |
572a6e24c4 | ||
![]() |
b3731a21ba | ||
![]() |
f58ba65d97 | ||
![]() |
45a99bb0cb | ||
![]() |
ab70be983a | ||
![]() |
cad1f96df7 | ||
![]() |
71866521f4 | ||
![]() |
56206d4fb8 | ||
![]() |
347681d199 | ||
![]() |
e6abdcf3e9 | ||
![]() |
dbae3b66cd | ||
![]() |
40ec457d20 | ||
![]() |
063caf3bcd | ||
![]() |
9325fd0977 | ||
![]() |
6231583590 | ||
![]() |
fce3a2c822 | ||
![]() |
73f45c9a24 | ||
![]() |
ca65f23c70 | ||
![]() |
f525398827 | ||
![]() |
cf2baa74a8 | ||
![]() |
fbd896d593 | ||
![]() |
f747107042 | ||
![]() |
760ad140cd | ||
![]() |
a0df434ed2 | ||
![]() |
0bcd5cfd8f | ||
![]() |
e0b71783c0 | ||
![]() |
45b76b2d3e | ||
![]() |
8228c99350 | ||
![]() |
c4dbd8180d | ||
![]() |
d3b1545a1e | ||
![]() |
57d7936239 | ||
![]() |
9e5315043d | ||
![]() |
89ba81f2e1 | ||
![]() |
1ee734cd18 | ||
![]() |
0bcfb6babf | ||
![]() |
fddd9e9db2 | ||
![]() |
2433fd1442 | ||
![]() |
437d832ac1 | ||
![]() |
c90aef1dfa | ||
![]() |
2d25f15041 | ||
![]() |
993d5bfd25 | ||
![]() |
4d8681b5ee | ||
![]() |
1384e26dd8 | ||
![]() |
809bf3360d | ||
![]() |
444875b82a | ||
![]() |
c261b64c8e | ||
![]() |
ab1b8a4500 | ||
![]() |
e65d5ba93b | ||
![]() |
6f8ba6b44b | ||
![]() |
aede1c47a2 | ||
![]() |
7b9ff043ad | ||
![]() |
d759104c76 | ||
![]() |
676bbba7a4 | ||
![]() |
b8d9f6442a | ||
![]() |
fc501b5e51 | ||
![]() |
3ddec27b69 | ||
![]() |
6574cf6b50 | ||
![]() |
d36ef2e48e | ||
![]() |
f8645df60c | ||
![]() |
f69942a3a3 | ||
![]() |
a36431a08c | ||
![]() |
f570d7e428 | ||
![]() |
fb502bc926 | ||
![]() |
2e12041688 | ||
![]() |
ca444bcf65 | ||
![]() |
da1b7b9a80 | ||
![]() |
e02d458109 | ||
![]() |
51e1a19e3e | ||
![]() |
68869a52e2 | ||
![]() |
8527c3f531 | ||
![]() |
4317094f1f | ||
![]() |
00cf7ef67d | ||
![]() |
21e7e762cd | ||
![]() |
ebb35cf9be | ||
![]() |
bd7f9a8030 | ||
![]() |
b1e0ed457e | ||
![]() |
59e40d53a1 | ||
![]() |
666a180f3f | ||
![]() |
ffc767c738 | ||
![]() |
49b252f7cf | ||
![]() |
81e9a0c796 | ||
![]() |
f3f5b21910 | ||
![]() |
b29ab2690f | ||
![]() |
f58cab0820 | ||
![]() |
b21631acd8 | ||
![]() |
52258f9949 | ||
![]() |
1308e842ce | ||
![]() |
c4ba51f4ee | ||
![]() |
65d411b00d | ||
![]() |
52d596b01b | ||
![]() |
1607dd90ba | ||
![]() |
11767f7d27 | ||
![]() |
7cbcf6844d | ||
![]() |
9f36258c60 | ||
![]() |
8f964ffc37 | ||
![]() |
4c137996ff | ||
![]() |
966bd5f917 | ||
![]() |
b55011d595 | ||
![]() |
38bd59641e | ||
![]() |
c449b60940 | ||
![]() |
ee618cdd1f | ||
![]() |
a5bddc21bb | ||
![]() |
35bf195f42 | ||
![]() |
044c2dda0e | ||
![]() |
9f2a70fc88 | ||
![]() |
409476c729 | ||
![]() |
91f8f25d26 | ||
![]() |
f853839939 | ||
![]() |
6099b44723 | ||
![]() |
e4e8140145 | ||
![]() |
8b34ab5102 | ||
![]() |
6dc987163d | ||
![]() |
a082c71a52 | ||
![]() |
cc37725014 | ||
![]() |
c6fd86dca6 | ||
![]() |
7f55d6f1e2 | ||
![]() |
129a7c1a09 | ||
![]() |
4969c4e2fc | ||
![]() |
d290fbe933 | ||
![]() |
358d3d82d6 | ||
![]() |
9b560c79ab | ||
![]() |
8b58bc420d | ||
![]() |
78a74ffe1b | ||
![]() |
565c675ce1 | ||
![]() |
a25a13188d | ||
![]() |
2daf85f753 | ||
![]() |
3189258fbb | ||
![]() |
d678bf68c5 | ||
![]() |
9498b17b98 | ||
![]() |
b48a335aed | ||
![]() |
71488e749a | ||
![]() |
0f1cbfa3ee | ||
![]() |
32d33bf85e | ||
![]() |
c7f1aa895d | ||
![]() |
675bc3d281 | ||
![]() |
c63ba8c22b | ||
![]() |
e9be73226e | ||
![]() |
4a72e554b6 | ||
![]() |
33bb3b0722 | ||
![]() |
46b4288c98 | ||
![]() |
9477117236 | ||
![]() |
33e048238e | ||
![]() |
5895d42444 | ||
![]() |
5d6abca503 | ||
![]() |
8ca91af927 | ||
![]() |
c886fd6915 | ||
![]() |
e403ca6eff | ||
![]() |
8f1b401137 | ||
![]() |
0952899204 | ||
![]() |
a11c643e41 | ||
![]() |
17fbdeafac | ||
![]() |
06e09f3a45 | ||
![]() |
4fa63aa939 | ||
![]() |
46622cd5d9 | ||
![]() |
6d3aace1c9 | ||
![]() |
e3a569be18 | ||
![]() |
2a256ef2bd | ||
![]() |
4f3fcc8b22 | ||
![]() |
045cc0d243 | ||
![]() |
51e33abbe6 | ||
![]() |
4900c043ca | ||
![]() |
09d55979ce | ||
![]() |
5d431fa9cf | ||
![]() |
113573b2d2 | ||
![]() |
2c3d93608b | ||
![]() |
d38af18582 | ||
![]() |
140f7c51f4 | ||
![]() |
ffa4350420 | ||
![]() |
99737323de | ||
![]() |
e9e2429632 | ||
![]() |
07597ac79a | ||
![]() |
248ec60612 | ||
![]() |
726847d5df | ||
![]() |
6065a95132 | ||
![]() |
e1259475d2 | ||
![]() |
ee018e7c02 | ||
![]() |
db43381f0d | ||
![]() |
28c58d4ec0 | ||
![]() |
68efe2b3c4 | ||
![]() |
795979be07 | ||
![]() |
c87a1b92d3 | ||
![]() |
2548ad6605 | ||
![]() |
b6519c6626 | ||
![]() |
2b8bb47aed | ||
![]() |
bbf332171e | ||
![]() |
ef5c9b52a5 | ||
![]() |
4632523d70 | ||
![]() |
56b996e6e4 | ||
![]() |
0ca0996493 | ||
![]() |
e1a8e72742 | ||
![]() |
50959f4490 | ||
![]() |
baaebb402e | ||
![]() |
9e862772eb | ||
![]() |
6cb5505ded | ||
![]() |
eb0d8615e1 | ||
![]() |
f2885c2fce | ||
![]() |
c04018bc70 | ||
![]() |
3e5032ca8b | ||
![]() |
a5014243d9 | ||
![]() |
2692eb141c | ||
![]() |
b447f1d52e | ||
![]() |
606b9af3f1 | ||
![]() |
3deab9af24 | ||
![]() |
afab0c5cde | ||
![]() |
e1a03f0dfb | ||
![]() |
bfe8dfab02 | ||
![]() |
a4335edc07 | ||
![]() |
8613698be9 | ||
![]() |
731ddc3e28 | ||
![]() |
2303e32256 | ||
![]() |
9064f123b3 | ||
![]() |
9d3ee4a612 | ||
![]() |
20602eed6d | ||
![]() |
f2cd86738c | ||
![]() |
fc55446342 | ||
![]() |
dbc12c06cb | ||
![]() |
efb551cd94 | ||
![]() |
a87c1aa864 | ||
![]() |
555f55592a | ||
![]() |
ab46739986 | ||
![]() |
7ac7958462 | ||
![]() |
46d5dace8f | ||
![]() |
aace3f42d0 | ||
![]() |
c4297f2b2b | ||
![]() |
3c90e904fc | ||
![]() |
f8c8065e4a | ||
![]() |
d45a5a35d8 | ||
![]() |
a9c6b868fb | ||
![]() |
ffe8168f0f | ||
![]() |
d1f5ebd546 | ||
![]() |
2e8b465b3f | ||
![]() |
4a535c94a6 | ||
![]() |
531d47cbd1 | ||
![]() |
7a2491fe49 | ||
![]() |
2773c61677 | ||
![]() |
788b063384 | ||
![]() |
0e112899df | ||
![]() |
d8c635bc1d | ||
![]() |
dfe55b94ff | ||
![]() |
6b4b6b522f | ||
![]() |
e0eedca7c9 |
@@ -253,6 +253,24 @@
|
|||||||
"code",
|
"code",
|
||||||
"plugin"
|
"plugin"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "orin220444",
|
||||||
|
"name": "orin220444",
|
||||||
|
"avatar_url": "https://avatars3.githubusercontent.com/u/30747229?v=4",
|
||||||
|
"profile": "https://github.com/orin220444",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Goobles",
|
||||||
|
"name": "Gobius Dolhain",
|
||||||
|
"avatar_url": "https://avatars3.githubusercontent.com/u/8776771?v=4",
|
||||||
|
"profile": "https://github.com/Goobles",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
@@ -99,3 +99,8 @@ rules:
|
|||||||
'@typescript-eslint/restrict-template-expressions': off
|
'@typescript-eslint/restrict-template-expressions': off
|
||||||
'@typescript-eslint/no-dynamic-delete': off
|
'@typescript-eslint/no-dynamic-delete': off
|
||||||
'@typescript-eslint/prefer-nullish-coalescing': off
|
'@typescript-eslint/prefer-nullish-coalescing': off
|
||||||
|
'@typescript-eslint/prefer-readonly-parameter-types': off
|
||||||
|
'@typescript-eslint/no-unsafe-member-access': off
|
||||||
|
'@typescript-eslint/no-unsafe-call': off
|
||||||
|
'@typescript-eslint/no-unsafe-return': off
|
||||||
|
'@typescript-eslint/no-base-to-string': off # broken in typescript-eslint
|
||||||
|
4
.github/workflows/macos.yml
vendored
4
.github/workflows/macos.yml
vendored
@@ -31,6 +31,8 @@ jobs:
|
|||||||
- name: Prepackage plugins
|
- name: Prepackage plugins
|
||||||
run: scripts/prepackage-plugins.js
|
run: scripts/prepackage-plugins.js
|
||||||
|
|
||||||
|
- run: sed -i '' 's/updateInfo = await/\/\/updateInfo = await/g' node_modules/app-builder-lib/out/targets/ArchiveTarget.js
|
||||||
|
|
||||||
- name: Build and sign packages
|
- name: Build and sign packages
|
||||||
run: scripts/build-macos.js
|
run: scripts/build-macos.js
|
||||||
if: github.repository == 'Eugeny/terminus' && github.event_name == 'push'
|
if: github.repository == 'Eugeny/terminus' && github.event_name == 'push'
|
||||||
@@ -39,6 +41,8 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||||
|
APPSTORE_USERNAME: ${{ secrets.APPSTORE_USERNAME }}
|
||||||
|
APPSTORE_PASSWORD: ${{ secrets.APPSTORE_PASSWORD }}
|
||||||
|
|
||||||
- name: Build packages without signing
|
- name: Build packages without signing
|
||||||
run: scripts/build-macos.js
|
run: scripts/build-macos.js
|
||||||
|
@@ -107,6 +107,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<td align="center"><a href="https://github.com/LeSeulArtichaut"><img src="https://avatars1.githubusercontent.com/u/38361244?v=4" width="100px;" alt=""/><br /><sub><b>LeSeulArtichaut</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=LeSeulArtichaut" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/LeSeulArtichaut"><img src="https://avatars1.githubusercontent.com/u/38361244?v=4" width="100px;" alt=""/><br /><sub><b>LeSeulArtichaut</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=LeSeulArtichaut" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/CyrilTaylor"><img src="https://avatars0.githubusercontent.com/u/12631466?v=4" width="100px;" alt=""/><br /><sub><b>Cyril Taylor</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=CyrilTaylor" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/CyrilTaylor"><img src="https://avatars0.githubusercontent.com/u/12631466?v=4" width="100px;" alt=""/><br /><sub><b>Cyril Taylor</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=CyrilTaylor" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/nstefanou"><img src="https://avatars3.githubusercontent.com/u/51129173?v=4" width="100px;" alt=""/><br /><sub><b>nstefanou</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=nstefanou" title="Code">💻</a> <a href="#plugin-nstefanou" title="Plugin/utility libraries">🔌</a></td>
|
<td align="center"><a href="https://github.com/nstefanou"><img src="https://avatars3.githubusercontent.com/u/51129173?v=4" width="100px;" alt=""/><br /><sub><b>nstefanou</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=nstefanou" title="Code">💻</a> <a href="#plugin-nstefanou" title="Plugin/utility libraries">🔌</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/orin220444"><img src="https://avatars3.githubusercontent.com/u/30747229?v=4" width="100px;" alt=""/><br /><sub><b>orin220444</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=orin220444" title="Code">💻</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://github.com/Goobles"><img src="https://avatars3.githubusercontent.com/u/8776771?v=4" width="100px;" alt=""/><br /><sub><b>Gobius Dolhain</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Goobles" title="Code">💻</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@@ -8,17 +8,15 @@ html
|
|||||||
window.nodeRequire = require
|
window.nodeRequire = require
|
||||||
script(src='./preload.js')
|
script(src='./preload.js')
|
||||||
script(src='./bundle.js', defer)
|
script(src='./bundle.js', defer)
|
||||||
style#custom-css
|
|
||||||
style.
|
style.
|
||||||
body { transition: 0.5s background; }
|
body { transition: 0.5s background; }
|
||||||
body
|
body
|
||||||
|
style#custom-css
|
||||||
app-root
|
app-root
|
||||||
.preload-logo
|
.preload-logo
|
||||||
div
|
div
|
||||||
.terminus-logo
|
.terminus-logo
|
||||||
h1.terminus-title Terminus
|
h1.terminus-title Terminus
|
||||||
sup α
|
sup α
|
||||||
.progress
|
.progress
|
||||||
.bar(style='width: 0%')
|
.bar(style='width: 0%')
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { app, ipcMain, Menu, Tray, shell } from 'electron'
|
import { app, ipcMain, Menu, Tray, shell, globalShortcut } from 'electron'
|
||||||
// eslint-disable-next-line no-duplicate-imports
|
// eslint-disable-next-line no-duplicate-imports
|
||||||
import * as electron from 'electron'
|
import * as electron from 'electron'
|
||||||
import { loadConfig } from './config'
|
import { loadConfig } from './config'
|
||||||
@@ -9,8 +9,17 @@ export class Application {
|
|||||||
private windows: Window[] = []
|
private windows: Window[] = []
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
ipcMain.on('app:config-change', () => {
|
ipcMain.on('app:config-change', (_event, config) => {
|
||||||
this.broadcast('host:config-change')
|
this.broadcast('host:config-change', config)
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.on('app:register-global-hotkey', (_event, specs) => {
|
||||||
|
globalShortcut.unregisterAll()
|
||||||
|
for (let spec of specs) {
|
||||||
|
globalShortcut.register(spec, () => {
|
||||||
|
this.onGlobalHotkey()
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const configData = loadConfig()
|
const configData = loadConfig()
|
||||||
@@ -45,6 +54,9 @@ export class Application {
|
|||||||
this.enableTray()
|
this.enableTray()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
window.closed$.subscribe(() => {
|
||||||
|
this.windows = this.windows.filter(x => x !== window)
|
||||||
|
})
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
this.setupMenu()
|
this.setupMenu()
|
||||||
}
|
}
|
||||||
@@ -52,6 +64,24 @@ export class Application {
|
|||||||
return window
|
return window
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onGlobalHotkey (): void {
|
||||||
|
if (this.windows.some(x => x.isFocused())) {
|
||||||
|
for (let window of this.windows) {
|
||||||
|
window.hide()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let window of this.windows) {
|
||||||
|
window.present()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
presentAllWindows (): void {
|
||||||
|
for (let window of this.windows) {
|
||||||
|
window.present()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
broadcast (event: string, ...args): void {
|
broadcast (event: string, ...args): void {
|
||||||
for (const window of this.windows) {
|
for (const window of this.windows) {
|
||||||
window.send(event, ...args)
|
window.send(event, ...args)
|
||||||
@@ -107,6 +137,13 @@ export class Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSecondInstance (argv: string[], cwd: string): void {
|
||||||
|
this.presentAllWindows()
|
||||||
|
for (let window of this.windows) {
|
||||||
|
window.handleSecondInstance(argv, cwd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private setupMenu () {
|
private setupMenu () {
|
||||||
let template: Electron.MenuItemConstructorOptions[] = [
|
let template: Electron.MenuItemConstructorOptions[] = [
|
||||||
{
|
{
|
||||||
|
@@ -34,7 +34,7 @@ process.on('uncaughtException' as any, err => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
app.on('second-instance', (_event, argv, cwd) => {
|
app.on('second-instance', (_event, argv, cwd) => {
|
||||||
application.send('host:second-instance', parseArgs(argv, cwd), cwd)
|
application.handleSecondInstance(argv, cwd)
|
||||||
})
|
})
|
||||||
|
|
||||||
const argv = parseArgs(process.argv, process.cwd())
|
const argv = parseArgs(process.argv, process.cwd())
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { Subject, Observable } from 'rxjs'
|
import { Subject, Observable } from 'rxjs'
|
||||||
import { debounceTime } from 'rxjs/operators'
|
import { debounceTime } from 'rxjs/operators'
|
||||||
import { BrowserWindow, app, ipcMain, Rectangle, screen } from 'electron'
|
import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen } from 'electron'
|
||||||
import ElectronConfig = require('electron-config')
|
import ElectronConfig = require('electron-config')
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
|
||||||
|
import { parseArgs } from './cli'
|
||||||
import { loadConfig } from './config'
|
import { loadConfig } from './config'
|
||||||
|
|
||||||
let SetWindowCompositionAttribute: any
|
let SetWindowCompositionAttribute: any
|
||||||
@@ -23,17 +24,20 @@ export interface WindowOptions {
|
|||||||
export class Window {
|
export class Window {
|
||||||
ready: Promise<void>
|
ready: Promise<void>
|
||||||
private visible = new Subject<boolean>()
|
private visible = new Subject<boolean>()
|
||||||
|
private closed = new Subject<void>()
|
||||||
private window: BrowserWindow
|
private window: BrowserWindow
|
||||||
private windowConfig: ElectronConfig
|
private windowConfig: ElectronConfig
|
||||||
private windowBounds: Rectangle
|
private windowBounds: Rectangle
|
||||||
private closing = false
|
private closing = false
|
||||||
private lastVibrancy: {enabled: boolean, type?: string} | null = null
|
private lastVibrancy: {enabled: boolean, type?: string} | null = null
|
||||||
private disableVibrancyWhileDragging = false
|
private disableVibrancyWhileDragging = false
|
||||||
|
private configStore: any
|
||||||
|
|
||||||
get visible$ (): Observable<boolean> { return this.visible }
|
get visible$ (): Observable<boolean> { return this.visible }
|
||||||
|
get closed$ (): Observable<void> { return this.closed }
|
||||||
|
|
||||||
constructor (options?: WindowOptions) {
|
constructor (options?: WindowOptions) {
|
||||||
let configData = loadConfig()
|
this.configStore = loadConfig()
|
||||||
|
|
||||||
options = options || {}
|
options = options || {}
|
||||||
|
|
||||||
@@ -70,7 +74,7 @@ export class Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((configData.appearance || {}).frame === 'native') {
|
if ((this.configStore.appearance || {}).frame === 'native') {
|
||||||
bwOptions.frame = true
|
bwOptions.frame = true
|
||||||
} else {
|
} else {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
@@ -86,7 +90,7 @@ export class Window {
|
|||||||
this.window.once('ready-to-show', () => {
|
this.window.once('ready-to-show', () => {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
this.window.setVibrancy('window')
|
this.window.setVibrancy('window')
|
||||||
} else if (process.platform === 'win32' && (configData.appearance || {}).vibrancy) {
|
} else if (process.platform === 'win32' && (this.configStore.appearance || {}).vibrancy) {
|
||||||
this.setVibrancy(true)
|
this.setVibrancy(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +101,13 @@ export class Window {
|
|||||||
this.window.show()
|
this.window.show()
|
||||||
}
|
}
|
||||||
this.window.focus()
|
this.window.focus()
|
||||||
|
this.window.moveTop()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.window.on('blur', () => {
|
||||||
|
if (this.configStore.appearance?.dockHideOnBlur) {
|
||||||
|
this.hide()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -142,6 +153,7 @@ export class Window {
|
|||||||
|
|
||||||
show (): void {
|
show (): void {
|
||||||
this.window.show()
|
this.window.show()
|
||||||
|
this.window.moveTop()
|
||||||
}
|
}
|
||||||
|
|
||||||
focus (): void {
|
focus (): void {
|
||||||
@@ -153,12 +165,60 @@ export class Window {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.window.webContents.send(event, ...args)
|
this.window.webContents.send(event, ...args)
|
||||||
|
if (event === 'host:config-change') {
|
||||||
|
this.configStore = args[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isDestroyed (): boolean {
|
isDestroyed (): boolean {
|
||||||
return !this.window || this.window.isDestroyed()
|
return !this.window || this.window.isDestroyed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isFocused (): boolean {
|
||||||
|
return this.window.isFocused()
|
||||||
|
}
|
||||||
|
|
||||||
|
hide (): void {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
// Lose focus
|
||||||
|
Menu.sendActionToFirstResponder('hide:')
|
||||||
|
}
|
||||||
|
this.window.blur()
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
this.window.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
present (): void {
|
||||||
|
if (!this.window.isVisible()) {
|
||||||
|
// unfocused, invisible
|
||||||
|
this.window.show()
|
||||||
|
this.window.focus()
|
||||||
|
} else {
|
||||||
|
if (!this.configStore.appearance?.dock || this.configStore.appearance?.dock === 'off') {
|
||||||
|
// not docked, visible
|
||||||
|
setTimeout(() => {
|
||||||
|
this.window.show()
|
||||||
|
this.window.focus()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (this.configStore.appearance?.dockAlwaysOnTop) {
|
||||||
|
// docked, visible, on top
|
||||||
|
this.window.hide()
|
||||||
|
} else {
|
||||||
|
// docked, visible, not on top
|
||||||
|
this.window.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSecondInstance (argv: string[], cwd: string): void {
|
||||||
|
if (!this.configStore.appearance?.dock) {
|
||||||
|
this.send('host:second-instance', parseArgs(argv, cwd), cwd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private setupWindowManagement () {
|
private setupWindowManagement () {
|
||||||
this.window.on('show', () => {
|
this.window.on('show', () => {
|
||||||
this.visible.next(true)
|
this.visible.next(true)
|
||||||
@@ -326,6 +386,8 @@ export class Window {
|
|||||||
|
|
||||||
private destroy () {
|
private destroy () {
|
||||||
this.window = null
|
this.window = null
|
||||||
|
this.closed.next()
|
||||||
this.visible.complete()
|
this.visible.complete()
|
||||||
|
this.closed.complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,35 +13,35 @@
|
|||||||
"watch": "webpack --progress --color --watch"
|
"watch": "webpack --progress --color --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "9.0.4",
|
"@angular/animations": "9.1.9",
|
||||||
"@angular/common": "9.0.4",
|
"@angular/common": "9.1.9",
|
||||||
"@angular/compiler": "9.0.4",
|
"@angular/compiler": "9.1.9",
|
||||||
"@angular/core": "9.0.4",
|
"@angular/core": "9.1.9",
|
||||||
"@angular/forms": "9.0.4",
|
"@angular/forms": "9.1.9",
|
||||||
"@angular/platform-browser": "9.0.4",
|
"@angular/platform-browser": "9.1.9",
|
||||||
"@angular/platform-browser-dynamic": "9.0.4",
|
"@angular/platform-browser-dynamic": "9.1.9",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^6.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^6.1.0",
|
||||||
"devtron": "1.4.0",
|
"devtron": "1.4.0",
|
||||||
"electron-config": "2.0.0",
|
"electron-config": "2.0.0",
|
||||||
"electron-debug": "^3.0.1",
|
"electron-debug": "^3.0.1",
|
||||||
"electron-is-dev": "1.1.0",
|
"electron-is-dev": "1.1.0",
|
||||||
"electron-updater": "^4.2.2",
|
"electron-updater": "^4.3.1",
|
||||||
"fontmanager-redux": "0.4.0",
|
"fontmanager-redux": "0.4.0",
|
||||||
"js-yaml": "3.13.1",
|
"js-yaml": "3.14.0",
|
||||||
"keytar": "^5.4.0",
|
"keytar": "^5.6.0",
|
||||||
"mz": "^2.7.0",
|
"mz": "^2.7.0",
|
||||||
"ngx-toastr": "^10.2.0",
|
"ngx-toastr": "^12.0.1",
|
||||||
"node-pty": "^0.10.0-beta2",
|
"node-pty": "^0.10.0-beta9",
|
||||||
"npm": "6.9.0",
|
"npm": "6.9.0",
|
||||||
"path": "0.12.7",
|
"path": "0.12.7",
|
||||||
"rxjs": "^6.5.4",
|
"rxjs": "^6.5.5",
|
||||||
"rxjs-compat": "^6.5.4",
|
"rxjs-compat": "^6.5.5",
|
||||||
"yargs": "^15.1.0",
|
"yargs": "^15.3.1",
|
||||||
"zone.js": "^0.10.2"
|
"zone.js": "^0.10.3"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"macos-native-processlist": "^1.0.2",
|
"macos-native-processlist": "^1.0.2",
|
||||||
"serialport": "^8.0.7",
|
"serialport": "^9.0.0",
|
||||||
"windows-blurbehind": "^1.0.1",
|
"windows-blurbehind": "^1.0.1",
|
||||||
"windows-native-registry": "^1.0.17",
|
"windows-native-registry": "^1.0.17",
|
||||||
"windows-process-tree": "^0.2.4",
|
"windows-process-tree": "^0.2.4",
|
||||||
@@ -50,6 +50,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mz": "0.0.32",
|
"@types/mz": "0.0.32",
|
||||||
"@types/node": "12.7.12",
|
"@types/node": "12.7.12",
|
||||||
"node-abi": "^2.15.0"
|
"node-abi": "^2.17.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
220
app/yarn.lock
220
app/yarn.lock
@@ -2,45 +2,45 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@angular/animations@9.0.4":
|
"@angular/animations@9.1.9":
|
||||||
version "9.0.4"
|
version "9.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-9.0.4.tgz#c95c601dfb8fc4e96aee577c9c0f6cf18b64e5d7"
|
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-9.1.9.tgz#de54334ea195189402487855c9a98f5618605da4"
|
||||||
integrity sha512-zTCgrIAA9FYPMbqqpQnoNltiLR58q0FMfzP2t96q/1tjyVy/Y/IaNgVQ7eL0HeQ0nG6IAzQ1HVx8Xeneg4Yj5Q==
|
integrity sha512-qWVi0TxmU6HeXAgEsfpQvFFygh+a0kH2kGe6bWij4XvG6dWfV3xZjlaFwSIYGk+yK4yL0+9+PAXH+ENfxNw+Cw==
|
||||||
|
|
||||||
"@angular/common@9.0.4":
|
"@angular/common@9.1.9":
|
||||||
version "9.0.4"
|
version "9.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/common/-/common-9.0.4.tgz#7d168b22c5c43e72112d0a19242eca22b62bb4f3"
|
resolved "https://registry.yarnpkg.com/@angular/common/-/common-9.1.9.tgz#16e77b2db675b80e32f1788a20c538150fd09294"
|
||||||
integrity sha512-F3qoYrceEdCd5SlgObcbSIIdKfRXgyTBO2gbbArQHFe4GvewkH3isTn5uqAF6sfJlb7rXWZGrD6C3d9brw/fEw==
|
integrity sha512-y/tJtkuOJhV2kcaXZyrLZH84i4uQ1r+vaaEHvXj+JZYfYfcMMd/TDqMiPcIkUb3RxqghtZ+q0ZNW5D1Nlru3Pw==
|
||||||
|
|
||||||
"@angular/compiler@9.0.4":
|
"@angular/compiler@9.1.9":
|
||||||
version "9.0.4"
|
version "9.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-9.0.4.tgz#038c9cdbf76f1cce47bd1b355c7d212cc89b18f9"
|
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-9.1.9.tgz#cbf678ee28a0811a8ef3ee7be565d4911ff28ec7"
|
||||||
integrity sha512-+Ku8RUU00yHaKVkVw6YIfM3c5Gmvas5gJcEleiagkLbc1f/jKk1cY4gaUP6xn4TLypFM7NQglneWd+E+8wh0hQ==
|
integrity sha512-kjFgaTB2ckr9lgmkS1dOGRT7kmzpQueydxsxXSHWgICNVE6F/u1PHyeSOyJRpxW0GnrkLq3QM2EUFnQGGga5bg==
|
||||||
|
|
||||||
"@angular/core@9.0.4":
|
"@angular/core@9.1.9":
|
||||||
version "9.0.4"
|
version "9.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/core/-/core-9.0.4.tgz#6baa5ec6c594b47de541e47f4aa37241adec393a"
|
resolved "https://registry.yarnpkg.com/@angular/core/-/core-9.1.9.tgz#db4241f867d6e14b81ed6e7c50334813c6ebfc10"
|
||||||
integrity sha512-6RqQb1GO2uglSlgiGbxhvy8plztZtABCWLRn0X+T1PnrxoqgxqA5WkKJjGxao+1M/ECW1V0fw4Xy7DE6KvAJwQ==
|
integrity sha512-q/DERgVU6vK2LtTcdVCGGBcoO424WsEfImh3Vcuy+P/ZVmthlDUC/+q+tSKt8MMf4hLpxFDQJE8vUSkktj7QEw==
|
||||||
|
|
||||||
"@angular/forms@9.0.4":
|
"@angular/forms@9.1.9":
|
||||||
version "9.0.4"
|
version "9.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-9.0.4.tgz#31edac9917e592695a5c12b846e93dbda6afc510"
|
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-9.1.9.tgz#20c9a79d1dcb2cace45df9e2f304b658e02c1687"
|
||||||
integrity sha512-WyfZ2u2JzGrwkxQmfxHvZMoYHEGfoUL+JlSXa2Sy3T/FPGNckHzIzggqweJij/qGjabWLabZDla4vak42f+4PA==
|
integrity sha512-r675yImnb/0pY7K5W3V2ITa7YETu1I2AS+bRfII6UQ6gthyeFFOHb5noa7YneC2yqQiM6E4DQmF5ig3daPuFNg==
|
||||||
|
|
||||||
"@angular/platform-browser-dynamic@9.0.4":
|
"@angular/platform-browser-dynamic@9.1.9":
|
||||||
version "9.0.4"
|
version "9.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.0.4.tgz#343bd43fe00a279a737e02c16dd8790dc0da93a8"
|
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.1.9.tgz#12f8b05d3c9ef0844df88f3833e29ea1e49ec5e0"
|
||||||
integrity sha512-9vAn2QH07khuF4n7kyMJzgE6l30Yxg1AGd8GtOfa/4nbna+EZxFVYOkto9bpv4uEwDr9o7QrFLplko9a8xs7kg==
|
integrity sha512-b9MG5MWne+IuL3uLm8jwPhlJzqYaGBGk/qibOqb17T24j1iyrlO7T5bZ8zO6pUy5iT/TahVfHPnPJC1qTK5OmA==
|
||||||
|
|
||||||
"@angular/platform-browser@9.0.4":
|
"@angular/platform-browser@9.1.9":
|
||||||
version "9.0.4"
|
version "9.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-9.0.4.tgz#03853b435c3b964660727ac9d7e15912c920cdb8"
|
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-9.1.9.tgz#c2fcc50aebfdc268521b407e32dc0d967cb40411"
|
||||||
integrity sha512-mbiqmw0rDGPxEgKVgDuK7yZvtgjJmzpMGBYAMwkQ9YIE0SoA5XP0NvZiFkHZqDXwLgCv2IJ/kvkhfCBwnBKCXQ==
|
integrity sha512-V861X3MxJp1AlMTnkUPldpBLIJbApXF3ka0A5Dq2nVJCyOFeteGkaRWSBgqe2jxmq+LVpJbzcNvtDFXw6mQ0jA==
|
||||||
|
|
||||||
"@ng-bootstrap/ng-bootstrap@^6.0.0":
|
"@ng-bootstrap/ng-bootstrap@^6.1.0":
|
||||||
version "6.0.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-6.0.0.tgz#03b80acd711dd38a653b34339224d5063c50bd62"
|
resolved "https://registry.yarnpkg.com/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-6.1.0.tgz#fce7550a095aeac42108f76ac1ebd63caf8304e9"
|
||||||
integrity sha512-ho0Ssw+kwpGzc+Rvtu2pRSrcbduECMf9+uekhOMB1nzhUfF2vJQnTDpPfHZQgU/ukTMEJWvby5vcXJoPtHgE+w==
|
integrity sha512-2GzkNJBKdeHkaUqaCAqSILPft0IzzHjMfAlAuGY6/ZLlVQ0glt5MTbIXtIhSbjR+OvlrljoXFLrvzs1LGdmE+A==
|
||||||
|
|
||||||
"@serialport/binding-abstract@^8.0.6":
|
"@serialport/binding-abstract@^8.0.6":
|
||||||
version "8.0.6"
|
version "8.0.6"
|
||||||
@@ -290,6 +290,11 @@ asynckit@^0.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
|
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
|
||||||
|
|
||||||
|
at-least-node@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
||||||
|
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
||||||
|
|
||||||
aws-sign2@~0.7.0:
|
aws-sign2@~0.7.0:
|
||||||
version "0.7.0"
|
version "0.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||||
@@ -375,10 +380,10 @@ buffer-from@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||||
|
|
||||||
builder-util-runtime@8.6.0:
|
builder-util-runtime@8.7.0:
|
||||||
version "8.6.0"
|
version "8.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.6.0.tgz#b7007c30126da9a90e99932128d2922c8c178649"
|
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.7.0.tgz#e48ad004835c8284662e8eaf47a53468c66e8e8d"
|
||||||
integrity sha512-WTDhTUVrm7zkFyd6Qn7AXgmWifjpZ/fYnEdV3XCOIDMNNb/KPddBTbQ8bUlxxVeuOYlhGpcLUypG+4USdGL1ww==
|
integrity sha512-G1AqqVM2vYTrSFR982c1NNzwXKrGLQjVjaZaWQdn4O6Z3YKjdMDofw88aD9jpyK9ZXkrCxR0tI3Qe9wNbyTlXg==
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^4.1.1"
|
debug "^4.1.1"
|
||||||
sax "^1.2.4"
|
sax "^1.2.4"
|
||||||
@@ -857,18 +862,17 @@ electron-localshortcut@^3.1.0:
|
|||||||
keyboardevent-from-electron-accelerator "^1.1.0"
|
keyboardevent-from-electron-accelerator "^1.1.0"
|
||||||
keyboardevents-areequal "^0.2.1"
|
keyboardevents-areequal "^0.2.1"
|
||||||
|
|
||||||
electron-updater@^4.2.2:
|
electron-updater@^4.3.1:
|
||||||
version "4.2.2"
|
version "4.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.2.2.tgz#57e106bffad16f71b1ffa3968a52a1b71c8147e6"
|
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.3.1.tgz#9d485b6262bc56fcf7ee62b1dc1b3b105a3e96a7"
|
||||||
integrity sha512-e/OZhr5tLW0GcgmpR5wD0ImxgKMa8pPoNWRcwRyMzTL9pGej7+ORp0t9DtI5ZBHUbObIoEbrk+6EDGUGtJf+aA==
|
integrity sha512-UDC5AHCgeiHJYDYWZG/rsl1vdAFKqI/Lm7whN57LKAk8EfhTewhcEHzheRcncLgikMcQL8gFo1KeX51tf5a5Wg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/semver" "^7.1.0"
|
"@types/semver" "^7.1.0"
|
||||||
builder-util-runtime "8.6.0"
|
builder-util-runtime "8.7.0"
|
||||||
fs-extra "^8.1.0"
|
fs-extra "^9.0.0"
|
||||||
js-yaml "^3.13.1"
|
js-yaml "^3.13.1"
|
||||||
lazy-val "^1.0.4"
|
lazy-val "^1.0.4"
|
||||||
lodash.isequal "^4.5.0"
|
lodash.isequal "^4.5.0"
|
||||||
pako "^1.0.11"
|
|
||||||
semver "^7.1.3"
|
semver "^7.1.3"
|
||||||
|
|
||||||
emoji-regex@^8.0.0:
|
emoji-regex@^8.0.0:
|
||||||
@@ -1064,14 +1068,15 @@ fs-constants@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||||
|
|
||||||
fs-extra@^8.1.0:
|
fs-extra@^9.0.0:
|
||||||
version "8.1.0"
|
version "9.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.0.tgz#b6afc31036e247b2466dc99c29ae797d5d4580a3"
|
||||||
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
|
integrity sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==
|
||||||
dependencies:
|
dependencies:
|
||||||
|
at-least-node "^1.0.0"
|
||||||
graceful-fs "^4.2.0"
|
graceful-fs "^4.2.0"
|
||||||
jsonfile "^4.0.0"
|
jsonfile "^6.0.1"
|
||||||
universalify "^0.1.0"
|
universalify "^1.0.0"
|
||||||
|
|
||||||
fs-minipass@^1.2.5:
|
fs-minipass@^1.2.5:
|
||||||
version "1.2.6"
|
version "1.2.6"
|
||||||
@@ -1484,10 +1489,10 @@ isstream@~0.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||||
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
|
||||||
|
|
||||||
js-yaml@3.13.1, js-yaml@^3.13.1:
|
js-yaml@3.14.0, js-yaml@^3.13.1:
|
||||||
version "3.13.1"
|
version "3.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
|
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
|
||||||
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
|
integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse "^1.0.7"
|
argparse "^1.0.7"
|
||||||
esprima "^4.0.0"
|
esprima "^4.0.0"
|
||||||
@@ -1517,10 +1522,12 @@ json-stringify-safe@~5.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||||
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
||||||
|
|
||||||
jsonfile@^4.0.0:
|
jsonfile@^6.0.1:
|
||||||
version "4.0.0"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179"
|
||||||
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
|
integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==
|
||||||
|
dependencies:
|
||||||
|
universalify "^1.0.0"
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
@@ -1549,12 +1556,12 @@ keyboardevents-areequal@^0.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194"
|
resolved "https://registry.yarnpkg.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194"
|
||||||
integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw==
|
integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw==
|
||||||
|
|
||||||
keytar@^5.4.0:
|
keytar@^5.6.0:
|
||||||
version "5.4.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/keytar/-/keytar-5.4.0.tgz#71d8209e7dd2fe99008c243791350a6bd6ceab67"
|
resolved "https://registry.yarnpkg.com/keytar/-/keytar-5.6.0.tgz#7b5d4bd043d17211163640be6c4a27a49b12bb39"
|
||||||
integrity sha512-Ta0RtUmkq7un177SPgXKQ7FGfGDV4xvsV0cGNiWVEzash5U0wyOsXpwfrK2+Oq+hHvsvsbzIZUUuJPimm3avFw==
|
integrity sha512-ueulhshHSGoryfRXaIvTj0BV1yB0KddBGhGoqCxSN9LR1Ks1GKuuCdVhF+2/YOs5fMl6MlTI9On1a4DHDXoTow==
|
||||||
dependencies:
|
dependencies:
|
||||||
nan "2.14.0"
|
nan "2.14.1"
|
||||||
prebuild-install "5.3.3"
|
prebuild-install "5.3.3"
|
||||||
|
|
||||||
latest-version@^3.0.0:
|
latest-version@^3.0.0:
|
||||||
@@ -1972,27 +1979,25 @@ mz@^2.7.0:
|
|||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
thenify-all "^1.0.0"
|
thenify-all "^1.0.0"
|
||||||
|
|
||||||
nan@2.14.0, nan@^2.13.2, nan@^2.14.0:
|
nan@2.14.1, nan@^2.13.2, nan@^2.14.0:
|
||||||
version "2.14.0"
|
version "2.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
|
||||||
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
|
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
|
||||||
|
|
||||||
napi-build-utils@^1.0.1:
|
napi-build-utils@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508"
|
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508"
|
||||||
integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==
|
integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==
|
||||||
|
|
||||||
ngx-toastr@^10.2.0:
|
ngx-toastr@^12.0.1:
|
||||||
version "10.2.0"
|
version "12.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/ngx-toastr/-/ngx-toastr-10.2.0.tgz#8a79008de0b1c013f90120a53e0355af5762e969"
|
resolved "https://registry.yarnpkg.com/ngx-toastr/-/ngx-toastr-12.0.1.tgz#288c8ef505f1216aa4952cd2a8c6fa7c57a54ccc"
|
||||||
integrity sha512-6ASr5bcvQmtNKb4D2VEsQjCXyROq6GwberBWO0bVt+xcBYPUea4aRTgX8in9apX9buaTafzG+h3HlnIraspoPg==
|
integrity sha512-PABtbn2dyHweVSbo/py1W3veXzcmZO7uVItfTW9AykSSeAUju3gOCgauAw89km0aJ9EBcPOieaoI+9tAR7Pfug==
|
||||||
dependencies:
|
|
||||||
tslib "^1.9.0"
|
|
||||||
|
|
||||||
node-abi@^2.15.0, node-abi@^2.7.0:
|
node-abi@^2.16.0, node-abi@^2.7.0:
|
||||||
version "2.15.0"
|
version "2.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.15.0.tgz#51d55cc711bd9e4a24a572ace13b9231945ccb10"
|
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.16.0.tgz#7df94e9c0a7a189f4197ab84bac8089ef5894992"
|
||||||
integrity sha512-FeLpTS0F39U7hHZU1srAK4Vx+5AHNVOTP+hxBNQknR/54laTHSFIJkDWDqiquY1LeLUgTfPN7sLPhMubx0PLAg==
|
integrity sha512-+sa0XNlWDA6T+bDLmkCUYn6W5k5W6BPRL6mqzSCs6H/xUgtl4D5x2fORKDzopKiU6wsyn/+wXlRXwXeSp+mtoA==
|
||||||
dependencies:
|
dependencies:
|
||||||
semver "^5.4.1"
|
semver "^5.4.1"
|
||||||
|
|
||||||
@@ -2040,10 +2045,10 @@ node-gyp@^4.0.0:
|
|||||||
tar "^4.4.8"
|
tar "^4.4.8"
|
||||||
which "1"
|
which "1"
|
||||||
|
|
||||||
node-pty@^0.10.0-beta2:
|
node-pty@^0.10.0-beta9:
|
||||||
version "0.10.0-beta3"
|
version "0.10.0-beta9"
|
||||||
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta3.tgz#a33c9fc67c9e4d4f124111e1da2a72b0783008e7"
|
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.10.0-beta9.tgz#e5a795f9b53948346803cb71bac4ffc02e7909f0"
|
||||||
integrity sha512-j7MoJ3K999jrT9gAVs7JvM/skAQXQITrZK/PhL9B4W4GAPkANKwdu9uEtNvYionQ9dV8gRGte7lg9D2cRDdAiA==
|
integrity sha512-Qm6uSH30FUcAhJ9s76C+lgvTsOW2cHUbkIGjCdOVCL0c7S4DxsmKBRgjcr+guUK9d9KwfuZHeSjXYWjpJFPe4w==
|
||||||
dependencies:
|
dependencies:
|
||||||
nan "^2.14.0"
|
nan "^2.14.0"
|
||||||
|
|
||||||
@@ -2459,11 +2464,6 @@ pacote@^9.1.0, pacote@^9.2.3, pacote@^9.5.0:
|
|||||||
unique-filename "^1.1.1"
|
unique-filename "^1.1.1"
|
||||||
which "^1.3.1"
|
which "^1.3.1"
|
||||||
|
|
||||||
pako@^1.0.11:
|
|
||||||
version "1.0.11"
|
|
||||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
|
||||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
|
||||||
|
|
||||||
parallel-transform@^1.1.0:
|
parallel-transform@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"
|
resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"
|
||||||
@@ -2865,15 +2865,15 @@ run-queue@^1.0.0, run-queue@^1.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
aproba "^1.1.1"
|
aproba "^1.1.1"
|
||||||
|
|
||||||
rxjs-compat@^6.5.4:
|
rxjs-compat@^6.5.5:
|
||||||
version "6.5.4"
|
version "6.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs-compat/-/rxjs-compat-6.5.4.tgz#03825692af3fe363e04c43f41ff4113d76bbd305"
|
resolved "https://registry.yarnpkg.com/rxjs-compat/-/rxjs-compat-6.5.5.tgz#073c40510f29c45a2a5fc02dde87f8c3ad75f2c2"
|
||||||
integrity sha512-rkn+lbOHUQOurdd74J/hjmDsG9nFx0z66fvnbs8M95nrtKvNqCKdk7iZqdY51CGmDemTQk+kUPy4s8HVOHtkfA==
|
integrity sha512-F42sssVbUyWH4vJswEo6m+Eh02xHv3q93n8S7nUJO58R7sbc3CvJIOts605zdaBhWa1xMB9aVSyqPqhQ5q3eXg==
|
||||||
|
|
||||||
rxjs@^6.5.4:
|
rxjs@^6.5.5:
|
||||||
version "6.5.4"
|
version "6.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec"
|
||||||
integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
|
integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^1.9.0"
|
tslib "^1.9.0"
|
||||||
|
|
||||||
@@ -3343,10 +3343,10 @@ unique-string@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
crypto-random-string "^1.0.0"
|
crypto-random-string "^1.0.0"
|
||||||
|
|
||||||
universalify@^0.1.0:
|
universalify@^1.0.0:
|
||||||
version "0.1.2"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d"
|
||||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==
|
||||||
|
|
||||||
unpipe@~1.0.0:
|
unpipe@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
@@ -3566,10 +3566,10 @@ yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
|
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
|
||||||
integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
|
integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
|
||||||
|
|
||||||
yargs-parser@^16.1.0:
|
yargs-parser@^18.1.1:
|
||||||
version "16.1.0"
|
version "18.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-16.1.0.tgz#73747d53ae187e7b8dbe333f95714c76ea00ecf1"
|
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.1.tgz#bf7407b915427fc760fcbbccc6c82b4f0ffcbd37"
|
||||||
integrity sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==
|
integrity sha512-KRHEsOM16IX7XuLnMOqImcPNbLVXMNHYAoFc3BKR8Ortl5gzDbtXvvEoGx9imk5E+X1VeNKNlcHr8B8vi+7ipA==
|
||||||
dependencies:
|
dependencies:
|
||||||
camelcase "^5.0.0"
|
camelcase "^5.0.0"
|
||||||
decamelize "^1.2.0"
|
decamelize "^1.2.0"
|
||||||
@@ -3599,10 +3599,10 @@ yargs@^11.0.0:
|
|||||||
y18n "^3.2.1"
|
y18n "^3.2.1"
|
||||||
yargs-parser "^9.0.2"
|
yargs-parser "^9.0.2"
|
||||||
|
|
||||||
yargs@^15.1.0:
|
yargs@^15.3.1:
|
||||||
version "15.1.0"
|
version "15.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.1.0.tgz#e111381f5830e863a89550bd4b136bb6a5f37219"
|
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b"
|
||||||
integrity sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg==
|
integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==
|
||||||
dependencies:
|
dependencies:
|
||||||
cliui "^6.0.0"
|
cliui "^6.0.0"
|
||||||
decamelize "^1.2.0"
|
decamelize "^1.2.0"
|
||||||
@@ -3614,9 +3614,9 @@ yargs@^15.1.0:
|
|||||||
string-width "^4.2.0"
|
string-width "^4.2.0"
|
||||||
which-module "^2.0.0"
|
which-module "^2.0.0"
|
||||||
y18n "^4.0.0"
|
y18n "^4.0.0"
|
||||||
yargs-parser "^16.1.0"
|
yargs-parser "^18.1.1"
|
||||||
|
|
||||||
zone.js@^0.10.2:
|
zone.js@^0.10.3:
|
||||||
version "0.10.2"
|
version "0.10.3"
|
||||||
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.10.2.tgz#67ca084b3116fc33fc40435e0d5ea40a207e392e"
|
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.10.3.tgz#3e5e4da03c607c9dcd92e37dd35687a14a140c16"
|
||||||
integrity sha512-UAYfiuvxLN4oyuqhJwd21Uxb4CNawrq6fPS/05Su5L4G+1TN+HVDJMUHNMobVQDFJRir2cLAODXwluaOKB7HFg==
|
integrity sha512-LXVLVEq0NNOqK/fLJo3d0kfzd4sxwn2/h67/02pjCjfKDxgx1i9QqpvtHD8CrBnSSwMw5+dy11O7FRX5mkO7Cg==
|
||||||
|
16
build/mac/afterBuildHook.js
Normal file
16
build/mac/afterBuildHook.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const signHook = require('./afterSignHook')
|
||||||
|
|
||||||
|
module.exports = async function (params) {
|
||||||
|
// notarize the app on Mac OS only.
|
||||||
|
if (process.platform !== 'darwin' || !process.env.GITHUB_REF || !process.env.GITHUB_REF.startsWith('refs/tags/')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('afterBuild hook triggered')
|
||||||
|
|
||||||
|
let pkgName = fs.readdirSync('dist').find(x => x.endsWith('.pkg'))
|
||||||
|
signHook({
|
||||||
|
appOutDir: 'dist',
|
||||||
|
_pathOverride: pkgName,
|
||||||
|
})
|
||||||
|
}
|
@@ -6,14 +6,14 @@ const notarizer = require('electron-notarize')
|
|||||||
|
|
||||||
module.exports = async function (params) {
|
module.exports = async function (params) {
|
||||||
// notarize the app on Mac OS only.
|
// notarize the app on Mac OS only.
|
||||||
if (process.platform !== 'darwin' || process.env.GITHUB_REF !== 'refs/heads/master' || process.env.GITHUB_REF && !process.env.GITHUB_REF.startsWith('refs/tags/')) {
|
if (process.platform !== 'darwin' || !process.env.GITHUB_REF || !process.env.GITHUB_REF.startsWith('refs/tags/')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log('afterSign hook triggered', params)
|
console.log('afterSign hook triggered', params)
|
||||||
|
|
||||||
let appId = 'org.terminus'
|
let appId = 'org.terminus'
|
||||||
|
|
||||||
let appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`)
|
let appPath = path.join(params.appOutDir, params._pathOverride || `${params.packager.appInfo.productFilename}.app`)
|
||||||
if (!fs.existsSync(appPath)) {
|
if (!fs.existsSync(appPath)) {
|
||||||
throw new Error(`Cannot find application at: ${appPath}`)
|
throw new Error(`Cannot find application at: ${appPath}`)
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ appId: org.terminus
|
|||||||
productName: Terminus
|
productName: Terminus
|
||||||
compression: normal
|
compression: normal
|
||||||
afterSign: "./build/mac/afterSignHook.js"
|
afterSign: "./build/mac/afterSignHook.js"
|
||||||
|
afterAllArtifactBuild: "./build/mac/afterBuildHook.js"
|
||||||
files:
|
files:
|
||||||
- "**/*"
|
- "**/*"
|
||||||
- dist
|
- dist
|
||||||
@@ -48,6 +49,7 @@ deb:
|
|||||||
depends:
|
depends:
|
||||||
- gconf2
|
- gconf2
|
||||||
- gconf-service
|
- gconf-service
|
||||||
|
- gnome-keyring
|
||||||
- libnotify4
|
- libnotify4
|
||||||
- libsecret-1-0
|
- libsecret-1-0
|
||||||
- libappindicator1
|
- libappindicator1
|
||||||
@@ -57,4 +59,4 @@ deb:
|
|||||||
rpm:
|
rpm:
|
||||||
depends:
|
depends:
|
||||||
- screen
|
- screen
|
||||||
- gnome-python2-gnomekeyring
|
- gnome-keyring
|
||||||
|
52
package.json
52
package.json
@@ -1,35 +1,35 @@
|
|||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.12.1",
|
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||||
"@sentry/cli": "^1.51.1",
|
"@sentry/cli": "^1.52.3",
|
||||||
"@sentry/electron": "^1.2.1",
|
"@sentry/electron": "^1.2.1",
|
||||||
"@types/electron-config": "^3.2.2",
|
"@types/electron-config": "^3.2.2",
|
||||||
"@types/electron-debug": "^2.1.0",
|
"@types/electron-debug": "^2.1.0",
|
||||||
"@types/js-yaml": "^3.12.1",
|
"@types/js-yaml": "^3.12.4",
|
||||||
"@types/node": "12.7.12",
|
"@types/node": "12.7.12",
|
||||||
"@types/webpack-env": "1.15.0",
|
"@types/webpack-env": "^1.15.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.21.0",
|
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||||
"@typescript-eslint/parser": "^2.21.0",
|
"@typescript-eslint/parser": "^2.34.0",
|
||||||
"apply-loader": "2.0.0",
|
"apply-loader": "2.0.0",
|
||||||
"awesome-typescript-loader": "^5.0.0",
|
"awesome-typescript-loader": "^5.0.0",
|
||||||
"core-js": "^3.6.4",
|
"core-js": "^3.6.5",
|
||||||
"cross-env": "7.0.0",
|
"cross-env": "7.0.2",
|
||||||
"css-loader": "3.4.2",
|
"css-loader": "3.4.2",
|
||||||
"electron": "^8.0.2",
|
"electron": "^8.2.5",
|
||||||
"electron-builder": "22.3.2",
|
"electron-builder": "22.6.1",
|
||||||
"electron-download": "^4.1.1",
|
"electron-download": "^4.1.1",
|
||||||
"electron-installer-snap": "^5.0.0",
|
"electron-installer-snap": "^5.0.0",
|
||||||
"electron-notarize": "^0.1.1",
|
"electron-notarize": "^0.1.1",
|
||||||
"electron-rebuild": "^1.9.0",
|
"electron-rebuild": "^1.10.1",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-plugin-import": "^2.20.1",
|
"eslint-plugin-import": "^2.20.2",
|
||||||
"file-loader": "^5.0.2",
|
"file-loader": "^5.0.2",
|
||||||
"graceful-fs": "^4.2.2",
|
"graceful-fs": "^4.2.4",
|
||||||
"html-loader": "0.5.5",
|
"html-loader": "0.5.5",
|
||||||
"json-loader": "0.5.7",
|
"json-loader": "0.5.7",
|
||||||
"node-abi": "^2.15.0",
|
"node-abi": "^2.16.0",
|
||||||
"node-gyp": "^6.1.0",
|
"node-gyp": "^6.1.0",
|
||||||
"node-sass": "^4.13.0",
|
"node-sass": "^4.14.1",
|
||||||
"npmlog": "4.1.2",
|
"npmlog": "4.1.2",
|
||||||
"npx": "^10.2.0",
|
"npx": "^10.2.0",
|
||||||
"pug": "^2.0.4",
|
"pug": "^2.0.4",
|
||||||
@@ -37,22 +37,22 @@
|
|||||||
"pug-lint": "^2.6.0",
|
"pug-lint": "^2.6.0",
|
||||||
"pug-loader": "^2.4.0",
|
"pug-loader": "^2.4.0",
|
||||||
"pug-static-loader": "2.0.0",
|
"pug-static-loader": "2.0.0",
|
||||||
"raw-loader": "4.0.0",
|
"raw-loader": "4.0.1",
|
||||||
"sass-loader": "^8.0.0",
|
"sass-loader": "^8.0.0",
|
||||||
"shelljs": "0.8.3",
|
"shelljs": "0.8.4",
|
||||||
"source-code-pro": "^2.30.2",
|
"source-code-pro": "^2.30.2",
|
||||||
"source-sans-pro": "3.6.0",
|
"source-sans-pro": "3.6.0",
|
||||||
"style-loader": "^1.1.3",
|
"style-loader": "^1.1.4",
|
||||||
"svg-inline-loader": "^0.8.0",
|
"svg-inline-loader": "^0.8.0",
|
||||||
"to-string-loader": "1.1.6",
|
"to-string-loader": "1.1.6",
|
||||||
"tslib": "^1.11.1",
|
"tslib": "^2.0.0",
|
||||||
"typedoc": "^0.16.10",
|
"typedoc": "^0.17.6",
|
||||||
"typescript": "^3.8.2",
|
"typescript": "^3.9.3",
|
||||||
"url-loader": "^3.0.0",
|
"url-loader": "^3.0.0",
|
||||||
"val-loader": "2.1.0",
|
"val-loader": "2.1.1",
|
||||||
"webpack": "^5.0.0-beta.13",
|
"webpack": "^5.0.0-beta.16",
|
||||||
"webpack-cli": "^3.3.10",
|
"webpack-cli": "^3.3.10",
|
||||||
"yaml-loader": "0.5.0"
|
"yaml-loader": "0.6.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"*/node-abi": "^2.14.0"
|
"*/node-abi": "^2.14.0"
|
||||||
@@ -67,5 +67,7 @@
|
|||||||
"lint": "eslint --ext ts */src */lib",
|
"lint": "eslint --ext ts */src */lib",
|
||||||
"postinstall": "node ./scripts/install-deps.js"
|
"postinstall": "node ./scripts/install-deps.js"
|
||||||
},
|
},
|
||||||
"repository": "eugeny/terminus"
|
"repository": "eugeny/terminus",
|
||||||
|
"author": "Eugene Pankov",
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
const builder = require('electron-builder').build
|
const builder = require('electron-builder').build
|
||||||
const vars = require('./vars')
|
const vars = require('./vars')
|
||||||
|
const fs = require('fs')
|
||||||
|
const signHook = require('../build/mac/afterSignHook')
|
||||||
|
|
||||||
const isTag = (process.env.GITHUB_REF || '').startsWith('refs/tags/')
|
const isTag = (process.env.GITHUB_REF || '').startsWith('refs/tags/')
|
||||||
const isCI = !!process.env.GITHUB_REF
|
|
||||||
|
|
||||||
builder({
|
builder({
|
||||||
dir: true,
|
dir: true,
|
||||||
@@ -14,4 +15,7 @@ builder({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
publish: isTag ? 'always' : 'onTag',
|
publish: isTag ? 'always' : 'onTag',
|
||||||
}).catch(() => process.exit(1))
|
}).catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
@@ -30,7 +30,7 @@
|
|||||||
"ng2-dnd": "^5.0.2",
|
"ng2-dnd": "^5.0.2",
|
||||||
"ngx-perfect-scrollbar": "^8.0.0",
|
"ngx-perfect-scrollbar": "^8.0.0",
|
||||||
"shell-escape": "^0.2.0",
|
"shell-escape": "^0.2.0",
|
||||||
"uuid": "^7.0.1",
|
"uuid": "^8.0.0",
|
||||||
"winston": "^3.2.1"
|
"winston": "^3.2.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
export interface SelectorOption<T> {
|
export interface SelectorOption<T> {
|
||||||
name: string
|
name: string
|
||||||
description?: string
|
description?: string
|
||||||
result: T
|
result?: T
|
||||||
|
icon?: string
|
||||||
|
freeInputPattern?: string
|
||||||
|
callback?: (string?) => void
|
||||||
}
|
}
|
||||||
|
@@ -114,6 +114,9 @@ export class AppRootComponent {
|
|||||||
if (hotkey === 'move-tab-right') {
|
if (hotkey === 'move-tab-right') {
|
||||||
this.app.moveSelectedTabRight()
|
this.app.moveSelectedTabRight()
|
||||||
}
|
}
|
||||||
|
if (hotkey === 'reopen-tab') {
|
||||||
|
this.app.reopenLastTab()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (hotkey === 'toggle-fullscreen') {
|
if (hotkey === 'toggle-fullscreen') {
|
||||||
this.hostApp.toggleFullscreen()
|
this.hostApp.toggleFullscreen()
|
||||||
@@ -125,17 +128,8 @@ export class AppRootComponent {
|
|||||||
this.docking.dock()
|
this.docking.dock()
|
||||||
})
|
})
|
||||||
|
|
||||||
this.hostApp.secondInstance$.subscribe(() => {
|
|
||||||
this.presentWindow()
|
|
||||||
})
|
|
||||||
this.hotkeys.globalHotkey.subscribe(() => {
|
|
||||||
this.onGlobalHotkey()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.hostApp.windowCloseRequest$.subscribe(async () => {
|
this.hostApp.windowCloseRequest$.subscribe(async () => {
|
||||||
if (await this.app.closeAllTabs()) {
|
this.app.closeWindow()
|
||||||
this.hostApp.closeWindow()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (window['safeModeReason']) {
|
if (window['safeModeReason']) {
|
||||||
@@ -174,41 +168,6 @@ export class AppRootComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onGlobalHotkey () {
|
|
||||||
if (this.hostApp.getWindow().isFocused()) {
|
|
||||||
this.hideWindow()
|
|
||||||
} else {
|
|
||||||
this.presentWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
presentWindow () {
|
|
||||||
if (!this.hostApp.getWindow().isVisible()) {
|
|
||||||
// unfocused, invisible
|
|
||||||
this.hostApp.getWindow().show()
|
|
||||||
this.hostApp.getWindow().focus()
|
|
||||||
} else {
|
|
||||||
if (this.config.store.appearance.dock === 'off') {
|
|
||||||
// not docked, visible
|
|
||||||
setTimeout(() => {
|
|
||||||
this.hostApp.getWindow().show()
|
|
||||||
this.hostApp.getWindow().focus()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// docked, visible
|
|
||||||
this.hostApp.getWindow().hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hideWindow () {
|
|
||||||
this.electron.loseFocus()
|
|
||||||
this.hostApp.getWindow().blur()
|
|
||||||
if (this.hostApp.platform !== Platform.macOS) {
|
|
||||||
this.hostApp.getWindow().hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
this.ready = true
|
this.ready = true
|
||||||
|
|
||||||
|
@@ -14,6 +14,11 @@ export interface BaseTabProcess {
|
|||||||
* Abstract base class for custom tab components
|
* Abstract base class for custom tab components
|
||||||
*/
|
*/
|
||||||
export abstract class BaseTabComponent {
|
export abstract class BaseTabComponent {
|
||||||
|
/**
|
||||||
|
* Parent tab (usually a SplitTabComponent)
|
||||||
|
*/
|
||||||
|
parent: BaseTabComponent|null = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current tab title
|
* Current tab title
|
||||||
*/
|
*/
|
||||||
@@ -63,7 +68,7 @@ export abstract class BaseTabComponent {
|
|||||||
get destroyed$ (): Observable<void> { return this.destroyed }
|
get destroyed$ (): Observable<void> { return this.destroyed }
|
||||||
get recoveryStateChangedHint$ (): Observable<void> { return this.recoveryStateChangedHint }
|
get recoveryStateChangedHint$ (): Observable<void> { return this.recoveryStateChangedHint }
|
||||||
|
|
||||||
constructor () {
|
protected constructor () {
|
||||||
this.focused$.subscribe(() => {
|
this.focused$.subscribe(() => {
|
||||||
this.hasFocus = true
|
this.hasFocus = true
|
||||||
})
|
})
|
||||||
|
@@ -4,15 +4,23 @@
|
|||||||
[(ngModel)]='filter',
|
[(ngModel)]='filter',
|
||||||
autofocus,
|
autofocus,
|
||||||
[placeholder]='name',
|
[placeholder]='name',
|
||||||
(ngModelChange)='onFilterChange()',
|
(ngModelChange)='onFilterChange()'
|
||||||
(keyup.enter)='onFilterEnter()',
|
|
||||||
(keyup.escape)='close()'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
.list-group.mt-3(*ngIf='filteredOptions.length')
|
.list-group.mt-3(*ngIf='filteredOptions.length')
|
||||||
a.list-group-item.list-group-item-action.d-flex.align-items-center(
|
a.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||||
|
#item,
|
||||||
(click)='selectOption(option)',
|
(click)='selectOption(option)',
|
||||||
*ngFor='let option of filteredOptions'
|
[class.active]='selectedIndex == i',
|
||||||
|
*ngFor='let option of filteredOptions; let i = index'
|
||||||
)
|
)
|
||||||
.mr-2 {{option.name}}
|
i.icon(
|
||||||
|
class='fa-fw fas fa-{{option.icon}}',
|
||||||
|
*ngIf='!iconIsSVG(option.icon)'
|
||||||
|
)
|
||||||
|
.icon(
|
||||||
|
[fastHtmlBind]='option.icon',
|
||||||
|
*ngIf='iconIsSVG(option.icon)'
|
||||||
|
)
|
||||||
|
.mr-2.title {{getOptionText(option)}}
|
||||||
.text-muted {{option.description}}
|
.text-muted {{option.description}}
|
||||||
|
13
terminus-core/src/components/selectorModal.component.scss
Normal file
13
terminus-core/src/components/selectorModal.component.scss
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
.list-group {
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 1.25rem;
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
@@ -1,17 +1,19 @@
|
|||||||
import { Component, Input } from '@angular/core'
|
import { Component, Input, HostListener, ViewChildren, QueryList, ElementRef } from '@angular/core'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { SelectorOption } from '../api/selector'
|
import { SelectorOption } from '../api/selector'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
template: require('./selectorModal.component.pug'),
|
template: require('./selectorModal.component.pug'),
|
||||||
// styles: [require('./selectorModal.component.scss')],
|
styles: [require('./selectorModal.component.scss')],
|
||||||
})
|
})
|
||||||
export class SelectorModalComponent<T> {
|
export class SelectorModalComponent<T> {
|
||||||
@Input() options: SelectorOption<T>[]
|
@Input() options: SelectorOption<T>[]
|
||||||
@Input() filteredOptions: SelectorOption<T>[]
|
@Input() filteredOptions: SelectorOption<T>[]
|
||||||
@Input() filter = ''
|
@Input() filter = ''
|
||||||
@Input() name: string
|
@Input() name: string
|
||||||
|
@Input() selectedIndex = 0
|
||||||
|
@ViewChildren('item') itemChildren: QueryList<ElementRef>
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public modalInstance: NgbActiveModal,
|
public modalInstance: NgbActiveModal,
|
||||||
@@ -21,27 +23,56 @@ export class SelectorModalComponent<T> {
|
|||||||
this.onFilterChange()
|
this.onFilterChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@HostListener('keyup', ['$event']) onKeyUp (event: KeyboardEvent): void {
|
||||||
|
if (event.key === 'ArrowUp') {
|
||||||
|
this.selectedIndex--
|
||||||
|
}
|
||||||
|
if (event.key === 'ArrowDown') {
|
||||||
|
this.selectedIndex++
|
||||||
|
}
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
this.selectOption(this.filteredOptions[this.selectedIndex])
|
||||||
|
}
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedIndex = (this.selectedIndex + this.filteredOptions.length) % this.filteredOptions.length
|
||||||
|
Array.from(this.itemChildren)[this.selectedIndex]?.nativeElement.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'nearest',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onFilterChange (): void {
|
onFilterChange (): void {
|
||||||
const f = this.filter.trim().toLowerCase()
|
const f = this.filter.trim().toLowerCase()
|
||||||
if (!f) {
|
if (!f) {
|
||||||
this.filteredOptions = this.options
|
this.filteredOptions = this.options.filter(x => !x.freeInputPattern)
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||||
this.filteredOptions = this.options.filter(x => (x.name + (x.description || '')).toLowerCase().includes(f))
|
this.filteredOptions = this.options.filter(x => x.freeInputPattern || (x.name + (x.description || '')).toLowerCase().includes(f))
|
||||||
}
|
}
|
||||||
|
this.selectedIndex = Math.max(0, this.selectedIndex)
|
||||||
|
this.selectedIndex = Math.min(this.filteredOptions.length - 1, this.selectedIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilterEnter (): void {
|
getOptionText (option: SelectorOption<T>): string {
|
||||||
if (this.filteredOptions.length === 1) {
|
if (option.freeInputPattern) {
|
||||||
this.selectOption(this.filteredOptions[0])
|
return option.freeInputPattern.replace('%s', this.filter)
|
||||||
}
|
}
|
||||||
|
return option.name
|
||||||
}
|
}
|
||||||
|
|
||||||
selectOption (option: SelectorOption<T>): void {
|
selectOption (option: SelectorOption<T>): void {
|
||||||
|
option.callback?.(this.filter)
|
||||||
this.modalInstance.close(option.result)
|
this.modalInstance.close(option.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
close (): void {
|
close (): void {
|
||||||
this.modalInstance.dismiss()
|
this.modalInstance.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iconIsSVG (icon: string): boolean {
|
||||||
|
return icon?.startsWith('<')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -324,7 +324,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
* Inserts a new `tab` to the `side` of the `relative` tab
|
* Inserts a new `tab` to the `side` of the `relative` tab
|
||||||
*/
|
*/
|
||||||
async addTab (tab: BaseTabComponent, relative: BaseTabComponent|null, side: SplitDirection): Promise<void> {
|
async addTab (tab: BaseTabComponent, relative: BaseTabComponent|null, side: SplitDirection): Promise<void> {
|
||||||
await this.initialized$.toPromise()
|
tab.parent = this
|
||||||
|
|
||||||
let target = (relative ? this.getParentOf(relative) : null) || this.root
|
let target = (relative ? this.getParentOf(relative) : null) || this.root
|
||||||
let insertIndex = relative ? target.children.indexOf(relative) : -1
|
let insertIndex = relative ? target.children.indexOf(relative) : -1
|
||||||
@@ -355,6 +355,9 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
target.children.splice(insertIndex, 0, tab)
|
target.children.splice(insertIndex, 0, tab)
|
||||||
|
|
||||||
this.recoveryStateChangedHint.next()
|
this.recoveryStateChangedHint.next()
|
||||||
|
|
||||||
|
await this.initialized$.toPromise()
|
||||||
|
|
||||||
this.attachTabView(tab)
|
this.attachTabView(tab)
|
||||||
|
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
@@ -374,11 +377,11 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
parent.children.splice(index, 1)
|
parent.children.splice(index, 1)
|
||||||
|
|
||||||
this.detachTabView(tab)
|
this.detachTabView(tab)
|
||||||
|
tab.parent = null
|
||||||
|
|
||||||
this.layout()
|
this.layout()
|
||||||
|
|
||||||
this.tabRemoved.next(tab)
|
this.tabRemoved.next(tab)
|
||||||
|
|
||||||
if (this.root.children.length === 0) {
|
if (this.root.children.length === 0) {
|
||||||
this.destroy()
|
this.destroy()
|
||||||
} else {
|
} else {
|
||||||
@@ -526,21 +529,24 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
if (child instanceof SplitContainer) {
|
if (child instanceof SplitContainer) {
|
||||||
this.layoutInternal(child, childX, childY, childW, childH)
|
this.layoutInternal(child, childX, childY, childW, childH)
|
||||||
} else {
|
} else {
|
||||||
const element = this.viewRefs.get(child)!.rootNodes[0]
|
const viewRef = this.viewRefs.get(child)
|
||||||
element.classList.toggle('child', true)
|
if (viewRef) {
|
||||||
element.classList.toggle('maximized', child === this.maximizedTab)
|
const element = viewRef.rootNodes[0]
|
||||||
element.classList.toggle('minimized', this.maximizedTab && child !== this.maximizedTab)
|
element.classList.toggle('child', true)
|
||||||
element.classList.toggle('focused', child === this.focusedTab)
|
element.classList.toggle('maximized', child === this.maximizedTab)
|
||||||
element.style.left = `${childX}%`
|
element.classList.toggle('minimized', this.maximizedTab && child !== this.maximizedTab)
|
||||||
element.style.top = `${childY}%`
|
element.classList.toggle('focused', child === this.focusedTab)
|
||||||
element.style.width = `${childW}%`
|
element.style.left = `${childX}%`
|
||||||
element.style.height = `${childH}%`
|
element.style.top = `${childY}%`
|
||||||
|
element.style.width = `${childW}%`
|
||||||
|
element.style.height = `${childH}%`
|
||||||
|
|
||||||
if (child === this.maximizedTab) {
|
if (child === this.maximizedTab) {
|
||||||
element.style.left = '5%'
|
element.style.left = '5%'
|
||||||
element.style.top = '5%'
|
element.style.top = '5%'
|
||||||
element.style.width = '90%'
|
element.style.width = '90%'
|
||||||
element.style.height = '90%'
|
element.style.height = '90%'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
offset += sizes[i]
|
offset += sizes[i]
|
||||||
@@ -569,6 +575,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
if (recovered) {
|
if (recovered) {
|
||||||
const tab = this.tabsService.create(recovered.type, recovered.options)
|
const tab = this.tabsService.create(recovered.type, recovered.options)
|
||||||
children.push(tab)
|
children.push(tab)
|
||||||
|
tab.parent = this
|
||||||
this.attachTabView(tab)
|
this.attachTabView(tab)
|
||||||
} else {
|
} else {
|
||||||
state.ratios.splice(state.children.indexOf(childState), 0)
|
state.ratios.splice(state.children.indexOf(childState), 0)
|
||||||
|
@@ -13,7 +13,7 @@ import { ToolbarButton, ToolbarButtonProvider } from '../api'
|
|||||||
export class StartPageComponent {
|
export class StartPageComponent {
|
||||||
version: string
|
version: string
|
||||||
|
|
||||||
constructor (
|
private constructor (
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private domSanitizer: DomSanitizer,
|
private domSanitizer: DomSanitizer,
|
||||||
public homeBase: HomeBaseService,
|
public homeBase: HomeBaseService,
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
.mb-4
|
.container.mt-5.mb-5
|
||||||
.terminus-logo
|
.mb-4
|
||||||
h1.terminus-title Terminus
|
.terminus-logo
|
||||||
sup α
|
h1.terminus-title Terminus
|
||||||
|
sup α
|
||||||
|
|
||||||
.container
|
|
||||||
.text-center.mb-5 Thank you for downloading Terminus!
|
.text-center.mb-5 Thank you for downloading Terminus!
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
|
@@ -2,5 +2,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
flex: 0 1 500px;
|
flex: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
@@ -10,11 +10,9 @@ import { AppService } from '../services/app.service'
|
|||||||
styles: [require('./windowControls.component.scss')],
|
styles: [require('./windowControls.component.scss')],
|
||||||
})
|
})
|
||||||
export class WindowControlsComponent {
|
export class WindowControlsComponent {
|
||||||
constructor (public hostApp: HostAppService, public app: AppService) { }
|
private constructor (public hostApp: HostAppService, public app: AppService) { }
|
||||||
|
|
||||||
async closeWindow () {
|
async closeWindow () {
|
||||||
if (await this.app.closeAllTabs()) {
|
this.app.closeWindow()
|
||||||
this.hostApp.closeWindow()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,8 @@ hotkeys:
|
|||||||
- 'F11'
|
- 'F11'
|
||||||
close-tab:
|
close-tab:
|
||||||
- 'Ctrl-Shift-W'
|
- 'Ctrl-Shift-W'
|
||||||
|
reopen-tab:
|
||||||
|
- 'Ctrl-Shift-T'
|
||||||
toggle-last-tab: []
|
toggle-last-tab: []
|
||||||
rename-tab:
|
rename-tab:
|
||||||
- 'Ctrl-Shift-R'
|
- 'Ctrl-Shift-R'
|
||||||
|
@@ -7,6 +7,8 @@ hotkeys:
|
|||||||
- 'Ctrl+⌘+F'
|
- 'Ctrl+⌘+F'
|
||||||
close-tab:
|
close-tab:
|
||||||
- '⌘-W'
|
- '⌘-W'
|
||||||
|
reopen-tab:
|
||||||
|
- '⌘-Shift-T'
|
||||||
toggle-last-tab: []
|
toggle-last-tab: []
|
||||||
rename-tab:
|
rename-tab:
|
||||||
- '⌘-R'
|
- '⌘-R'
|
||||||
|
@@ -8,6 +8,8 @@ hotkeys:
|
|||||||
- 'Alt-Enter'
|
- 'Alt-Enter'
|
||||||
close-tab:
|
close-tab:
|
||||||
- 'Ctrl-Shift-W'
|
- 'Ctrl-Shift-W'
|
||||||
|
reopen-tab:
|
||||||
|
- 'Ctrl-Shift-T'
|
||||||
toggle-last-tab: []
|
toggle-last-tab: []
|
||||||
rename-tab:
|
rename-tab:
|
||||||
- 'Ctrl-Shift-R'
|
- 'Ctrl-Shift-R'
|
||||||
|
@@ -2,6 +2,8 @@ appearance:
|
|||||||
dock: off
|
dock: off
|
||||||
dockScreen: current
|
dockScreen: current
|
||||||
dockFill: 0.5
|
dockFill: 0.5
|
||||||
|
dockHideOnBlur: false
|
||||||
|
dockAlwaysOnTop: true
|
||||||
tabsLocation: top
|
tabsLocation: top
|
||||||
cycleTabs: true
|
cycleTabs: true
|
||||||
theme: Standard
|
theme: Standard
|
||||||
|
@@ -25,6 +25,10 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
|||||||
id: 'close-tab',
|
id: 'close-tab',
|
||||||
name: 'Close tab',
|
name: 'Close tab',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'reopen-tab',
|
||||||
|
name: 'Reopen last tab',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'toggle-last-tab',
|
id: 'toggle-last-tab',
|
||||||
name: 'Toggle last tab',
|
name: 'Toggle last tab',
|
||||||
|
@@ -36,7 +36,7 @@ import { ConfigService } from './services/config.service'
|
|||||||
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
|
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
|
||||||
import { CoreConfigProvider } from './config'
|
import { CoreConfigProvider } from './config'
|
||||||
import { AppHotkeyProvider } from './hotkeys'
|
import { AppHotkeyProvider } from './hotkeys'
|
||||||
import { TaskCompletionContextMenu, CommonOptionsContextMenu, CloseContextMenu } from './tabContextMenu'
|
import { TaskCompletionContextMenu, CommonOptionsContextMenu, TabManagementContextMenu } from './tabContextMenu'
|
||||||
|
|
||||||
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
||||||
import 'ng2-dnd/bundles/style.css'
|
import 'ng2-dnd/bundles/style.css'
|
||||||
@@ -54,7 +54,7 @@ const PROVIDERS = [
|
|||||||
{ provide: Theme, useClass: PaperTheme, multi: true },
|
{ provide: Theme, useClass: PaperTheme, multi: true },
|
||||||
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
|
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
|
||||||
{ provide: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true },
|
{ provide: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true },
|
||||||
{ provide: TabContextMenuItemProvider, useClass: CloseContextMenu, multi: true },
|
{ provide: TabContextMenuItemProvider, useClass: TabManagementContextMenu, multi: true },
|
||||||
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
||||||
{ provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
|
{ provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
|
||||||
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
|
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
|
||||||
|
@@ -8,6 +8,7 @@ import { BaseTabComponent } from '../components/baseTab.component'
|
|||||||
import { SplitTabComponent } from '../components/splitTab.component'
|
import { SplitTabComponent } from '../components/splitTab.component'
|
||||||
import { SelectorModalComponent } from '../components/selectorModal.component'
|
import { SelectorModalComponent } from '../components/selectorModal.component'
|
||||||
import { SelectorOption } from '../api/selector'
|
import { SelectorOption } from '../api/selector'
|
||||||
|
import { RecoveryToken } from '../api/tabRecovery'
|
||||||
|
|
||||||
import { ConfigService } from './config.service'
|
import { ConfigService } from './config.service'
|
||||||
import { HostAppService } from './hostApp.service'
|
import { HostAppService } from './hostApp.service'
|
||||||
@@ -49,6 +50,7 @@ export class AppService {
|
|||||||
|
|
||||||
private lastTabIndex = 0
|
private lastTabIndex = 0
|
||||||
private _activeTab: BaseTabComponent
|
private _activeTab: BaseTabComponent
|
||||||
|
private closedTabsStack: RecoveryToken[] = []
|
||||||
|
|
||||||
private activeTabChange = new Subject<BaseTabComponent>()
|
private activeTabChange = new Subject<BaseTabComponent>()
|
||||||
private tabsChanged = new Subject<void>()
|
private tabsChanged = new Subject<void>()
|
||||||
@@ -67,39 +69,44 @@ export class AppService {
|
|||||||
get ready$ (): Observable<void> { return this.ready }
|
get ready$ (): Observable<void> { return this.ready }
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (
|
private constructor (
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
private tabRecovery: TabRecoveryService,
|
private tabRecovery: TabRecoveryService,
|
||||||
private tabsService: TabsService,
|
private tabsService: TabsService,
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
) {
|
) {
|
||||||
if (hostApp.getWindow().id === 1) {
|
|
||||||
if (config.store.terminal.recoverTabs) {
|
|
||||||
this.tabRecovery.recoverTabs().then(tabs => {
|
|
||||||
for (const tab of tabs) {
|
|
||||||
this.openNewTabRaw(tab.type, tab.options)
|
|
||||||
}
|
|
||||||
this.startTabStorage()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
/** Continue to store the tabs even if the setting is currently off */
|
|
||||||
this.startTabStorage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hostApp.windowFocused$.subscribe(() => {
|
|
||||||
this._activeTab?.emitFocused()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
startTabStorage (): void {
|
|
||||||
this.tabsChanged$.subscribe(() => {
|
this.tabsChanged$.subscribe(() => {
|
||||||
this.tabRecovery.saveTabs(this.tabs)
|
this.tabRecovery.saveTabs(this.tabs)
|
||||||
})
|
})
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.tabRecovery.saveTabs(this.tabs)
|
this.tabRecovery.saveTabs(this.tabs)
|
||||||
}, 30000)
|
}, 30000)
|
||||||
|
|
||||||
|
if (hostApp.getWindow().id === 1) {
|
||||||
|
if (config.store.terminal.recoverTabs) {
|
||||||
|
this.tabRecovery.recoverTabs().then(tabs => {
|
||||||
|
for (const tab of tabs) {
|
||||||
|
this.openNewTabRaw(tab.type, tab.options)
|
||||||
|
}
|
||||||
|
this.tabRecovery.enabled = true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
/** Continue to store the tabs even if the setting is currently off */
|
||||||
|
this.tabRecovery.enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hostApp.windowFocused$.subscribe(() => {
|
||||||
|
this._activeTab?.emitFocused()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.tabClosed$.subscribe(async tab => {
|
||||||
|
const token = await tab.getRecoveryToken()
|
||||||
|
if (token) {
|
||||||
|
this.closedTabsStack.push(token)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addTabRaw (tab: BaseTabComponent, index: number|null = null): void {
|
addTabRaw (tab: BaseTabComponent, index: number|null = null): void {
|
||||||
@@ -163,6 +170,23 @@ export class AppService {
|
|||||||
return tab
|
return tab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async reopenLastTab (): Promise<BaseTabComponent|null> {
|
||||||
|
const token = this.closedTabsStack.pop()
|
||||||
|
if (token) {
|
||||||
|
const recoveredTab = await this.tabRecovery.recoverTab(token)
|
||||||
|
if (recoveredTab) {
|
||||||
|
const tab = this.tabsService.create(recoveredTab.type, recoveredTab.options)
|
||||||
|
if (this.activeTab) {
|
||||||
|
this.addTabRaw(tab, this.tabs.indexOf(this.activeTab) + 1)
|
||||||
|
} else {
|
||||||
|
this.addTabRaw(tab)
|
||||||
|
}
|
||||||
|
return tab
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
selectTab (tab: BaseTabComponent): void {
|
selectTab (tab: BaseTabComponent): void {
|
||||||
if (this._activeTab === tab) {
|
if (this._activeTab === tab) {
|
||||||
this._activeTab.emitFocused()
|
this._activeTab.emitFocused()
|
||||||
@@ -295,6 +319,16 @@ export class AppService {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async closeWindow (): Promise<void> {
|
||||||
|
this.tabRecovery.enabled = false
|
||||||
|
await this.tabRecovery.saveTabs(this.tabs)
|
||||||
|
if (await this.closeAllTabs()) {
|
||||||
|
this.hostApp.closeWindow()
|
||||||
|
} else {
|
||||||
|
this.tabRecovery.enabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
emitReady (): void {
|
emitReady (): void {
|
||||||
this.ready.next()
|
this.ready.next()
|
||||||
|
@@ -102,7 +102,7 @@ export class ConfigService {
|
|||||||
get changed$ (): Observable<void> { return this.changed }
|
get changed$ (): Observable<void> { return this.changed }
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (
|
private constructor (
|
||||||
electron: ElectronService,
|
electron: ElectronService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
@Inject(ConfigProvider) configProviders: ConfigProvider[],
|
@Inject(ConfigProvider) configProviders: ConfigProvider[],
|
||||||
@@ -155,9 +155,11 @@ export class ConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save (): void {
|
save (): void {
|
||||||
|
// Scrub undefined values
|
||||||
|
this._store = JSON.parse(JSON.stringify(this._store))
|
||||||
fs.writeFileSync(this.path, yaml.safeDump(this._store), 'utf8')
|
fs.writeFileSync(this.path, yaml.safeDump(this._store), 'utf8')
|
||||||
this.emitChange()
|
this.emitChange()
|
||||||
this.hostApp.broadcastConfigChange()
|
this.hostApp.broadcastConfigChange(this.store)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -6,7 +6,7 @@ import { HostAppService, Bounds } from '../services/hostApp.service'
|
|||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class DockingService {
|
export class DockingService {
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (
|
private constructor (
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
@@ -53,7 +53,9 @@ export class DockingService {
|
|||||||
newBounds.y = display.bounds.y
|
newBounds.y = display.bounds.y
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hostApp.setAlwaysOnTop(true)
|
const alwaysOnTop = this.config.store.appearance.dockAlwaysOnTop
|
||||||
|
|
||||||
|
this.hostApp.setAlwaysOnTop(alwaysOnTop)
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
this.hostApp.setBounds(newBounds)
|
this.hostApp.setBounds(newBounds)
|
||||||
})
|
})
|
||||||
|
@@ -25,7 +25,7 @@ export class ElectronService {
|
|||||||
private electron: any
|
private electron: any
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor () {
|
private constructor () {
|
||||||
this.electron = require('electron')
|
this.electron = require('electron')
|
||||||
this.remote = this.electron.remote
|
this.remote = this.electron.remote
|
||||||
this.app = this.remote.app
|
this.app = this.remote.app
|
||||||
@@ -43,15 +43,6 @@ export class ElectronService {
|
|||||||
this.MenuItem = this.remote.MenuItem
|
this.MenuItem = this.remote.MenuItem
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes OS focus from Terminus' window
|
|
||||||
*/
|
|
||||||
loseFocus (): void {
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
this.remote.Menu.sendActionToFirstResponder('hide:')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async showMessageBox (
|
async showMessageBox (
|
||||||
browserWindow: Electron.BrowserWindow,
|
browserWindow: Electron.BrowserWindow,
|
||||||
options: Electron.MessageBoxOptions
|
options: Electron.MessageBoxOptions
|
||||||
|
@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'
|
|||||||
import { ElectronService } from './electron.service'
|
import { ElectronService } from './electron.service'
|
||||||
import { ConfigService } from './config.service'
|
import { ConfigService } from './config.service'
|
||||||
import * as mixpanel from 'mixpanel'
|
import * as mixpanel from 'mixpanel'
|
||||||
import * as uuidv4 from 'uuid/v4'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class HomeBaseService {
|
export class HomeBaseService {
|
||||||
@@ -11,7 +11,7 @@ export class HomeBaseService {
|
|||||||
mixpanel: any
|
mixpanel: any
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (
|
private constructor (
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
) {
|
) {
|
||||||
|
@@ -166,7 +166,6 @@ export class HostAppService {
|
|||||||
this.configChangeBroadcast.next()
|
this.configChangeBroadcast.next()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED) &&
|
isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED) &&
|
||||||
!isWindowsBuild(WIN_BUILD_FLUENT_BG_MOVE_BUG_FIXED)
|
!isWindowsBuild(WIN_BUILD_FLUENT_BG_MOVE_BUG_FIXED)
|
||||||
@@ -251,8 +250,8 @@ export class HostAppService {
|
|||||||
/**
|
/**
|
||||||
* Notifies other windows of config file changes
|
* Notifies other windows of config file changes
|
||||||
*/
|
*/
|
||||||
broadcastConfigChange (): void {
|
broadcastConfigChange (configStore: {[k: string]: any}): void {
|
||||||
this.electron.ipcRenderer.send('app:config-change')
|
this.electron.ipcRenderer.send('app:config-change', configStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
emitReady (): void {
|
emitReady (): void {
|
||||||
@@ -267,6 +266,10 @@ export class HostAppService {
|
|||||||
this.electron.ipcRenderer.send('window-close')
|
this.electron.ipcRenderer.send('window-close')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerGlobalHotkey (specs: string[]): void {
|
||||||
|
this.electron.ipcRenderer.send('app:register-global-hotkey', specs)
|
||||||
|
}
|
||||||
|
|
||||||
relaunch (): void {
|
relaunch (): void {
|
||||||
if (this.isPortable) {
|
if (this.isPortable) {
|
||||||
this.electron.app.relaunch({ execPath: process.env.PORTABLE_EXECUTABLE_FILE })
|
this.electron.app.relaunch({ execPath: process.env.PORTABLE_EXECUTABLE_FILE })
|
||||||
|
@@ -2,8 +2,9 @@ import { Injectable, Inject, NgZone, EventEmitter } from '@angular/core'
|
|||||||
import { Observable, Subject } from 'rxjs'
|
import { Observable, Subject } from 'rxjs'
|
||||||
import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
|
import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
|
||||||
import { stringifyKeySequence } from './hotkeys.util'
|
import { stringifyKeySequence } from './hotkeys.util'
|
||||||
import { ConfigService } from '../services/config.service'
|
import { ConfigService } from './config.service'
|
||||||
import { ElectronService } from '../services/electron.service'
|
import { ElectronService } from './electron.service'
|
||||||
|
import { HostAppService } from './hostApp.service'
|
||||||
|
|
||||||
export interface PartialHotkeyMatch {
|
export interface PartialHotkeyMatch {
|
||||||
id: string
|
id: string
|
||||||
@@ -30,7 +31,6 @@ export class HotkeysService {
|
|||||||
*/
|
*/
|
||||||
get hotkey$ (): Observable<string> { return this._hotkey }
|
get hotkey$ (): Observable<string> { return this._hotkey }
|
||||||
|
|
||||||
globalHotkey = new EventEmitter<void>()
|
|
||||||
private _hotkey = new Subject<string>()
|
private _hotkey = new Subject<string>()
|
||||||
private currentKeystrokes: EventBufferEntry[] = []
|
private currentKeystrokes: EventBufferEntry[] = []
|
||||||
private disabledLevel = 0
|
private disabledLevel = 0
|
||||||
@@ -38,6 +38,7 @@ export class HotkeysService {
|
|||||||
|
|
||||||
private constructor (
|
private constructor (
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
|
private hostApp: HostAppService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
@Inject(HotkeyProvider) private hotkeyProviders: HotkeyProvider[],
|
@Inject(HotkeyProvider) private hotkeyProviders: HotkeyProvider[],
|
||||||
@@ -182,6 +183,7 @@ export class HotkeysService {
|
|||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
value = [value]
|
value = [value]
|
||||||
}
|
}
|
||||||
|
const specs: string[] = []
|
||||||
value.forEach((item: string | string[]) => {
|
value.forEach((item: string | string[]) => {
|
||||||
item = typeof item === 'string' ? [item] : item
|
item = typeof item === 'string' ? [item] : item
|
||||||
|
|
||||||
@@ -190,13 +192,13 @@ export class HotkeysService {
|
|||||||
electronKeySpec = electronKeySpec.replace('⌘', 'Command')
|
electronKeySpec = electronKeySpec.replace('⌘', 'Command')
|
||||||
electronKeySpec = electronKeySpec.replace('⌥', 'Alt')
|
electronKeySpec = electronKeySpec.replace('⌥', 'Alt')
|
||||||
electronKeySpec = electronKeySpec.replace(/-/g, '+')
|
electronKeySpec = electronKeySpec.replace(/-/g, '+')
|
||||||
this.electron.globalShortcut.register(electronKeySpec, () => {
|
specs.push(electronKeySpec)
|
||||||
this.globalHotkey.emit()
|
|
||||||
})
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Could not register the global hotkey:', err)
|
console.error('Could not register the global hotkey:', err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.hostApp.registerGlobalHotkey(specs)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getHotkeysConfig () {
|
private getHotkeysConfig () {
|
||||||
|
@@ -65,7 +65,7 @@ export class LogService {
|
|||||||
private log: any
|
private log: any
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (electron: ElectronService) {
|
private constructor (electron: ElectronService) {
|
||||||
this.log = initializeWinston(electron)
|
this.log = initializeWinston(electron)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -33,7 +33,7 @@ export class ShellIntegrationService {
|
|||||||
command: 'paste "%V"',
|
command: 'paste "%V"',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
constructor (
|
private constructor (
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
) {
|
) {
|
||||||
|
@@ -8,8 +8,9 @@ import { ConfigService } from '../services/config.service'
|
|||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class TabRecoveryService {
|
export class TabRecoveryService {
|
||||||
logger: Logger
|
logger: Logger
|
||||||
|
enabled = false
|
||||||
|
|
||||||
constructor (
|
private constructor (
|
||||||
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[],
|
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[],
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
log: LogService
|
log: LogService
|
||||||
@@ -18,6 +19,9 @@ export class TabRecoveryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveTabs (tabs: BaseTabComponent[]): Promise<void> {
|
async saveTabs (tabs: BaseTabComponent[]): Promise<void> {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
window.localStorage.tabsRecovery = JSON.stringify(
|
window.localStorage.tabsRecovery = JSON.stringify(
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
tabs
|
tabs
|
||||||
@@ -25,8 +29,11 @@ export class TabRecoveryService {
|
|||||||
let token = tab.getRecoveryToken()
|
let token = tab.getRecoveryToken()
|
||||||
if (token) {
|
if (token) {
|
||||||
token = token.then(r => {
|
token = token.then(r => {
|
||||||
if (r && tab.color) {
|
if (r) {
|
||||||
r.tabColor = tab.color
|
r.tabTitle = tab.title
|
||||||
|
if (tab.color) {
|
||||||
|
r.tabColor = tab.color
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
})
|
})
|
||||||
@@ -45,6 +52,7 @@ export class TabRecoveryService {
|
|||||||
if (tab !== null) {
|
if (tab !== null) {
|
||||||
tab.options = tab.options || {}
|
tab.options = tab.options || {}
|
||||||
tab.options.color = token.tabColor || null
|
tab.options.color = token.tabColor || null
|
||||||
|
tab.options.title = token.tabTitle || ''
|
||||||
return tab
|
return tab
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@@ -8,7 +8,7 @@ export type TabComponentType = new (...args: any[]) => BaseTabComponent
|
|||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class TabsService {
|
export class TabsService {
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (
|
private constructor (
|
||||||
private componentFactoryResolver: ComponentFactoryResolver,
|
private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
private injector: Injector,
|
private injector: Injector,
|
||||||
private tabRecovery: TabRecoveryService,
|
private tabRecovery: TabRecoveryService,
|
||||||
|
@@ -7,7 +7,7 @@ export class ThemesService {
|
|||||||
private styleElement: HTMLElement|null = null
|
private styleElement: HTMLElement|null = null
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
constructor (
|
private constructor (
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
@Inject(Theme) private themes: Theme[],
|
@Inject(Theme) private themes: Theme[],
|
||||||
) {
|
) {
|
||||||
|
@@ -14,7 +14,7 @@ export class TouchbarService {
|
|||||||
private tabSegments: SegmentedControlSegment[] = []
|
private tabSegments: SegmentedControlSegment[] = []
|
||||||
private nsImageCache: {[id: string]: Electron.NativeImage} = {}
|
private nsImageCache: {[id: string]: Electron.NativeImage} = {}
|
||||||
|
|
||||||
constructor (
|
private constructor (
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
||||||
|
@@ -21,7 +21,7 @@ export class UpdaterService {
|
|||||||
private updateURL: string
|
private updateURL: string
|
||||||
private autoUpdater: AppUpdater
|
private autoUpdater: AppUpdater
|
||||||
|
|
||||||
constructor (
|
private constructor (
|
||||||
log: LogService,
|
log: LogService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
|
@@ -4,12 +4,13 @@ import { Subscription } from 'rxjs'
|
|||||||
import { AppService } from './services/app.service'
|
import { AppService } from './services/app.service'
|
||||||
import { BaseTabComponent } from './components/baseTab.component'
|
import { BaseTabComponent } from './components/baseTab.component'
|
||||||
import { TabHeaderComponent } from './components/tabHeader.component'
|
import { TabHeaderComponent } from './components/tabHeader.component'
|
||||||
|
import { SplitTabComponent, SplitDirection } from './components/splitTab.component'
|
||||||
import { TabContextMenuItemProvider } from './api/tabContextMenuProvider'
|
import { TabContextMenuItemProvider } from './api/tabContextMenuProvider'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CloseContextMenu extends TabContextMenuItemProvider {
|
export class TabManagementContextMenu extends TabContextMenuItemProvider {
|
||||||
weight = -5
|
weight = 99
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
@@ -19,7 +20,7 @@ export class CloseContextMenu extends TabContextMenuItemProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
|
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||||
let items = [
|
let items: Electron.MenuItemConstructorOptions[] = [
|
||||||
{
|
{
|
||||||
label: 'Close',
|
label: 'Close',
|
||||||
click: () => this.zone.run(() => {
|
click: () => this.zone.run(() => {
|
||||||
@@ -59,6 +60,24 @@ export class CloseContextMenu extends TabContextMenuItemProvider {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
} else {
|
||||||
|
if (tab.parent instanceof SplitTabComponent) {
|
||||||
|
const directions: SplitDirection[] = ['r', 'b', 'l', 't']
|
||||||
|
items.push({
|
||||||
|
label: 'Split',
|
||||||
|
submenu: directions.map(dir => ({
|
||||||
|
label: {
|
||||||
|
r: 'Right',
|
||||||
|
b: 'Down',
|
||||||
|
l: 'Left',
|
||||||
|
t: 'Up',
|
||||||
|
}[dir],
|
||||||
|
click: () => this.zone.run(() => {
|
||||||
|
(tab.parent as SplitTabComponent).splitTab(tab, dir)
|
||||||
|
}),
|
||||||
|
})) as Electron.MenuItemConstructorOptions[],
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
@@ -87,8 +106,10 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
|
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||||
|
let items: Electron.MenuItemConstructorOptions[] = []
|
||||||
if (tabHeader) {
|
if (tabHeader) {
|
||||||
return [
|
items = [
|
||||||
|
...items,
|
||||||
{
|
{
|
||||||
label: 'Rename',
|
label: 'Rename',
|
||||||
click: () => this.zone.run(() => tabHeader?.showRenameTabModal()),
|
click: () => this.zone.run(() => tabHeader?.showRenameTabModal()),
|
||||||
@@ -111,7 +132,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
return []
|
return items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,4 +20,8 @@ app-root {
|
|||||||
ssh-tab .content {
|
ssh-tab .content {
|
||||||
margin: 5px !important;
|
margin: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serial-tab .content {
|
||||||
|
margin: 5px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -246,7 +246,7 @@ ngb-tabset .tab-content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.list-group-item {
|
.list-group-item {
|
||||||
transition: 0.25s background;
|
transition: 0.0625s background;
|
||||||
|
|
||||||
i + * {
|
i + * {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
@@ -262,6 +262,29 @@ ngb-tabset .tab-content {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.list-group-light {
|
||||||
|
.list-group-item {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, .1);
|
||||||
|
|
||||||
|
&:not(.combi) {
|
||||||
|
padding: $list-group-item-padding-y $list-group-item-padding-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.list-group-item-action {
|
||||||
|
&:hover, &.active {
|
||||||
|
background: $list-group-hover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
checkbox i.on {
|
checkbox i.on {
|
||||||
color: $blue;
|
color: $blue;
|
||||||
}
|
}
|
||||||
@@ -392,3 +415,7 @@ search-panel {
|
|||||||
border-color: $nav-tabs-link-active-border-color;
|
border-color: $nav-tabs-link-active-border-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border-color: $list-group-border-color;
|
||||||
|
}
|
||||||
|
@@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
|
|
||||||
"@types/js-yaml@^3.9.0":
|
"@types/js-yaml@^3.9.0":
|
||||||
version "3.12.2"
|
version "3.12.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.2.tgz#a35a1809c33a68200fb6403d1ad708363c56470a"
|
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.4.tgz#7d3b534ec35a0585128e2d332db1403ebe057e25"
|
||||||
integrity sha512-0CFu/g4mDSNkodVwWijdlr8jH7RoplRWNgovjFLEZeT+QEbbZXjBmCe3HwaWheAlCbHwomTwzZoSedeOycABug==
|
integrity sha512-fYMgzN+9e28R81weVN49inn/u798ruU91En1ZnGvSZzCRc5jXx9B2EDhlRaWmcO1RIxFHL8AajRXzxDuJu93+A==
|
||||||
|
|
||||||
"@types/node@*":
|
"@types/node@*":
|
||||||
version "13.7.1"
|
version "13.7.1"
|
||||||
@@ -52,6 +52,11 @@ async@^2.6.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.11"
|
lodash "^4.17.11"
|
||||||
|
|
||||||
|
at-least-node@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
||||||
|
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
||||||
|
|
||||||
axios@^0.19.0:
|
axios@^0.19.0:
|
||||||
version "0.19.2"
|
version "0.19.2"
|
||||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
|
||||||
@@ -64,10 +69,10 @@ bootstrap@^4.1.3:
|
|||||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.4.1.tgz#8582960eea0c5cd2bede84d8b0baf3789c3e8b01"
|
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.4.1.tgz#8582960eea0c5cd2bede84d8b0baf3789c3e8b01"
|
||||||
integrity sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA==
|
integrity sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA==
|
||||||
|
|
||||||
builder-util-runtime@8.6.0:
|
builder-util-runtime@8.7.0:
|
||||||
version "8.6.0"
|
version "8.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.6.0.tgz#b7007c30126da9a90e99932128d2922c8c178649"
|
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.7.0.tgz#e48ad004835c8284662e8eaf47a53468c66e8e8d"
|
||||||
integrity sha512-WTDhTUVrm7zkFyd6Qn7AXgmWifjpZ/fYnEdV3XCOIDMNNb/KPddBTbQ8bUlxxVeuOYlhGpcLUypG+4USdGL1ww==
|
integrity sha512-G1AqqVM2vYTrSFR982c1NNzwXKrGLQjVjaZaWQdn4O6Z3YKjdMDofw88aD9jpyK9ZXkrCxR0tI3Qe9wNbyTlXg==
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^4.1.1"
|
debug "^4.1.1"
|
||||||
sax "^1.2.4"
|
sax "^1.2.4"
|
||||||
@@ -124,9 +129,9 @@ colorspace@1.1.x:
|
|||||||
text-hex "1.0.x"
|
text-hex "1.0.x"
|
||||||
|
|
||||||
core-js@^3.1.2:
|
core-js@^3.1.2:
|
||||||
version "3.6.4"
|
version "3.6.5"
|
||||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647"
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
|
||||||
integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==
|
integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
|
||||||
|
|
||||||
core-util-is@~1.0.0:
|
core-util-is@~1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
@@ -169,17 +174,16 @@ diagnostics@^1.1.1:
|
|||||||
kuler "1.0.x"
|
kuler "1.0.x"
|
||||||
|
|
||||||
electron-updater@^4.0.6:
|
electron-updater@^4.0.6:
|
||||||
version "4.2.2"
|
version "4.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.2.2.tgz#57e106bffad16f71b1ffa3968a52a1b71c8147e6"
|
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.3.1.tgz#9d485b6262bc56fcf7ee62b1dc1b3b105a3e96a7"
|
||||||
integrity sha512-e/OZhr5tLW0GcgmpR5wD0ImxgKMa8pPoNWRcwRyMzTL9pGej7+ORp0t9DtI5ZBHUbObIoEbrk+6EDGUGtJf+aA==
|
integrity sha512-UDC5AHCgeiHJYDYWZG/rsl1vdAFKqI/Lm7whN57LKAk8EfhTewhcEHzheRcncLgikMcQL8gFo1KeX51tf5a5Wg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/semver" "^7.1.0"
|
"@types/semver" "^7.1.0"
|
||||||
builder-util-runtime "8.6.0"
|
builder-util-runtime "8.7.0"
|
||||||
fs-extra "^8.1.0"
|
fs-extra "^9.0.0"
|
||||||
js-yaml "^3.13.1"
|
js-yaml "^3.13.1"
|
||||||
lazy-val "^1.0.4"
|
lazy-val "^1.0.4"
|
||||||
lodash.isequal "^4.5.0"
|
lodash.isequal "^4.5.0"
|
||||||
pako "^1.0.11"
|
|
||||||
semver "^7.1.3"
|
semver "^7.1.3"
|
||||||
|
|
||||||
enabled@1.0.x:
|
enabled@1.0.x:
|
||||||
@@ -228,14 +232,15 @@ follow-redirects@1.5.10:
|
|||||||
dependencies:
|
dependencies:
|
||||||
debug "=3.1.0"
|
debug "=3.1.0"
|
||||||
|
|
||||||
fs-extra@^8.1.0:
|
fs-extra@^9.0.0:
|
||||||
version "8.1.0"
|
version "9.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.0.tgz#b6afc31036e247b2466dc99c29ae797d5d4580a3"
|
||||||
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
|
integrity sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==
|
||||||
dependencies:
|
dependencies:
|
||||||
|
at-least-node "^1.0.0"
|
||||||
graceful-fs "^4.2.0"
|
graceful-fs "^4.2.0"
|
||||||
jsonfile "^4.0.0"
|
jsonfile "^6.0.1"
|
||||||
universalify "^0.1.0"
|
universalify "^1.0.0"
|
||||||
|
|
||||||
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||||
version "4.2.2"
|
version "4.2.2"
|
||||||
@@ -271,17 +276,19 @@ isarray@~1.0.0:
|
|||||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||||
|
|
||||||
js-yaml@^3.13.1, js-yaml@^3.9.0:
|
js-yaml@^3.13.1, js-yaml@^3.9.0:
|
||||||
version "3.13.1"
|
version "3.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
|
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
|
||||||
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
|
integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse "^1.0.7"
|
argparse "^1.0.7"
|
||||||
esprima "^4.0.0"
|
esprima "^4.0.0"
|
||||||
|
|
||||||
jsonfile@^4.0.0:
|
jsonfile@^6.0.1:
|
||||||
version "4.0.0"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179"
|
||||||
integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
|
integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==
|
||||||
|
dependencies:
|
||||||
|
universalify "^1.0.0"
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
@@ -353,11 +360,6 @@ one-time@0.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e"
|
resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e"
|
||||||
integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=
|
integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=
|
||||||
|
|
||||||
pako@^1.0.11:
|
|
||||||
version "1.0.11"
|
|
||||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
|
|
||||||
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
|
|
||||||
|
|
||||||
perfect-scrollbar@^1.4.0:
|
perfect-scrollbar@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.4.0.tgz#5d014ef9775e1f43058a1dbae9ed1daf0e7091f1"
|
resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.4.0.tgz#5d014ef9775e1f43058a1dbae9ed1daf0e7091f1"
|
||||||
@@ -456,20 +458,20 @@ triple-beam@^1.2.0, triple-beam@^1.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
|
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
|
||||||
integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
|
integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
|
||||||
|
|
||||||
universalify@^0.1.0:
|
universalify@^1.0.0:
|
||||||
version "0.1.2"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d"
|
||||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==
|
||||||
|
|
||||||
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||||
|
|
||||||
uuid@^7.0.1:
|
uuid@^8.0.0:
|
||||||
version "7.0.1"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.1.tgz#95ed6ff3d8c881cbf85f0f05cc3915ef994818ef"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d"
|
||||||
integrity sha512-yqjRXZzSJm9Dbl84H2VDHpM3zMjzSJQ+hn6C4zqd5ilW+7P4ZmLEEqwho9LjP+tGuZlF4xrHQXT0h9QZUS/pWA==
|
integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg==
|
||||||
|
|
||||||
winston-transport@^4.3.0:
|
winston-transport@^4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
|
@@ -34,7 +34,7 @@ export class PluginManagerService {
|
|||||||
private npmReady: Promise<void>
|
private npmReady: Promise<void>
|
||||||
private npm: any
|
private npm: any
|
||||||
|
|
||||||
constructor (
|
private constructor (
|
||||||
log: LogService,
|
log: LogService,
|
||||||
) {
|
) {
|
||||||
this.logger = log.create('pluginManager')
|
this.logger = log.create('pluginManager')
|
||||||
|
@@ -60,9 +60,9 @@ object-assign@^4.0.1:
|
|||||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||||
|
|
||||||
semver@^7.1.1:
|
semver@^7.1.1:
|
||||||
version "7.1.1"
|
version "7.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.1.tgz#29104598a197d6cbe4733eeecbe968f7b43a9667"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.2.2.tgz#d01432d74ed3010a20ffaf909d63a691520521cd"
|
||||||
integrity sha512-WfuG+fl6eh3eZ2qAf6goB7nhiCd7NPXhmyFxigB/TOkQyeLP8w8GsVehvtGNtnNmyboz4TgeK40B1Kbql/8c5A==
|
integrity sha512-Zo84u6o2PebMSK3zjJ6Zp5wi8VnQZnEaCP13Ul/lt1ANsLACxnJxq4EEm1PY94/por1Hm9+7xpIswdS5AkieMA==
|
||||||
|
|
||||||
thenify-all@^1.0.0:
|
thenify-all@^1.0.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
|
@@ -1,14 +1,13 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable, Injector } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
import { HotkeysService, ToolbarButtonProvider, ToolbarButton } from 'terminus-core'
|
import { HotkeysService, ToolbarButtonProvider, ToolbarButton } from 'terminus-core'
|
||||||
import { SerialModalComponent } from './components/serialModal.component'
|
import { SerialService } from './services/serial.service'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ButtonProvider extends ToolbarButtonProvider {
|
export class ButtonProvider extends ToolbarButtonProvider {
|
||||||
constructor (
|
constructor (
|
||||||
private ngbModal: NgbModal,
|
private injector: Injector,
|
||||||
hotkeys: HotkeysService,
|
hotkeys: HotkeysService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
@@ -20,7 +19,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activate () {
|
activate () {
|
||||||
this.ngbModal.open(SerialModalComponent)
|
this.injector.get(SerialService).showConnectionSelector()
|
||||||
}
|
}
|
||||||
|
|
||||||
provide (): ToolbarButton[] {
|
provide (): ToolbarButton[] {
|
||||||
|
@@ -1,32 +0,0 @@
|
|||||||
.modal-body
|
|
||||||
input.form-control(
|
|
||||||
type='text',
|
|
||||||
[(ngModel)]='quickTarget',
|
|
||||||
autofocus,
|
|
||||||
placeholder='Quick connect: path@baudrate',
|
|
||||||
(ngModelChange)='refresh()',
|
|
||||||
(keyup.enter)='quickConnect()'
|
|
||||||
)
|
|
||||||
|
|
||||||
.list-group.mt-3(*ngIf='lastConnection')
|
|
||||||
a.list-group-item.list-group-item-action.d-flex.align-items-center((click)='connect(lastConnection)')
|
|
||||||
i.fas.fa-fw.fa-history
|
|
||||||
.mr-auto {{lastConnection.name}}
|
|
||||||
button.btn.btn-outline-danger.btn-sm((click)='clearLastConnection(); $event.stopPropagation()')
|
|
||||||
i.fas.fa-trash
|
|
||||||
|
|
||||||
.list-group.mt-3.connections-list(*ngIf='connections.length')
|
|
||||||
a.list-group-item.list-group-item-action.d-flex.align-items-center(
|
|
||||||
*ngFor='let connection of connections',
|
|
||||||
(click)='connect(connection)'
|
|
||||||
)
|
|
||||||
.mr-2 {{connection.name}}
|
|
||||||
.text-muted {{connection.port}}
|
|
||||||
|
|
||||||
.list-group.mt-3(*ngIf='foundPorts.length')
|
|
||||||
a.list-group-item.list-group-item-action.d-flex.align-items-center(
|
|
||||||
(click)='connectFoundPort(port)',
|
|
||||||
*ngFor='let port of foundPorts'
|
|
||||||
)
|
|
||||||
.mr-2 {{port.name}}
|
|
||||||
.text-muted {{port.description}}
|
|
@@ -1,5 +0,0 @@
|
|||||||
.list-group.connections-list {
|
|
||||||
display: block;
|
|
||||||
max-height: 70vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
@@ -1,102 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
||||||
import { Component, NgZone } from '@angular/core'
|
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
import { ToastrService } from 'ngx-toastr'
|
|
||||||
import { ConfigService, AppService } from 'terminus-core'
|
|
||||||
import { SettingsTabComponent } from 'terminus-settings'
|
|
||||||
import { SerialService } from '../services/serial.service'
|
|
||||||
import { SerialConnection, SerialPortInfo, BAUD_RATES } from '../api'
|
|
||||||
import { SerialTabComponent } from './serialTab.component'
|
|
||||||
|
|
||||||
/** @hidden */
|
|
||||||
@Component({
|
|
||||||
template: require('./serialModal.component.pug'),
|
|
||||||
styles: [require('./serialModal.component.scss')],
|
|
||||||
})
|
|
||||||
export class SerialModalComponent {
|
|
||||||
connections: SerialConnection[]
|
|
||||||
quickTarget: string
|
|
||||||
lastConnection: SerialConnection|null = null
|
|
||||||
foundPorts: SerialPortInfo[] = []
|
|
||||||
|
|
||||||
constructor (
|
|
||||||
public modalInstance: NgbActiveModal,
|
|
||||||
private config: ConfigService,
|
|
||||||
private serial: SerialService,
|
|
||||||
private app: AppService,
|
|
||||||
private zone: NgZone,
|
|
||||||
private toastr: ToastrService,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
async ngOnInit () {
|
|
||||||
this.connections = this.config.store.serial.connections
|
|
||||||
if (window.localStorage.lastSerialConnection) {
|
|
||||||
this.lastConnection = JSON.parse(window.localStorage.lastSerialConnection)
|
|
||||||
}
|
|
||||||
this.foundPorts = await this.serial.listPorts()
|
|
||||||
}
|
|
||||||
|
|
||||||
quickConnect () {
|
|
||||||
let path = this.quickTarget
|
|
||||||
let baudrate = 115200
|
|
||||||
if (this.quickTarget.includes('@')) {
|
|
||||||
baudrate = parseInt(path.split('@')[1])
|
|
||||||
path = path.split('@')[0]
|
|
||||||
}
|
|
||||||
const connection: SerialConnection = {
|
|
||||||
name: this.quickTarget,
|
|
||||||
port: path,
|
|
||||||
baudrate: baudrate,
|
|
||||||
databits: 8,
|
|
||||||
parity: 'none',
|
|
||||||
rtscts: false,
|
|
||||||
stopbits: 1,
|
|
||||||
xany: false,
|
|
||||||
xoff: false,
|
|
||||||
xon: false,
|
|
||||||
}
|
|
||||||
window.localStorage.lastSerialConnection = JSON.stringify(connection)
|
|
||||||
this.connect(connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
clearLastConnection () {
|
|
||||||
window.localStorage.lastSerialConnection = null
|
|
||||||
this.lastConnection = null
|
|
||||||
}
|
|
||||||
|
|
||||||
async connect (connection: SerialConnection) {
|
|
||||||
this.close()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const tab = this.zone.run(() => this.app.openNewTab(
|
|
||||||
SerialTabComponent,
|
|
||||||
{ connection }
|
|
||||||
) as SerialTabComponent)
|
|
||||||
if (connection.color) {
|
|
||||||
(this.app.getParentTab(tab) || tab).color = connection.color
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
this.app.activeTab.emitFocused()
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
this.toastr.error(`Could not connect: ${error}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
manageConnections () {
|
|
||||||
this.close()
|
|
||||||
this.app.openNewTab(SettingsTabComponent, { activeTab: 'serial' })
|
|
||||||
}
|
|
||||||
|
|
||||||
close () {
|
|
||||||
this.modalInstance.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
async connectFoundPort (port: SerialPortInfo) {
|
|
||||||
const rate = await this.app.showSelector('Baud rate', BAUD_RATES.map(x => ({
|
|
||||||
name: x.toString(), result: x,
|
|
||||||
})))
|
|
||||||
this.quickTarget = `${port.name}@${rate}`
|
|
||||||
this.quickConnect()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -21,9 +21,9 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
|
|||||||
serialPort: any
|
serialPort: any
|
||||||
private homeEndSubscription: Subscription
|
private homeEndSubscription: Subscription
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
||||||
constructor (
|
constructor (
|
||||||
injector: Injector,
|
injector: Injector,
|
||||||
private serial: SerialService,
|
|
||||||
) {
|
) {
|
||||||
super(injector)
|
super(injector)
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.session = this.serial.createSession(this.connection)
|
this.session = this.injector.get(SerialService).createSession(this.connection)
|
||||||
this.session.serviceMessage$.subscribe(msg => {
|
this.session.serviceMessage$.subscribe(msg => {
|
||||||
this.write('\r\n' + colors.black.bgWhite(' serial ') + ' ' + msg + '\r\n')
|
this.write('\r\n' + colors.black.bgWhite(' serial ') + ' ' + msg + '\r\n')
|
||||||
this.session.resize(this.size.columns, this.size.rows)
|
this.session.resize(this.size.columns, this.size.rows)
|
||||||
@@ -80,11 +80,7 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
|
|||||||
spinner.start()
|
spinner.start()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.serialPort = await this.serial.connectSession(this.session, (message: string) => {
|
this.serialPort = await this.injector.get(SerialService).connectSession(this.session)
|
||||||
spinner.stop(true)
|
|
||||||
this.write(message + '\r\n')
|
|
||||||
spinner.start()
|
|
||||||
})
|
|
||||||
spinner.stop(true)
|
spinner.stop(true)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
spinner.stop(true)
|
spinner.stop(true)
|
||||||
|
@@ -8,7 +8,6 @@ import { SettingsTabProvider } from 'terminus-settings'
|
|||||||
import TerminusTerminalModule from 'terminus-terminal'
|
import TerminusTerminalModule from 'terminus-terminal'
|
||||||
|
|
||||||
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
|
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
|
||||||
import { SerialModalComponent } from './components/serialModal.component'
|
|
||||||
import { SerialSettingsTabComponent } from './components/serialSettingsTab.component'
|
import { SerialSettingsTabComponent } from './components/serialSettingsTab.component'
|
||||||
import { SerialTabComponent } from './components/serialTab.component'
|
import { SerialTabComponent } from './components/serialTab.component'
|
||||||
|
|
||||||
@@ -37,13 +36,11 @@ import { SerialHotkeyProvider } from './hotkeys'
|
|||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
EditConnectionModalComponent,
|
EditConnectionModalComponent,
|
||||||
SerialModalComponent,
|
|
||||||
SerialSettingsTabComponent,
|
SerialSettingsTabComponent,
|
||||||
SerialTabComponent,
|
SerialTabComponent,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
EditConnectionModalComponent,
|
EditConnectionModalComponent,
|
||||||
SerialModalComponent,
|
|
||||||
SerialSettingsTabComponent,
|
SerialSettingsTabComponent,
|
||||||
SerialTabComponent,
|
SerialTabComponent,
|
||||||
],
|
],
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
import { Injectable, NgZone } from '@angular/core'
|
import { Injectable, NgZone } from '@angular/core'
|
||||||
import SerialPort from 'serialport'
|
import SerialPort from 'serialport'
|
||||||
import { ToastrService } from 'ngx-toastr'
|
import { ToastrService } from 'ngx-toastr'
|
||||||
import { LogService } from 'terminus-core'
|
import { LogService, AppService, SelectorOption, ConfigService } from 'terminus-core'
|
||||||
import { SerialConnection, SerialSession, SerialPortInfo } from '../api'
|
import { SettingsTabComponent } from 'terminus-settings'
|
||||||
|
import { SerialConnection, SerialSession, SerialPortInfo, BAUD_RATES } from '../api'
|
||||||
|
import { SerialTabComponent } from '../components/serialTab.component'
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class SerialService {
|
export class SerialService {
|
||||||
@@ -10,6 +12,8 @@ export class SerialService {
|
|||||||
private log: LogService,
|
private log: LogService,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private toastr: ToastrService,
|
private toastr: ToastrService,
|
||||||
|
private app: AppService,
|
||||||
|
private config: ConfigService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async listPorts (): Promise<SerialPortInfo[]> {
|
async listPorts (): Promise<SerialPortInfo[]> {
|
||||||
@@ -25,7 +29,7 @@ export class SerialService {
|
|||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectSession (session: SerialSession, _?: (s: any) => void): Promise<SerialPort> {
|
async connectSession (session: SerialSession): Promise<SerialPort> {
|
||||||
const serial = new SerialPort(session.connection.port, { autoOpen: false, baudRate: session.connection.baudrate,
|
const serial = new SerialPort(session.connection.port, { autoOpen: false, baudRate: session.connection.baudrate,
|
||||||
dataBits: session.connection.databits, stopBits: session.connection.stopbits, parity: session.connection.parity,
|
dataBits: session.connection.databits, stopBits: session.connection.stopbits, parity: session.connection.parity,
|
||||||
rtscts: session.connection.rtscts, xon: session.connection.xon, xoff: session.connection.xoff,
|
rtscts: session.connection.rtscts, xon: session.connection.xon, xoff: session.connection.xoff,
|
||||||
@@ -57,4 +61,109 @@ export class SerialService {
|
|||||||
})
|
})
|
||||||
return serial
|
return serial
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async showConnectionSelector (): Promise<void> {
|
||||||
|
const options: SelectorOption<void>[] = []
|
||||||
|
const foundPorts = await this.listPorts()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const lastConnection = JSON.parse(window.localStorage.lastSerialConnection)
|
||||||
|
if (lastConnection) {
|
||||||
|
options.push({
|
||||||
|
name: lastConnection.name,
|
||||||
|
icon: 'history',
|
||||||
|
callback: () => this.connect(lastConnection),
|
||||||
|
})
|
||||||
|
options.push({
|
||||||
|
name: 'Clear last connection',
|
||||||
|
icon: 'eraser',
|
||||||
|
callback: () => {
|
||||||
|
window.localStorage.lastSerialConnection = null
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
for (const port of foundPorts) {
|
||||||
|
options.push({
|
||||||
|
name: port.name,
|
||||||
|
description: port.description,
|
||||||
|
icon: 'arrow-right',
|
||||||
|
callback: () => this.connectFoundPort(port),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const connection of this.config.store.serial.connections) {
|
||||||
|
options.push({
|
||||||
|
name: connection.name,
|
||||||
|
description: connection.port,
|
||||||
|
callback: () => this.connect(connection),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
name: 'Manage connections',
|
||||||
|
icon: 'cog',
|
||||||
|
callback: () => this.app.openNewTab(SettingsTabComponent, { activeTab: 'serial' }),
|
||||||
|
})
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
name: 'Quick connect',
|
||||||
|
freeInputPattern: 'Open device: %s...',
|
||||||
|
icon: 'arrow-right',
|
||||||
|
callback: query => this.quickConnect(query),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
await this.app.showSelector('Open a serial port', options)
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect (connection: SerialConnection): Promise<SerialTabComponent> {
|
||||||
|
try {
|
||||||
|
const tab = this.app.openNewTab(
|
||||||
|
SerialTabComponent,
|
||||||
|
{ connection }
|
||||||
|
) as SerialTabComponent
|
||||||
|
if (connection.color) {
|
||||||
|
(this.app.getParentTab(tab) || tab).color = connection.color
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
this.app.activeTab.emitFocused()
|
||||||
|
})
|
||||||
|
return tab
|
||||||
|
} catch (error) {
|
||||||
|
this.toastr.error(`Could not connect: ${error}`)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quickConnect (query: string): Promise<SerialTabComponent> {
|
||||||
|
let path = query
|
||||||
|
let baudrate = 115200
|
||||||
|
if (query.includes('@')) {
|
||||||
|
baudrate = parseInt(path.split('@')[1])
|
||||||
|
path = path.split('@')[0]
|
||||||
|
}
|
||||||
|
const connection: SerialConnection = {
|
||||||
|
name: query,
|
||||||
|
port: path,
|
||||||
|
baudrate: baudrate,
|
||||||
|
databits: 8,
|
||||||
|
parity: 'none',
|
||||||
|
rtscts: false,
|
||||||
|
stopbits: 1,
|
||||||
|
xany: false,
|
||||||
|
xoff: false,
|
||||||
|
xon: false,
|
||||||
|
}
|
||||||
|
window.localStorage.lastSerialConnection = JSON.stringify(connection)
|
||||||
|
return this.connect(connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
async connectFoundPort (port: SerialPortInfo): Promise<SerialTabComponent> {
|
||||||
|
const rate = await this.app.showSelector('Baud rate', BAUD_RATES.map(x => ({
|
||||||
|
name: x.toString(), result: x,
|
||||||
|
})))
|
||||||
|
return this.quickConnect(`${port.name}@${rate}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -88,7 +88,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
|||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title Transparency
|
.title Opacity
|
||||||
input(
|
input(
|
||||||
type='range',
|
type='range',
|
||||||
[(ngModel)]='config.store.appearance.opacity',
|
[(ngModel)]='config.store.appearance.opacity',
|
||||||
@@ -207,6 +207,15 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
|||||||
)
|
)
|
||||||
| {{screen.name}}
|
| {{screen.name}}
|
||||||
|
|
||||||
|
.form-line(*ngIf='config.store.appearance.dock != "off"')
|
||||||
|
.header
|
||||||
|
.title Dock always on top
|
||||||
|
.description Keep docked terminal always on top
|
||||||
|
toggle(
|
||||||
|
[(ngModel)]='config.store.appearance.dockAlwaysOnTop',
|
||||||
|
(ngModelChange)='config.save(); docking.dock()',
|
||||||
|
)
|
||||||
|
|
||||||
.form-line(*ngIf='config.store.appearance.dock != "off"')
|
.form-line(*ngIf='config.store.appearance.dock != "off"')
|
||||||
.header
|
.header
|
||||||
.title Docked terminal size
|
.title Docked terminal size
|
||||||
@@ -219,6 +228,15 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
|||||||
step='0.01'
|
step='0.01'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.form-line(*ngIf='config.store.appearance.dock != "off"')
|
||||||
|
.header
|
||||||
|
.title Hide dock on blur
|
||||||
|
.description Hides the docked terminal when you click away.
|
||||||
|
toggle(
|
||||||
|
[(ngModel)]='config.store.appearance.dockHideOnBlur',
|
||||||
|
(ngModelChange)='config.save(); ',
|
||||||
|
)
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title Debugging
|
.title Debugging
|
||||||
|
@@ -34,6 +34,8 @@ export interface SSHConnection {
|
|||||||
color?: string
|
color?: string
|
||||||
x11?: boolean
|
x11?: boolean
|
||||||
skipBanner?: boolean
|
skipBanner?: boolean
|
||||||
|
disableDynamicTitle?: boolean
|
||||||
|
jumpHost?: string
|
||||||
|
|
||||||
algorithms?: {[t: string]: string[]}
|
algorithms?: {[t: string]: string[]}
|
||||||
}
|
}
|
||||||
@@ -79,6 +81,7 @@ export class SSHSession extends BaseSession {
|
|||||||
ssh: Client
|
ssh: Client
|
||||||
forwardedPorts: ForwardedPort[] = []
|
forwardedPorts: ForwardedPort[] = []
|
||||||
logger: Logger
|
logger: Logger
|
||||||
|
jumpStream: any
|
||||||
|
|
||||||
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
||||||
private serviceMessage = new Subject<string>()
|
private serviceMessage = new Subject<string>()
|
||||||
@@ -86,6 +89,13 @@ export class SSHSession extends BaseSession {
|
|||||||
constructor (public connection: SSHConnection) {
|
constructor (public connection: SSHConnection) {
|
||||||
super()
|
super()
|
||||||
this.scripts = connection.scripts || []
|
this.scripts = connection.scripts || []
|
||||||
|
this.destroyed$.subscribe(() => {
|
||||||
|
for (const port of this.forwardedPorts) {
|
||||||
|
if (port.type === PortForwardType.Local) {
|
||||||
|
port.stopLocalListener()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async start (): Promise<void> {
|
async start (): Promise<void> {
|
||||||
@@ -237,14 +247,16 @@ export class SSHSession extends BaseSession {
|
|||||||
socket.destroy()
|
socket.destroy()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
stream.pipe(socket)
|
if (stream) {
|
||||||
socket.pipe(stream)
|
stream.pipe(socket)
|
||||||
stream.on('close', () => {
|
socket.pipe(stream)
|
||||||
socket.destroy()
|
stream.on('close', () => {
|
||||||
})
|
socket.destroy()
|
||||||
socket.on('close', () => {
|
})
|
||||||
stream.close()
|
socket.on('close', () => {
|
||||||
})
|
stream.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
@@ -290,7 +302,7 @@ export class SSHSession extends BaseSession {
|
|||||||
|
|
||||||
write (data: Buffer): void {
|
write (data: Buffer): void {
|
||||||
if (this.shell) {
|
if (this.shell) {
|
||||||
this.shell.write(data.toString())
|
this.shell.write(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,15 +1,14 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
import { HotkeysService, ToolbarButtonProvider, ToolbarButton } from 'terminus-core'
|
import { HotkeysService, ToolbarButtonProvider, ToolbarButton } from 'terminus-core'
|
||||||
import { SSHModalComponent } from './components/sshModal.component'
|
import { SSHService } from './services/ssh.service'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ButtonProvider extends ToolbarButtonProvider {
|
export class ButtonProvider extends ToolbarButtonProvider {
|
||||||
constructor (
|
constructor (
|
||||||
private ngbModal: NgbModal,
|
|
||||||
hotkeys: HotkeysService,
|
hotkeys: HotkeysService,
|
||||||
|
private ssh: SSHService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
hotkeys.matchedHotkey.subscribe(async (hotkey: string) => {
|
hotkeys.matchedHotkey.subscribe(async (hotkey: string) => {
|
||||||
@@ -20,7 +19,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activate () {
|
activate () {
|
||||||
this.ngbModal.open(SSHModalComponent)
|
this.ssh.showConnectionSelector()
|
||||||
}
|
}
|
||||||
|
|
||||||
provide (): ToolbarButton[] {
|
provide (): ToolbarButton[] {
|
||||||
|
@@ -70,6 +70,13 @@
|
|||||||
ngb-tab(id='advanced')
|
ngb-tab(id='advanced')
|
||||||
ng-template(ngbTabTitle) Advanced
|
ng-template(ngbTabTitle) Advanced
|
||||||
ng-template(ngbTabContent)
|
ng-template(ngbTabContent)
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Jump host
|
||||||
|
select.form-control([(ngModel)]='connection.jumpHost')
|
||||||
|
option([value]='null') None
|
||||||
|
option([value]='x.name', *ngFor='let x of config.store.ssh.connections') {{x.name}}
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title X11 forwarding
|
.title X11 forwarding
|
||||||
@@ -85,9 +92,16 @@
|
|||||||
placeholder='#000000'
|
placeholder='#000000'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Disable dynamic tab title
|
||||||
|
.description Connection name will be used as a title instead
|
||||||
|
toggle([(ngModel)]='connection.disableDynamicTitle')
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title Skip MoTD/banner
|
.title Skip MoTD/banner
|
||||||
|
.description Will prevent the SSH greeting from showing up
|
||||||
toggle([(ngModel)]='connection.skipBanner')
|
toggle([(ngModel)]='connection.skipBanner')
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { ElectronService, HostAppService } from 'terminus-core'
|
import { ElectronService, HostAppService, ConfigService } from 'terminus-core'
|
||||||
import { PasswordStorageService } from '../services/passwordStorage.service'
|
import { PasswordStorageService } from '../services/passwordStorage.service'
|
||||||
import { SSHConnection, LoginScript, SSHAlgorithmType } from '../api'
|
import { SSHConnection, LoginScript, SSHAlgorithmType } from '../api'
|
||||||
import { PromptModalComponent } from './promptModal.component'
|
import { PromptModalComponent } from './promptModal.component'
|
||||||
@@ -20,6 +20,7 @@ export class EditConnectionModalComponent {
|
|||||||
algorithms: {[id: string]: {[a: string]: boolean}} = {}
|
algorithms: {[id: string]: {[a: string]: boolean}} = {}
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
|
public config: ConfigService,
|
||||||
private modalInstance: NgbActiveModal,
|
private modalInstance: NgbActiveModal,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
|
@@ -1,32 +0,0 @@
|
|||||||
.modal-body
|
|
||||||
input.form-control(
|
|
||||||
type='text',
|
|
||||||
[(ngModel)]='quickTarget',
|
|
||||||
autofocus,
|
|
||||||
placeholder='Quick connect: [user@]host[:port]',
|
|
||||||
(ngModelChange)='refresh()',
|
|
||||||
(keyup.enter)='quickConnect()'
|
|
||||||
)
|
|
||||||
|
|
||||||
.list-group.mt-3(*ngIf='lastConnection')
|
|
||||||
a.list-group-item.list-group-item-action.d-flex.align-items-center((click)='connect(lastConnection)')
|
|
||||||
i.fas.fa-fw.fa-history
|
|
||||||
.mr-auto {{lastConnection.name}}
|
|
||||||
button.btn.btn-outline-danger.btn-sm((click)='clearLastConnection(); $event.stopPropagation()')
|
|
||||||
i.fas.fa-trash
|
|
||||||
|
|
||||||
.list-group.mt-3.connections-list(*ngIf='childGroups.length')
|
|
||||||
ng-container(*ngFor='let group of childGroups')
|
|
||||||
.list-group-item.list-group-item-action.d-flex.align-items-center(
|
|
||||||
(click)='groupCollapsed[group.name] = !groupCollapsed[group.name]'
|
|
||||||
)
|
|
||||||
.fa.fa-fw.fa-chevron-right(*ngIf='groupCollapsed[group.name]')
|
|
||||||
.fa.fa-fw.fa-chevron-down(*ngIf='!groupCollapsed[group.name]')
|
|
||||||
.ml-2 {{group.name || "Ungrouped"}}
|
|
||||||
ng-container(*ngIf='!groupCollapsed[group.name]')
|
|
||||||
.list-group-item.list-group-item-action.pl-5.d-flex.align-items-center(
|
|
||||||
*ngFor='let connection of group.connections',
|
|
||||||
(click)='connect(connection)'
|
|
||||||
)
|
|
||||||
.mr-2 {{connection.name}}
|
|
||||||
.text-muted {{connection.host}}
|
|
@@ -1,5 +0,0 @@
|
|||||||
.list-group.connections-list {
|
|
||||||
display: block;
|
|
||||||
max-height: 70vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
@@ -1,117 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
||||||
import { Component, NgZone } from '@angular/core'
|
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
import { ToastrService } from 'ngx-toastr'
|
|
||||||
import { ConfigService, AppService } from 'terminus-core'
|
|
||||||
import { SettingsTabComponent } from 'terminus-settings'
|
|
||||||
import { SSHConnection, SSHConnectionGroup } from '../api'
|
|
||||||
import { SSHTabComponent } from './sshTab.component'
|
|
||||||
|
|
||||||
/** @hidden */
|
|
||||||
@Component({
|
|
||||||
template: require('./sshModal.component.pug'),
|
|
||||||
styles: [require('./sshModal.component.scss')],
|
|
||||||
})
|
|
||||||
export class SSHModalComponent {
|
|
||||||
connections: SSHConnection[]
|
|
||||||
childFolders: SSHConnectionGroup[]
|
|
||||||
quickTarget: string
|
|
||||||
lastConnection: SSHConnection|null = null
|
|
||||||
childGroups: SSHConnectionGroup[]
|
|
||||||
groupCollapsed: {[id: string]: boolean} = {}
|
|
||||||
|
|
||||||
constructor (
|
|
||||||
public modalInstance: NgbActiveModal,
|
|
||||||
private config: ConfigService,
|
|
||||||
private app: AppService,
|
|
||||||
private toastr: ToastrService,
|
|
||||||
private zone: NgZone,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit () {
|
|
||||||
this.connections = this.config.store.ssh.connections
|
|
||||||
if (window.localStorage.lastConnection) {
|
|
||||||
this.lastConnection = JSON.parse(window.localStorage.lastConnection)
|
|
||||||
}
|
|
||||||
this.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
quickConnect () {
|
|
||||||
let user = 'root'
|
|
||||||
let host = this.quickTarget
|
|
||||||
let port = 22
|
|
||||||
if (host.includes('@')) {
|
|
||||||
[user, host] = host.split('@')
|
|
||||||
}
|
|
||||||
if (host.includes(':')) {
|
|
||||||
port = parseInt(host.split(':')[1])
|
|
||||||
host = host.split(':')[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
const connection: SSHConnection = {
|
|
||||||
name: this.quickTarget,
|
|
||||||
group: null,
|
|
||||||
host,
|
|
||||||
user,
|
|
||||||
port,
|
|
||||||
}
|
|
||||||
window.localStorage.lastConnection = JSON.stringify(connection)
|
|
||||||
this.connect(connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
clearLastConnection () {
|
|
||||||
window.localStorage.lastConnection = null
|
|
||||||
this.lastConnection = null
|
|
||||||
}
|
|
||||||
|
|
||||||
async connect (connection: SSHConnection) {
|
|
||||||
this.close()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const tab = this.zone.run(() => this.app.openNewTab(
|
|
||||||
SSHTabComponent,
|
|
||||||
{ connection }
|
|
||||||
) as SSHTabComponent)
|
|
||||||
if (connection.color) {
|
|
||||||
(this.app.getParentTab(tab) || tab).color = connection.color
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.app.activeTab?.emitFocused()
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
this.toastr.error(`Could not connect: ${error}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
manageConnections () {
|
|
||||||
this.close()
|
|
||||||
this.app.openNewTab(SettingsTabComponent, { activeTab: 'ssh' })
|
|
||||||
}
|
|
||||||
|
|
||||||
close () {
|
|
||||||
this.modalInstance.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh () {
|
|
||||||
this.childGroups = []
|
|
||||||
|
|
||||||
let connections = this.connections
|
|
||||||
if (this.quickTarget) {
|
|
||||||
connections = connections.filter((connection: SSHConnection) => (connection.name + connection.group!).toLowerCase().includes(this.quickTarget))
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const connection of connections) {
|
|
||||||
connection.group = connection.group || null
|
|
||||||
let group = this.childGroups.find(x => x.name === connection.group)
|
|
||||||
if (!group) {
|
|
||||||
group = {
|
|
||||||
name: connection.group!,
|
|
||||||
connections: [],
|
|
||||||
}
|
|
||||||
this.childGroups.push(group!)
|
|
||||||
}
|
|
||||||
group.connections.push(connection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -96,7 +96,7 @@ export class SSHSettingsTabComponent {
|
|||||||
this.hostApp.getWindow(),
|
this.hostApp.getWindow(),
|
||||||
{
|
{
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: `Delete "${group}"?`,
|
message: `Delete "${group.name}"?`,
|
||||||
buttons: ['Keep', 'Delete'],
|
buttons: ['Keep', 'Delete'],
|
||||||
defaultId: 1,
|
defaultId: 1,
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ import { SSHConnection, SSHSession } from '../api'
|
|||||||
import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.component'
|
import { SSHPortForwardingModalComponent } from './sshPortForwardingModal.component'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
|
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ssh-tab',
|
selector: 'ssh-tab',
|
||||||
@@ -20,6 +21,7 @@ import { Subscription } from 'rxjs'
|
|||||||
export class SSHTabComponent extends BaseTerminalTabComponent {
|
export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||||
connection: SSHConnection
|
connection: SSHConnection
|
||||||
session: SSHSession
|
session: SSHSession
|
||||||
|
private sessionStack: SSHSession[] = []
|
||||||
private homeEndSubscription: Subscription
|
private homeEndSubscription: Subscription
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@@ -33,6 +35,8 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
ngOnInit (): void {
|
ngOnInit (): void {
|
||||||
this.logger = this.log.create('terminalTab')
|
this.logger = this.log.create('terminalTab')
|
||||||
|
|
||||||
|
this.enableDynamicTitle = !this.connection.disableDynamicTitle
|
||||||
|
|
||||||
this.homeEndSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
this.homeEndSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
||||||
if (!this.hasFocus) {
|
if (!this.hasFocus) {
|
||||||
return
|
return
|
||||||
@@ -58,19 +62,40 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async initializeSession (): Promise<void> {
|
async setupOneSession (session: SSHSession): Promise<void> {
|
||||||
if (!this.connection) {
|
if (session.connection.jumpHost) {
|
||||||
this.logger.error('No SSH connection info supplied')
|
const jumpConnection = this.config.store.ssh.connections.find(x => x.name === session.connection.jumpHost)
|
||||||
return
|
const jumpSession = this.ssh.createSession(jumpConnection)
|
||||||
|
|
||||||
|
await this.setupOneSession(jumpSession)
|
||||||
|
|
||||||
|
jumpSession.destroyed$.subscribe(() => session.destroy())
|
||||||
|
|
||||||
|
session.jumpStream = await new Promise((resolve, reject) => jumpSession.ssh.forwardOut(
|
||||||
|
'127.0.0.1', 0, session.connection.host, session.connection.port,
|
||||||
|
(err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
jumpSession.emitServiceMessage(colors.bgRed.black(' X ') + ` Could not set up port forward on ${jumpConnection.name}`)
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
resolve(stream)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
|
||||||
|
session.jumpStream.on('close', () => {
|
||||||
|
jumpSession.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.sessionStack.push(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.session = this.ssh.createSession(this.connection)
|
|
||||||
this.session.serviceMessage$.subscribe(msg => {
|
session.serviceMessage$.subscribe(msg => {
|
||||||
this.write('\r\n' + colors.black.bgWhite(' SSH ') + ' ' + msg + '\r\n')
|
this.write('\r\n' + colors.black.bgWhite(' SSH ') + ' ' + msg + '\r\n')
|
||||||
this.session.resize(this.size.columns, this.size.rows)
|
session.resize(this.size.columns, this.size.rows)
|
||||||
})
|
})
|
||||||
this.attachSessionHandlers()
|
|
||||||
this.write(`Connecting to ${this.connection.host}`)
|
this.write('\r\n' + colors.black.bgCyan(' SSH ') + ` Connecting to ${session.connection.host}\r\n`)
|
||||||
|
|
||||||
const spinner = new Spinner({
|
const spinner = new Spinner({
|
||||||
text: 'Connecting',
|
text: 'Connecting',
|
||||||
@@ -82,7 +107,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
spinner.start()
|
spinner.start()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.ssh.connectSession(this.session, (message: string) => {
|
await this.ssh.connectSession(session, (message: string) => {
|
||||||
spinner.stop(true)
|
spinner.stop(true)
|
||||||
this.write(message + '\r\n')
|
this.write(message + '\r\n')
|
||||||
spinner.start()
|
spinner.start()
|
||||||
@@ -93,6 +118,20 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n')
|
this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async initializeSession (): Promise<void> {
|
||||||
|
if (!this.connection) {
|
||||||
|
this.logger.error('No SSH connection info supplied')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.session = this.ssh.createSession(this.connection)
|
||||||
|
|
||||||
|
await this.setupOneSession(this.session)
|
||||||
|
|
||||||
|
this.attachSessionHandlers()
|
||||||
|
|
||||||
await this.session.start()
|
await this.session.start()
|
||||||
this.session.resize(this.size.columns, this.size.rows)
|
this.session.resize(this.size.columns, this.size.rows)
|
||||||
}
|
}
|
||||||
@@ -114,6 +153,18 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
this.initializeSession()
|
this.initializeSession()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async canClose (): Promise<boolean> {
|
||||||
|
return (await this.electron.showMessageBox(
|
||||||
|
this.hostApp.getWindow(),
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
message: `Disconnect from ${this.connection.host}?`,
|
||||||
|
buttons: ['Cancel', 'Disconnect'],
|
||||||
|
defaultId: 1,
|
||||||
|
}
|
||||||
|
)).response === 1
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy (): void {
|
ngOnDestroy (): void {
|
||||||
this.homeEndSubscription.unsubscribe()
|
this.homeEndSubscription.unsubscribe()
|
||||||
super.ngOnDestroy()
|
super.ngOnDestroy()
|
||||||
|
@@ -5,6 +5,7 @@ export class SSHConfigProvider extends ConfigProvider {
|
|||||||
defaults = {
|
defaults = {
|
||||||
ssh: {
|
ssh: {
|
||||||
connections: [],
|
connections: [],
|
||||||
|
recentConnections: [],
|
||||||
options: {
|
options: {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -8,7 +8,6 @@ import { SettingsTabProvider } from 'terminus-settings'
|
|||||||
import TerminusTerminalModule from 'terminus-terminal'
|
import TerminusTerminalModule from 'terminus-terminal'
|
||||||
|
|
||||||
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
|
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
|
||||||
import { SSHModalComponent } from './components/sshModal.component'
|
|
||||||
import { SSHPortForwardingModalComponent } from './components/sshPortForwardingModal.component'
|
import { SSHPortForwardingModalComponent } from './components/sshPortForwardingModal.component'
|
||||||
import { PromptModalComponent } from './components/promptModal.component'
|
import { PromptModalComponent } from './components/promptModal.component'
|
||||||
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
|
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
|
||||||
@@ -40,7 +39,6 @@ import { SSHHotkeyProvider } from './hotkeys'
|
|||||||
entryComponents: [
|
entryComponents: [
|
||||||
EditConnectionModalComponent,
|
EditConnectionModalComponent,
|
||||||
PromptModalComponent,
|
PromptModalComponent,
|
||||||
SSHModalComponent,
|
|
||||||
SSHPortForwardingModalComponent,
|
SSHPortForwardingModalComponent,
|
||||||
SSHSettingsTabComponent,
|
SSHSettingsTabComponent,
|
||||||
SSHTabComponent,
|
SSHTabComponent,
|
||||||
@@ -48,7 +46,6 @@ import { SSHHotkeyProvider } from './hotkeys'
|
|||||||
declarations: [
|
declarations: [
|
||||||
EditConnectionModalComponent,
|
EditConnectionModalComponent,
|
||||||
PromptModalComponent,
|
PromptModalComponent,
|
||||||
SSHModalComponent,
|
|
||||||
SSHPortForwardingModalComponent,
|
SSHPortForwardingModalComponent,
|
||||||
SSHSettingsTabComponent,
|
SSHSettingsTabComponent,
|
||||||
SSHTabComponent,
|
SSHTabComponent,
|
||||||
|
@@ -3,16 +3,18 @@ import { open as openTemp } from 'temp'
|
|||||||
import { Injectable, NgZone } from '@angular/core'
|
import { Injectable, NgZone } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { Client } from 'ssh2'
|
import { Client } from 'ssh2'
|
||||||
|
import { SSH2Stream } from 'ssh2-streams'
|
||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import { execFile } from 'mz/child_process'
|
import { execFile } from 'mz/child_process'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as sshpk from 'sshpk'
|
import * as sshpk from 'sshpk'
|
||||||
import { ToastrService } from 'ngx-toastr'
|
import { ToastrService } from 'ngx-toastr'
|
||||||
import { HostAppService, Platform, Logger, LogService, ElectronService } from 'terminus-core'
|
import { HostAppService, Platform, Logger, LogService, ElectronService, AppService, SelectorOption, ConfigService } from 'terminus-core'
|
||||||
|
import { SettingsTabComponent } from 'terminus-settings'
|
||||||
import { SSHConnection, SSHSession } from '../api'
|
import { SSHConnection, SSHSession } from '../api'
|
||||||
import { PromptModalComponent } from '../components/promptModal.component'
|
import { PromptModalComponent } from '../components/promptModal.component'
|
||||||
import { PasswordStorageService } from './passwordStorage.service'
|
import { PasswordStorageService } from './passwordStorage.service'
|
||||||
import { SSH2Stream } from 'ssh2-streams'
|
import { SSHTabComponent } from '../components/sshTab.component'
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var windowsProcessTreeNative = require('windows-process-tree/build/Release/windows_process_tree.node') // eslint-disable-line @typescript-eslint/no-var-requires, no-var
|
var windowsProcessTreeNative = require('windows-process-tree/build/Release/windows_process_tree.node') // eslint-disable-line @typescript-eslint/no-var-requires, no-var
|
||||||
@@ -30,6 +32,8 @@ export class SSHService {
|
|||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
private passwordStorage: PasswordStorageService,
|
private passwordStorage: PasswordStorageService,
|
||||||
private toastr: ToastrService,
|
private toastr: ToastrService,
|
||||||
|
private app: AppService,
|
||||||
|
private config: ConfigService,
|
||||||
) {
|
) {
|
||||||
this.logger = log.create('ssh')
|
this.logger = log.create('ssh')
|
||||||
}
|
}
|
||||||
@@ -109,6 +113,8 @@ export class SSHService {
|
|||||||
'ssh-keygen',
|
'ssh-keygen',
|
||||||
'ssh-keygen.exe',
|
'ssh-keygen.exe',
|
||||||
)
|
)
|
||||||
|
await execFile('icacls', [temp.path, '/inheritance:r'])
|
||||||
|
await execFile('icacls', [temp.path, '/grant:r', `${process.env.USERNAME}:(R,W)`])
|
||||||
}
|
}
|
||||||
|
|
||||||
await execFile(sshKeygenPath, [
|
await execFile(sshKeygenPath, [
|
||||||
@@ -145,6 +151,12 @@ export class SSHService {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
ssh.on('close', () => {
|
||||||
|
if (session.open) {
|
||||||
|
session.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ssh.on('keyboard-interactive', (name, instructions, instructionsLang, prompts, finish) => this.zone.run(async () => {
|
ssh.on('keyboard-interactive', (name, instructions, instructionsLang, prompts, finish) => this.zone.run(async () => {
|
||||||
log(colors.bgBlackBright(' ') + ` Keyboard-interactive auth requested: ${name}`)
|
log(colors.bgBlackBright(' ') + ` Keyboard-interactive auth requested: ${name}`)
|
||||||
this.logger.info('Keyboard-interactive auth:', name, instructions, instructionsLang)
|
this.logger.info('Keyboard-interactive auth:', name, instructions, instructionsLang)
|
||||||
@@ -205,6 +217,7 @@ export class SSHService {
|
|||||||
},
|
},
|
||||||
hostHash: 'sha256' as any,
|
hostHash: 'sha256' as any,
|
||||||
algorithms: session.connection.algorithms,
|
algorithms: session.connection.algorithms,
|
||||||
|
sock: session.jumpStream,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.toastr.error(e.message)
|
this.toastr.error(e.message)
|
||||||
@@ -247,6 +260,124 @@ export class SSHService {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async showConnectionSelector (): Promise<void> {
|
||||||
|
const options: SelectorOption<void>[] = []
|
||||||
|
const recentConnections = this.config.store.ssh.recentConnections
|
||||||
|
|
||||||
|
for (const connection of recentConnections) {
|
||||||
|
options.push({
|
||||||
|
name: connection.name,
|
||||||
|
description: connection.host,
|
||||||
|
icon: 'history',
|
||||||
|
callback: () => this.connect(connection),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recentConnections.length) {
|
||||||
|
options.push({
|
||||||
|
name: 'Clear recent connections',
|
||||||
|
icon: 'eraser',
|
||||||
|
callback: () => {
|
||||||
|
this.config.store.ssh.recentConnections = []
|
||||||
|
this.config.save()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let groups: { name: string, connections: SSHConnection[] }[] = []
|
||||||
|
let connections = this.config.store.ssh.connections
|
||||||
|
for (const connection of connections) {
|
||||||
|
connection.group = connection.group || null
|
||||||
|
let group = groups.find(x => x.name === connection.group)
|
||||||
|
if (!group) {
|
||||||
|
group = {
|
||||||
|
name: connection.group!,
|
||||||
|
connections: [],
|
||||||
|
}
|
||||||
|
groups.push(group!)
|
||||||
|
}
|
||||||
|
group.connections.push(connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const group of groups) {
|
||||||
|
for (const connection of group.connections) {
|
||||||
|
options.push({
|
||||||
|
name: (group.name ? `${group.name} / ` : '') + connection.name,
|
||||||
|
description: connection.host,
|
||||||
|
icon: 'desktop',
|
||||||
|
callback: () => this.connect(connection),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
name: 'Manage connections',
|
||||||
|
icon: 'cog',
|
||||||
|
callback: () => this.app.openNewTab(SettingsTabComponent, { activeTab: 'ssh' }),
|
||||||
|
})
|
||||||
|
|
||||||
|
options.push({
|
||||||
|
name: 'Quick connect',
|
||||||
|
freeInputPattern: 'Connect to "%s"...',
|
||||||
|
icon: 'arrow-right',
|
||||||
|
callback: query => this.quickConnect(query),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
await this.app.showSelector('Open an SSH connection', options)
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect (connection: SSHConnection): Promise<SSHTabComponent> {
|
||||||
|
try {
|
||||||
|
const tab = this.app.openNewTab(
|
||||||
|
SSHTabComponent,
|
||||||
|
{ connection }
|
||||||
|
) as SSHTabComponent
|
||||||
|
if (connection.color) {
|
||||||
|
(this.app.getParentTab(tab) || tab).color = connection.color
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.app.activeTab?.emitFocused()
|
||||||
|
})
|
||||||
|
|
||||||
|
return tab
|
||||||
|
} catch (error) {
|
||||||
|
this.toastr.error(`Could not connect: ${error}`)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quickConnect (query: string): Promise<SSHTabComponent> {
|
||||||
|
let user = 'root'
|
||||||
|
let host = query
|
||||||
|
let port = 22
|
||||||
|
if (host.includes('@')) {
|
||||||
|
[user, host] = host.split('@')
|
||||||
|
}
|
||||||
|
if (host.includes(':')) {
|
||||||
|
port = parseInt(host.split(':')[1])
|
||||||
|
host = host.split(':')[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const connection: SSHConnection = {
|
||||||
|
name: query,
|
||||||
|
group: null,
|
||||||
|
host,
|
||||||
|
user,
|
||||||
|
port,
|
||||||
|
}
|
||||||
|
|
||||||
|
const recentConnections = this.config.store.ssh.recentConnections
|
||||||
|
recentConnections.unshift(connection)
|
||||||
|
if (recentConnections.length > 5) {
|
||||||
|
recentConnections.pop()
|
||||||
|
}
|
||||||
|
this.config.store.ssh.recentConnections = recentConnections
|
||||||
|
this.config.save()
|
||||||
|
return this.connect(connection)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
@@ -20,9 +20,9 @@
|
|||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/ssh2@^0.5.35":
|
"@types/ssh2@^0.5.35":
|
||||||
version "0.5.40"
|
version "0.5.43"
|
||||||
resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-0.5.40.tgz#b65465897f40c67e66e630b1f059f13a1ea7f1fa"
|
resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-0.5.43.tgz#a4b8fc0288ef79a0d2d89363644d0b4448fe9532"
|
||||||
integrity sha512-Yrs2vFIirdscR+xY4leiUosHTClRbVBiFLlm5gA0JMZMjbCulHKO3qcgMqATElrquiZal5sSHoXMk4t8DzDawA==
|
integrity sha512-rVEwm6fEmy9RJg1nTK3qhCKoviKtLg+axE1RXzAWuKz3QuZIZDxCKP38/tzhxkQ8I8FDVXgRoWtvltzQKtDaBg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
"@types/ssh2-streams" "*"
|
"@types/ssh2-streams" "*"
|
||||||
@@ -162,21 +162,21 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
ssh2-streams@^0.4.2, ssh2-streams@~0.4.9:
|
ssh2-streams@^0.4.2, ssh2-streams@~0.4.10:
|
||||||
version "0.4.9"
|
version "0.4.10"
|
||||||
resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.9.tgz#d3d92155410001437d27119d9c023b303cbe2309"
|
resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34"
|
||||||
integrity sha512-glMQKeYKuA+rLaH16fJC3nZMV1BWklbxuYCR4C5/LlBSf2yaoNRpPU7Ul702xsi5nvYjIx9XDkKBJwrBjkDynw==
|
integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
asn1 "~0.2.0"
|
asn1 "~0.2.0"
|
||||||
bcrypt-pbkdf "^1.0.2"
|
bcrypt-pbkdf "^1.0.2"
|
||||||
streamsearch "~0.1.2"
|
streamsearch "~0.1.2"
|
||||||
|
|
||||||
ssh2@^0.8.2:
|
ssh2@^0.8.2:
|
||||||
version "0.8.8"
|
version "0.8.9"
|
||||||
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.8.tgz#1d9815e287faef623ae2b7db32e674dadbef4664"
|
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3"
|
||||||
integrity sha512-egJVQkf3sbjECTY6rCeg8rgV/fab6S/7E5kpYqHT3Fe/YpfJbLYeA1qTcB2d+LRUUAjqKi7rlbfWkaP66YdpAQ==
|
integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==
|
||||||
dependencies:
|
dependencies:
|
||||||
ssh2-streams "~0.4.9"
|
ssh2-streams "~0.4.10"
|
||||||
|
|
||||||
sshpk@^1.16.1:
|
sshpk@^1.16.1:
|
||||||
version "1.16.1"
|
version "1.16.1"
|
||||||
|
@@ -26,14 +26,13 @@
|
|||||||
"ps-node": "^0.1.6",
|
"ps-node": "^0.1.6",
|
||||||
"runes": "^0.4.2",
|
"runes": "^0.4.2",
|
||||||
"slugify": "^1.4.0",
|
"slugify": "^1.4.0",
|
||||||
"uuid": "^7.0.1",
|
"xterm": "^4.6.0-beta.33",
|
||||||
"xterm": "^4.5.0-beta.9",
|
"xterm-addon-fit": "^0.4.0-beta.8",
|
||||||
"xterm-addon-fit": "^0.4.0-beta2",
|
"xterm-addon-ligatures": "^0.3.0-beta.1",
|
||||||
"xterm-addon-ligatures": "^0.2.1",
|
"xterm-addon-search": "^0.7.0-beta.1",
|
||||||
"xterm-addon-search": "^0.5.0",
|
"xterm-addon-serialize": "^0.3.0-beta.3",
|
||||||
"xterm-addon-serialize": "^0.1.1",
|
"xterm-addon-unicode11": "^0.2.0-beta.5",
|
||||||
"xterm-addon-unicode11": "^0.1.1",
|
"xterm-addon-webgl": "^0.7.0-beta.5",
|
||||||
"xterm-addon-webgl": "^0.6.0-beta.2",
|
|
||||||
"zmodem.js": "^0.1.9"
|
"zmodem.js": "^0.1.9"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
@@ -63,6 +63,13 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
*/
|
*/
|
||||||
enablePassthrough = true
|
enablePassthrough = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables receiving dynamic window/tab title provided by the shell
|
||||||
|
*/
|
||||||
|
enableDynamicTitle = true
|
||||||
|
|
||||||
|
alternateScreenActive = false
|
||||||
|
|
||||||
// Deps start
|
// Deps start
|
||||||
config: ConfigService
|
config: ConfigService
|
||||||
element: ElementRef
|
element: ElementRef
|
||||||
@@ -205,6 +212,10 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
this.session.releaseInitialDataBuffer()
|
this.session.releaseInitialDataBuffer()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.alternateScreenActive$.subscribe(x => {
|
||||||
|
this.alternateScreenActive = x
|
||||||
|
})
|
||||||
|
|
||||||
if (this.savedState) {
|
if (this.savedState) {
|
||||||
this.frontend.restoreState(this.savedState)
|
this.frontend.restoreState(this.savedState)
|
||||||
this.frontend.write('\r\n\r\n')
|
this.frontend.write('\r\n\r\n')
|
||||||
@@ -274,7 +285,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
*/
|
*/
|
||||||
write (data: string): void {
|
write (data: string): void {
|
||||||
const percentageMatch = /(^|[^\d])(\d+(\.\d+)?)%([^\d]|$)/.exec(data)
|
const percentageMatch = /(^|[^\d])(\d+(\.\d+)?)%([^\d]|$)/.exec(data)
|
||||||
if (percentageMatch) {
|
if (!this.alternateScreenActive && percentageMatch) {
|
||||||
const percentage = percentageMatch[3] ? parseFloat(percentageMatch[2]) : parseInt(percentageMatch[2])
|
const percentage = percentageMatch[3] ? parseFloat(percentageMatch[2]) : parseInt(percentageMatch[2])
|
||||||
if (percentage > 0 && percentage <= 100) {
|
if (percentage > 0 && percentage <= 100) {
|
||||||
this.setProgress(percentage)
|
this.setProgress(percentage)
|
||||||
@@ -286,7 +297,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
this.frontend.write(data)
|
this.frontend.write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
paste (): void {
|
async paste (): Promise<void> {
|
||||||
let data = this.electron.clipboard.readText() as string
|
let data = this.electron.clipboard.readText() as string
|
||||||
if (this.config.store.terminal.bracketedPaste) {
|
if (this.config.store.terminal.bracketedPaste) {
|
||||||
data = '\x1b[200~' + data + '\x1b[201~'
|
data = '\x1b[200~' + data + '\x1b[201~'
|
||||||
@@ -296,6 +307,28 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
} else {
|
} else {
|
||||||
data = data.replace(/\n/g, '\r')
|
data = data.replace(/\n/g, '\r')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.alternateScreenActive && data.includes('\r') && this.config.store.terminal.warnOnMultilinePaste) {
|
||||||
|
const canTrim = !data.trim().includes('\r')
|
||||||
|
const buttons = canTrim ? ['Paste', 'Trim whitespace and paste', 'Cancel'] : ['Paste', 'Cancel']
|
||||||
|
const result = (await this.electron.showMessageBox(
|
||||||
|
this.hostApp.getWindow(),
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
detail: data,
|
||||||
|
message: `Paste multiple lines?`,
|
||||||
|
buttons,
|
||||||
|
defaultId: 0,
|
||||||
|
cancelId: buttons.length - 1,
|
||||||
|
}
|
||||||
|
)).response
|
||||||
|
if (result === buttons.length - 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (result === 1) {
|
||||||
|
data = data.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
this.sendInput(data)
|
this.sendInput(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +408,11 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.termContainerSubscriptions = [
|
this.termContainerSubscriptions = [
|
||||||
this.frontend.title$.subscribe(title => this.zone.run(() => this.setTitle(title))),
|
this.frontend.title$.subscribe(title => this.zone.run(() => {
|
||||||
|
if (this.enableDynamicTitle) {
|
||||||
|
this.setTitle(title)
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
|
||||||
this.focused$.subscribe(() => this.frontend.enableResizing = true),
|
this.focused$.subscribe(() => this.frontend.enableResizing = true),
|
||||||
this.blurred$.subscribe(() => this.frontend.enableResizing = false),
|
this.blurred$.subscribe(() => this.frontend.enableResizing = false),
|
||||||
|
@@ -19,6 +19,7 @@ export interface Profile {
|
|||||||
name: string
|
name: string
|
||||||
color?: string
|
color?: string
|
||||||
sessionOptions: SessionOptions
|
sessionOptions: SessionOptions
|
||||||
|
shell?: string
|
||||||
isBuiltin?: boolean
|
isBuiltin?: boolean
|
||||||
icon?: string
|
icon?: string
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { ToolbarButtonProvider, ToolbarButton, ElectronService } from 'terminus-core'
|
import { ToolbarButtonProvider, ToolbarButton, ElectronService, ConfigService, SelectorOption, AppService } from 'terminus-core'
|
||||||
|
|
||||||
import { TerminalService } from './services/terminal.service'
|
import { TerminalService } from './services/terminal.service'
|
||||||
|
|
||||||
@@ -10,6 +10,8 @@ import { TerminalService } from './services/terminal.service'
|
|||||||
export class ButtonProvider extends ToolbarButtonProvider {
|
export class ButtonProvider extends ToolbarButtonProvider {
|
||||||
constructor (
|
constructor (
|
||||||
electron: ElectronService,
|
electron: ElectronService,
|
||||||
|
private app: AppService,
|
||||||
|
private config: ConfigService,
|
||||||
private terminal: TerminalService,
|
private terminal: TerminalService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
@@ -27,27 +29,35 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async activate () {
|
||||||
|
const options: SelectorOption<void>[] = []
|
||||||
|
const profiles = await this.terminal.getProfiles({ skipDefault: !this.config.store.terminal.showDefaultProfiles })
|
||||||
|
|
||||||
|
for (const profile of profiles) {
|
||||||
|
options.push({
|
||||||
|
icon: profile.icon,
|
||||||
|
name: profile.name,
|
||||||
|
callback: () => this.terminal.openTab(profile),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.app.showSelector('Select profile', options)
|
||||||
|
}
|
||||||
|
|
||||||
provide (): ToolbarButton[] {
|
provide (): ToolbarButton[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
icon: require('./icons/plus.svg'),
|
icon: require('./icons/plus.svg'),
|
||||||
title: 'New terminal',
|
title: 'New terminal',
|
||||||
touchBarNSImage: 'NSTouchBarAddDetailTemplate',
|
touchBarNSImage: 'NSTouchBarAddDetailTemplate',
|
||||||
click: async () => {
|
click: () => {
|
||||||
this.terminal.openTab()
|
this.terminal.openTab()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: require('./icons/profiles.svg'),
|
icon: require('./icons/profiles.svg'),
|
||||||
title: 'New terminal with profile',
|
title: 'New terminal with profile',
|
||||||
submenu: async () => {
|
click: () => this.activate(),
|
||||||
const profiles = await this.terminal.getProfiles()
|
|
||||||
return profiles.map(profile => ({
|
|
||||||
icon: profile.icon,
|
|
||||||
title: profile.name,
|
|
||||||
click: () => this.terminal.openTab(profile),
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
h3.mb-3 Appearance
|
h3.mb-3 Appearance
|
||||||
.d-flex
|
.d-flex
|
||||||
.mr-5
|
.mr-5.w-100
|
||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title Font
|
.title Font
|
||||||
@@ -27,148 +27,7 @@ h3.mb-3 Appearance
|
|||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
)
|
)
|
||||||
|
|
||||||
.form-line(*ngIf='!editingColorScheme')
|
color-scheme-preview([scheme]='config.store.terminal.colorScheme', [fontPreview]='true')
|
||||||
.header
|
|
||||||
.title Color scheme
|
|
||||||
|
|
||||||
.input-group.w-50
|
|
||||||
select.form-control(
|
|
||||||
[compareWith]='equalComparator',
|
|
||||||
[(ngModel)]='config.store.terminal.colorScheme',
|
|
||||||
(ngModelChange)='config.save()',
|
|
||||||
)
|
|
||||||
option(*ngFor='let scheme of config.store.terminal.customColorSchemes', [ngValue]='scheme') Custom: {{scheme.name}}
|
|
||||||
option(*ngFor='let scheme of colorSchemes', [ngValue]='scheme') {{scheme.name}}
|
|
||||||
.input-group-append
|
|
||||||
button.btn.btn-secondary((click)='editScheme(config.store.terminal.colorScheme)')
|
|
||||||
i.fas.fa-pen
|
|
||||||
.input-group-append
|
|
||||||
button.btn.btn-outline-danger(
|
|
||||||
(click)='deleteScheme(config.store.terminal.colorScheme)',
|
|
||||||
*ngIf='isCustomScheme(config.store.terminal.colorScheme)'
|
|
||||||
)
|
|
||||||
i.fas.fa-trash
|
|
||||||
|
|
||||||
.form-group(*ngIf='editingColorScheme')
|
|
||||||
label Editing
|
|
||||||
.input-group
|
|
||||||
input.form-control(type='text', [(ngModel)]='editingColorScheme.name')
|
|
||||||
.input-group-append
|
|
||||||
button.btn.btn-secondary((click)='saveScheme()')
|
|
||||||
i.fas.fa-check
|
|
||||||
.input-group-append
|
|
||||||
button.btn.btn-secondary((click)='cancelEditing()')
|
|
||||||
i.fas.fa-times
|
|
||||||
|
|
||||||
.form-group(*ngIf='editingColorScheme')
|
|
||||||
color-picker(
|
|
||||||
[(model)]='editingColorScheme.foreground',
|
|
||||||
(modelChange)='config.save(); schemeChanged = true',
|
|
||||||
title='FG',
|
|
||||||
)
|
|
||||||
color-picker(
|
|
||||||
[(model)]='editingColorScheme.background',
|
|
||||||
(modelChange)='config.save(); schemeChanged = true',
|
|
||||||
title='BG',
|
|
||||||
)
|
|
||||||
color-picker(
|
|
||||||
[(model)]='editingColorScheme.cursor',
|
|
||||||
(modelChange)='config.save(); schemeChanged = true',
|
|
||||||
title='CU',
|
|
||||||
)
|
|
||||||
color-picker(
|
|
||||||
*ngFor='let _ of editingColorScheme.colors; let idx = index; trackBy: colorsTrackBy',
|
|
||||||
[(model)]='editingColorScheme.colors[idx]',
|
|
||||||
(modelChange)='config.save(); schemeChanged = true',
|
|
||||||
[title]='idx',
|
|
||||||
)
|
|
||||||
|
|
||||||
div
|
|
||||||
.form-group
|
|
||||||
.appearance-preview(
|
|
||||||
[style.font-family]='getPreviewFontFamily()',
|
|
||||||
[style.font-size]='config.store.terminal.fontSize + "px"',
|
|
||||||
[style.background-color]='(config.store.terminal.background == "theme") ? null : config.store.terminal.colorScheme.background',
|
|
||||||
[style.color]='config.store.terminal.colorScheme.foreground',
|
|
||||||
[style.font-feature-settings]='\'"liga" \' + config.store.terminal.ligatures ? 1 : 0',
|
|
||||||
[style.font-variant-ligatures]='config.store.terminal.ligatures ? "initial" : "none"',
|
|
||||||
)
|
|
||||||
div
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[0]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[1]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[2]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[3]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[4]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[5]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[6]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[7]')
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[0]') B
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[1]') R
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[2]') G
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[3]') Y
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[4]') B
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[5]') M
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[6]') T
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[7]') W
|
|
||||||
div
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[8]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[9]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[10]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[11]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[12]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[13]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[14]')
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.colors[15]')
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[8]') B
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[9]') R
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[10]') G
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[11]') Y
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[12]') B
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[13]') M
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[14]') T
|
|
||||||
span
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[15]') W
|
|
||||||
div
|
|
||||||
span
|
|
||||||
div
|
|
||||||
span john@doe-pc
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[1]') $
|
|
||||||
span ls -l
|
|
||||||
div
|
|
||||||
span drwxr-xr-x 1 root
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[4]') directory 📁
|
|
||||||
div
|
|
||||||
span -rw-r--r-- 1 root файл
|
|
||||||
div
|
|
||||||
span -rwxr-xr-x 1 root
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[2]') 実行可能ファイル
|
|
||||||
div
|
|
||||||
span -rwxr-xr-x 1 root
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[6]') sym
|
|
||||||
span ->
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[1]') link
|
|
||||||
div
|
|
||||||
span
|
|
||||||
div
|
|
||||||
span john@doe-pc
|
|
||||||
span([style.color]='config.store.terminal.colorScheme.colors[1]') $
|
|
||||||
span rm -rf /
|
|
||||||
span([style.background-color]='config.store.terminal.colorScheme.cursor')
|
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
|
@@ -1,12 +1,4 @@
|
|||||||
.appearance-preview {
|
color-scheme-preview {
|
||||||
padding: 10px 0;
|
flex-shrink: 0;
|
||||||
margin-left: 20px;
|
margin-bottom: 20px;
|
||||||
padding: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
max-width: 400px;
|
|
||||||
max-height: 400px;
|
|
||||||
|
|
||||||
span {
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -2,13 +2,10 @@
|
|||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
|
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
|
||||||
import { exec } from 'mz/child_process'
|
import { exec } from 'mz/child_process'
|
||||||
import deepEqual from 'deep-equal'
|
|
||||||
const fontManager = require('fontmanager-redux') // eslint-disable-line
|
const fontManager = require('fontmanager-redux') // eslint-disable-line
|
||||||
|
|
||||||
import { Component, Inject } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { ConfigService, HostAppService, Platform, ElectronService, getCSSFontFamily } from 'terminus-core'
|
import { ConfigService, HostAppService, Platform, getCSSFontFamily } from 'terminus-core'
|
||||||
import { TerminalColorSchemeProvider } from '../api/colorSchemeProvider'
|
|
||||||
import { TerminalColorScheme } from '../api/interfaces'
|
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
@@ -17,15 +14,9 @@ import { TerminalColorScheme } from '../api/interfaces'
|
|||||||
})
|
})
|
||||||
export class AppearanceSettingsTabComponent {
|
export class AppearanceSettingsTabComponent {
|
||||||
fonts: string[] = []
|
fonts: string[] = []
|
||||||
colorSchemes: TerminalColorScheme[] = []
|
|
||||||
equalComparator = deepEqual
|
|
||||||
editingColorScheme: TerminalColorScheme|null = null
|
|
||||||
schemeChanged = false
|
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
|
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
private electron: ElectronService,
|
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@@ -49,7 +40,6 @@ export class AppearanceSettingsTabComponent {
|
|||||||
this.fonts.sort()
|
this.fonts.sort()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.colorSchemes = (await Promise.all(this.config.enabledServices(this.colorSchemeProviders).map(x => x.getSchemes()))).reduce((a, b) => a.concat(b))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fontAutocomplete = (text$: Observable<string>) => {
|
fontAutocomplete = (text$: Observable<string>) => {
|
||||||
@@ -61,49 +51,6 @@ export class AppearanceSettingsTabComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
editScheme (scheme: TerminalColorScheme) {
|
|
||||||
this.editingColorScheme = scheme
|
|
||||||
this.schemeChanged = false
|
|
||||||
}
|
|
||||||
|
|
||||||
saveScheme () {
|
|
||||||
let schemes = this.config.store.terminal.customColorSchemes
|
|
||||||
schemes = schemes.filter(x => x !== this.editingColorScheme && x.name !== this.editingColorScheme!.name)
|
|
||||||
schemes.push(this.editingColorScheme)
|
|
||||||
this.config.store.terminal.customColorSchemes = schemes
|
|
||||||
this.config.save()
|
|
||||||
this.cancelEditing()
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelEditing () {
|
|
||||||
this.editingColorScheme = null
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteScheme (scheme: TerminalColorScheme) {
|
|
||||||
if ((await this.electron.showMessageBox(
|
|
||||||
this.hostApp.getWindow(),
|
|
||||||
{
|
|
||||||
type: 'warning',
|
|
||||||
message: `Delete "${scheme.name}"?`,
|
|
||||||
buttons: ['Keep', 'Delete'],
|
|
||||||
defaultId: 1,
|
|
||||||
}
|
|
||||||
)).response === 1) {
|
|
||||||
let schemes = this.config.store.terminal.customColorSchemes
|
|
||||||
schemes = schemes.filter(x => x !== scheme)
|
|
||||||
this.config.store.terminal.customColorSchemes = schemes
|
|
||||||
this.config.save()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isCustomScheme (scheme: TerminalColorScheme) {
|
|
||||||
return this.config.store.terminal.customColorSchemes.some(x => deepEqual(x, scheme))
|
|
||||||
}
|
|
||||||
|
|
||||||
colorsTrackBy (index) {
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
|
|
||||||
getPreviewFontFamily () {
|
getPreviewFontFamily () {
|
||||||
return getCSSFontFamily(this.config.store)
|
return getCSSFontFamily(this.config.store)
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ div(
|
|||||||
[ngbPopover]='content',
|
[ngbPopover]='content',
|
||||||
[style.background]='model',
|
[style.background]='model',
|
||||||
(click)='open()',
|
(click)='open()',
|
||||||
|
autoClose='outside',
|
||||||
container='body',
|
container='body',
|
||||||
#popover='ngbPopover',
|
#popover='ngbPopover',
|
||||||
) {{ title }}
|
) {{ title }}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, Input, Output, EventEmitter, HostListener, ViewChild } from '@angular/core'
|
import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core'
|
||||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@@ -12,34 +12,14 @@ export class ColorPickerComponent {
|
|||||||
@Input() title: string
|
@Input() title: string
|
||||||
@Output() modelChange = new EventEmitter<string>()
|
@Output() modelChange = new EventEmitter<string>()
|
||||||
@ViewChild('popover') popover: NgbPopover
|
@ViewChild('popover') popover: NgbPopover
|
||||||
@ViewChild('input') input
|
|
||||||
isOpen: boolean
|
|
||||||
|
|
||||||
open (): void {
|
open (): void {
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
this.popover.open()
|
this.popover.open()
|
||||||
setImmediate(() => {
|
this.popover['_windowRef'].location.nativeElement.querySelector('input').focus()
|
||||||
this.input.nativeElement.focus()
|
|
||||||
this.isOpen = true
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('document:click', ['$event']) onOutsideClick ($event: MouseEvent): void {
|
|
||||||
if (!this.isOpen) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const windowRef = (this.popover as any)._windowRef
|
|
||||||
if (!windowRef) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ($event.target !== windowRef.location.nativeElement &&
|
|
||||||
!windowRef.location.nativeElement.contains($event.target)) {
|
|
||||||
this.popover.close()
|
|
||||||
this.isOpen = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange (): void {
|
onChange (): void {
|
||||||
this.modelChange.emit(this.model)
|
this.modelChange.emit(this.model)
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,35 @@
|
|||||||
|
.preview(
|
||||||
|
[style.font-family]='getPreviewFontFamily()',
|
||||||
|
[style.font-size]='(fontPreview ? config.store.terminal.fontSize : 11) + "px"',
|
||||||
|
[style.background-color]='scheme.background',
|
||||||
|
[style.color]='scheme.foreground',
|
||||||
|
[style.font-feature-settings]='\'"liga" \' + config.store.terminal.ligatures ? 1 : 0',
|
||||||
|
[style.font-variant-ligatures]='config.store.terminal.ligatures ? "initial" : "none"',
|
||||||
|
)
|
||||||
|
div
|
||||||
|
span([style.color]='scheme.colors[2]') john
|
||||||
|
span([style.color]='scheme.colors[6]') @
|
||||||
|
span([style.color]='scheme.colors[4]') doe-pc
|
||||||
|
strong([style.color]='scheme.colors[1]') $
|
||||||
|
span ls
|
||||||
|
span([style.background-color]='scheme.cursor')
|
||||||
|
div
|
||||||
|
span -rwxr-xr-x 1 root
|
||||||
|
strong([style.color]='scheme.colors[3]') Documents
|
||||||
|
div
|
||||||
|
span -rwxr-xr-x 1 root
|
||||||
|
strong([style.color]='scheme.colors[5]') Downloads
|
||||||
|
div
|
||||||
|
span -rwxr-xr-x 1 root
|
||||||
|
strong([style.color]='scheme.colors[13]') Pictures
|
||||||
|
div
|
||||||
|
span -rwxr-xr-x 1 root
|
||||||
|
strong([style.color]='scheme.colors[12]') Music
|
||||||
|
div(*ngIf='fontPreview')
|
||||||
|
span -rwxr-xr-x 1 root
|
||||||
|
span([style.color]='scheme.colors[2]') 実行可能ファイル
|
||||||
|
div(*ngIf='fontPreview')
|
||||||
|
span -rwxr-xr-x 1 root
|
||||||
|
span([style.color]='scheme.colors[6]') sym
|
||||||
|
span ->
|
||||||
|
span([style.color]='scheme.colors[1]') link
|
@@ -0,0 +1,15 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
span {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
import { Component, Input, ChangeDetectionStrategy } from '@angular/core'
|
||||||
|
import { ConfigService, getCSSFontFamily } from 'terminus-core'
|
||||||
|
import { TerminalColorScheme } from '../api/interfaces'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
selector: 'color-scheme-preview',
|
||||||
|
template: require('./colorSchemePreview.component.pug'),
|
||||||
|
styles: [require('./colorSchemePreview.component.scss')],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class ColorSchemePreviewComponent {
|
||||||
|
@Input() scheme: TerminalColorScheme
|
||||||
|
@Input() fontPreview = false
|
||||||
|
|
||||||
|
constructor (public config: ConfigService) {}
|
||||||
|
|
||||||
|
getPreviewFontFamily (): string {
|
||||||
|
return getCSSFontFamily(this.config.store)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,96 @@
|
|||||||
|
.head
|
||||||
|
h3.mb-3 Current color scheme
|
||||||
|
|
||||||
|
.d-flex.align-items-center(*ngIf='!editing')
|
||||||
|
span {{getCurrentSchemeName()}}
|
||||||
|
.mr-auto
|
||||||
|
.btn-toolbar
|
||||||
|
button.btn.btn-secondary((click)='editScheme()')
|
||||||
|
i.fas.fa-pen
|
||||||
|
span Edit
|
||||||
|
.mr-1
|
||||||
|
button.btn.btn-danger(
|
||||||
|
(click)='deleteScheme(config.store.terminal.colorScheme)',
|
||||||
|
*ngIf='currentCustomScheme'
|
||||||
|
)
|
||||||
|
i.fas.fa-trash
|
||||||
|
span Delete
|
||||||
|
|
||||||
|
div(*ngIf='editing')
|
||||||
|
.form-group
|
||||||
|
label Name
|
||||||
|
input.form-control(type='text', [(ngModel)]='config.store.terminal.colorScheme.name')
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
color-picker(
|
||||||
|
[(model)]='config.store.terminal.colorScheme.foreground',
|
||||||
|
(modelChange)='config.save()',
|
||||||
|
title='FG',
|
||||||
|
)
|
||||||
|
color-picker(
|
||||||
|
[(model)]='config.store.terminal.colorScheme.background',
|
||||||
|
(modelChange)='config.save()',
|
||||||
|
title='BG',
|
||||||
|
)
|
||||||
|
color-picker(
|
||||||
|
[(model)]='config.store.terminal.colorScheme.cursor',
|
||||||
|
(modelChange)='config.save()',
|
||||||
|
title='CU',
|
||||||
|
)
|
||||||
|
color-picker(
|
||||||
|
*ngFor='let _ of config.store.terminal.colorScheme.colors; let idx = index; trackBy: colorsTrackBy',
|
||||||
|
[(model)]='config.store.terminal.colorScheme.colors[idx]',
|
||||||
|
(modelChange)='config.save()',
|
||||||
|
[title]='idx',
|
||||||
|
)
|
||||||
|
|
||||||
|
color-scheme-preview([scheme]='config.store.terminal.colorScheme')
|
||||||
|
|
||||||
|
.btn-toolbar.d-flex.mt-2(*ngIf='editing')
|
||||||
|
.mr-auto
|
||||||
|
button.btn.btn-primary((click)='saveScheme()')
|
||||||
|
i.fas.fa-check
|
||||||
|
span Save
|
||||||
|
.mr-1
|
||||||
|
button.btn.btn-secondary((click)='cancelEditing()')
|
||||||
|
i.fas.fa-times
|
||||||
|
span Cancel
|
||||||
|
|
||||||
|
hr.mt-3.mb-4
|
||||||
|
|
||||||
|
.input-group.mb-3
|
||||||
|
.input-group-prepend
|
||||||
|
.input-group-text
|
||||||
|
i.fas.fa-fw.fa-search
|
||||||
|
input.form-control(type='search', placeholder='Search color schemes', [(ngModel)]='filter')
|
||||||
|
|
||||||
|
.body
|
||||||
|
.list-group-light.mb-3
|
||||||
|
ng-container(*ngFor='let scheme of allColorSchemes')
|
||||||
|
.list-group-item.list-group-item-action(
|
||||||
|
[hidden]='filter && !scheme.name.toLowerCase().includes(filter.toLowerCase())',
|
||||||
|
(click)='selectScheme(scheme)',
|
||||||
|
[class.active]='(currentCustomScheme || currentStockScheme) === scheme'
|
||||||
|
)
|
||||||
|
.d-flex.w-100.align-items-center
|
||||||
|
i.fas.fa-fw([class.fa-check]='(currentCustomScheme || currentStockScheme) === scheme')
|
||||||
|
|
||||||
|
.ml-2
|
||||||
|
|
||||||
|
.mr-auto
|
||||||
|
span {{scheme.name}}
|
||||||
|
.badge.badge-info.ml-2(*ngIf='customColorSchemes.includes(scheme)') Custom
|
||||||
|
|
||||||
|
div
|
||||||
|
.d-flex
|
||||||
|
.swatch(
|
||||||
|
*ngFor='let index of colorIndexes.slice(0, 8)',
|
||||||
|
[style.background-color]='scheme.colors[index]'
|
||||||
|
)
|
||||||
|
.d-flex
|
||||||
|
.swatch(
|
||||||
|
*ngFor='let index of colorIndexes.slice(8, 16)',
|
||||||
|
[style.background-color]='scheme.colors[index]'
|
||||||
|
)
|
||||||
|
|
||||||
|
color-scheme-preview([scheme]='scheme')
|
@@ -0,0 +1,22 @@
|
|||||||
|
.head {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
overflow: auto;
|
||||||
|
flex: auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swatch {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 3px;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
box-shadow: 0 1px 1px rgba(0, 0, 0, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item color-scheme-preview {
|
||||||
|
margin-left: 14px;
|
||||||
|
}
|
@@ -0,0 +1,106 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
import deepEqual from 'deep-equal'
|
||||||
|
|
||||||
|
import { Component, Inject, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'
|
||||||
|
import { ConfigService, HostAppService, ElectronService } from 'terminus-core'
|
||||||
|
import { TerminalColorSchemeProvider } from '../api/colorSchemeProvider'
|
||||||
|
import { TerminalColorScheme } from '../api/interfaces'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
template: require('./colorSchemeSettingsTab.component.pug'),
|
||||||
|
styles: [require('./colorSchemeSettingsTab.component.scss')],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class ColorSchemeSettingsTabComponent {
|
||||||
|
@Input() stockColorSchemes: TerminalColorScheme[] = []
|
||||||
|
@Input() customColorSchemes: TerminalColorScheme[] = []
|
||||||
|
@Input() allColorSchemes: TerminalColorScheme[] = []
|
||||||
|
@Input() filter = ''
|
||||||
|
@Input() editing = false
|
||||||
|
colorIndexes = [...new Array(16).keys()]
|
||||||
|
|
||||||
|
currentStockScheme: TerminalColorScheme|null = null
|
||||||
|
currentCustomScheme: TerminalColorScheme|null = null
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
@Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[],
|
||||||
|
private changeDetector: ChangeDetectorRef,
|
||||||
|
private hostApp: HostAppService,
|
||||||
|
private electron: ElectronService,
|
||||||
|
public config: ConfigService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async ngOnInit () {
|
||||||
|
this.stockColorSchemes = (await Promise.all(this.config.enabledServices(this.colorSchemeProviders).map(x => x.getSchemes()))).reduce((a, b) => a.concat(b))
|
||||||
|
this.stockColorSchemes.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
this.customColorSchemes = this.config.store.terminal.customColorSchemes
|
||||||
|
this.changeDetector.markForCheck()
|
||||||
|
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges () {
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
selectScheme (scheme: TerminalColorScheme) {
|
||||||
|
this.config.store.terminal.colorScheme = { ...scheme }
|
||||||
|
this.config.save()
|
||||||
|
this.cancelEditing()
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
update () {
|
||||||
|
this.currentCustomScheme = this.findMatchingScheme(this.config.store.terminal.colorScheme, this.customColorSchemes)
|
||||||
|
this.currentStockScheme = this.findMatchingScheme(this.config.store.terminal.colorScheme, this.stockColorSchemes)
|
||||||
|
this.allColorSchemes = this.customColorSchemes.concat(this.stockColorSchemes)
|
||||||
|
this.changeDetector.markForCheck()
|
||||||
|
}
|
||||||
|
|
||||||
|
editScheme () {
|
||||||
|
this.editing = true
|
||||||
|
}
|
||||||
|
|
||||||
|
saveScheme () {
|
||||||
|
this.customColorSchemes = this.customColorSchemes.filter(x => x.name !== this.config.store.terminal.colorScheme.name)
|
||||||
|
this.customColorSchemes.push(this.config.store.terminal.colorScheme)
|
||||||
|
this.config.store.terminal.customColorSchemes = this.customColorSchemes
|
||||||
|
this.config.save()
|
||||||
|
this.cancelEditing()
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelEditing () {
|
||||||
|
this.editing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteScheme (scheme: TerminalColorScheme) {
|
||||||
|
if ((await this.electron.showMessageBox(
|
||||||
|
this.hostApp.getWindow(),
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
message: `Delete "${scheme.name}"?`,
|
||||||
|
buttons: ['Keep', 'Delete'],
|
||||||
|
defaultId: 1,
|
||||||
|
}
|
||||||
|
)).response === 1) {
|
||||||
|
this.customColorSchemes = this.customColorSchemes.filter(x => x.name !== scheme.name)
|
||||||
|
this.config.store.terminal.customColorSchemes = this.customColorSchemes
|
||||||
|
this.config.save()
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentSchemeName () {
|
||||||
|
return (this.currentCustomScheme || this.currentStockScheme)?.name || 'Custom'
|
||||||
|
}
|
||||||
|
|
||||||
|
findMatchingScheme (scheme: TerminalColorScheme, schemes: TerminalColorScheme[]) {
|
||||||
|
return schemes.find(x => deepEqual(x, scheme)) || null
|
||||||
|
}
|
||||||
|
|
||||||
|
colorsTrackBy (index) {
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
@@ -1,36 +1,51 @@
|
|||||||
.input-group.w-100
|
input.search-input.form-control(
|
||||||
input.search-input.form-control(
|
type='search',
|
||||||
type='search',
|
[(ngModel)]='query',
|
||||||
[(ngModel)]='query',
|
(ngModelChange)='onQueryChange()',
|
||||||
(ngModelChange)='onQueryChange()',
|
[class.text-danger]='notFound',
|
||||||
[class.text-danger]='notFound',
|
(click)='$event.stopPropagation()',
|
||||||
(click)='$event.stopPropagation()',
|
(keyup.enter)='findPrevious()',
|
||||||
(keyup.enter)='findNext()',
|
(keyup.esc)='close.emit()',
|
||||||
(keyup.esc)='close.emit()',
|
placeholder='Search...'
|
||||||
placeholder='Search...'
|
)
|
||||||
)
|
|
||||||
.input-group-append
|
button.btn.btn-link(
|
||||||
.input-group-text
|
(click)='findPrevious()',
|
||||||
a(
|
ngbTooltip='Next',
|
||||||
(click)='options.caseSensitive = !options.caseSensitive',
|
placement='bottom'
|
||||||
[class.text-info]='options.caseSensitive',
|
)
|
||||||
ngbTooltip='Case sensitivity',
|
i.fa.fa-fw.fa-arrow-up
|
||||||
placement='bottom'
|
|
||||||
)
|
button.btn.btn-link(
|
||||||
i.fa.fa-fw.fa-font
|
(click)='findNext()',
|
||||||
a(
|
ngbTooltip='Next',
|
||||||
(click)='options.regex = !options.regex',
|
placement='bottom'
|
||||||
[class.text-info]='options.regex',
|
)
|
||||||
ngbTooltip='Regular expression',
|
i.fa.fa-fw.fa-arrow-down
|
||||||
placement='bottom'
|
|
||||||
)
|
.mr-2
|
||||||
i.fa.fa-fw.fa-asterisk
|
|
||||||
a(
|
button.btn.btn-link(
|
||||||
(click)='options.wholeWord = !options.wholeWord',
|
(click)='options.caseSensitive = !options.caseSensitive',
|
||||||
[class.text-info]='options.wholeWord',
|
[class.active]='options.caseSensitive',
|
||||||
ngbTooltip='Whole word',
|
ngbTooltip='Case sensitivity',
|
||||||
placement='bottom'
|
placement='bottom'
|
||||||
)
|
)
|
||||||
i.fa.fa-fw.fa-text-width
|
i.fa.fa-fw.fa-font
|
||||||
|
|
||||||
|
button.btn.btn-link(
|
||||||
|
(click)='options.regex = !options.regex',
|
||||||
|
[class.active]='options.regex',
|
||||||
|
ngbTooltip='Regular expression',
|
||||||
|
placement='bottom'
|
||||||
|
)
|
||||||
|
i.fa.fa-fw.fa-asterisk
|
||||||
|
button.btn.btn-link(
|
||||||
|
(click)='options.wholeWord = !options.wholeWord',
|
||||||
|
[class.active]='options.wholeWord',
|
||||||
|
ngbTooltip='Whole word',
|
||||||
|
placement='bottom'
|
||||||
|
)
|
||||||
|
i.fa.fa-fw.fa-text-width
|
||||||
|
|
||||||
button.close.text-light.pl-3.pr-2((click)='close.emit()') ×
|
button.close.text-light.pl-3.pr-2((click)='close.emit()') ×
|
||||||
|
@@ -5,16 +5,13 @@
|
|||||||
z-index: 5;
|
z-index: 5;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 0 0 3px 3px;
|
border-radius: 0 0 3px 3px;
|
||||||
background: rgba(0, 0, 0, .75);
|
background: rgba(0, 0, 0, .95);
|
||||||
border: 1px solid rgba(0, 0, 0, .5);
|
border: 1px solid rgba(0, 0, 0, .5);
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
a {
|
button {
|
||||||
padding-left: 10px;
|
padding: 0 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
&:first-child {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,18 +23,24 @@ export class SearchPanelComponent {
|
|||||||
|
|
||||||
onQueryChange (): void {
|
onQueryChange (): void {
|
||||||
this.notFound = false
|
this.notFound = false
|
||||||
this.findNext(true)
|
this.findPrevious(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
findNext (incremental = false): void {
|
findNext (incremental = false): void {
|
||||||
|
if (!this.query) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!this.frontend.findNext(this.query, { ...this.options, incremental: incremental || undefined })) {
|
if (!this.frontend.findNext(this.query, { ...this.options, incremental: incremental || undefined })) {
|
||||||
this.notFound = true
|
this.notFound = true
|
||||||
this.toastr.error('Not found')
|
this.toastr.error('Not found')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findPrevious (): void {
|
findPrevious (incremental = false): void {
|
||||||
if (!this.frontend.findPrevious(this.query, this.options)) {
|
if (!this.query) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.frontend.findPrevious(this.query, { ...this.options, incremental: incremental || undefined })) {
|
||||||
this.notFound = true
|
this.notFound = true
|
||||||
this.toastr.error('Not found')
|
this.toastr.error('Not found')
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@ h3.mb-3 Shell
|
|||||||
)
|
)
|
||||||
option(
|
option(
|
||||||
*ngFor='let profile of profiles',
|
*ngFor='let profile of profiles',
|
||||||
[ngValue]='slugify(profile.name).toLowerCase()'
|
[ngValue]='terminal.getProfileID(profile)'
|
||||||
) {{profile.name}}
|
) {{profile.name}}
|
||||||
|
|
||||||
|
|
||||||
@@ -74,6 +74,16 @@ h3.mb-3 Shell
|
|||||||
|
|
||||||
environment-editor([(model)]='this.config.store.terminal.environment')
|
environment-editor([(model)]='this.config.store.terminal.environment')
|
||||||
|
|
||||||
|
.form-line(*ngIf='config.store.terminal.profiles.length > 0')
|
||||||
|
.header
|
||||||
|
.title Show default profiles in the selector
|
||||||
|
.description If disabled, only custom profiles will show up in the profile selector
|
||||||
|
|
||||||
|
toggle(
|
||||||
|
[(ngModel)]='config.store.terminal.showDefaultProfiles',
|
||||||
|
(ngModelChange)='config.save()'
|
||||||
|
)
|
||||||
|
|
||||||
h3.mt-3 Saved Profiles
|
h3.mt-3 Saved Profiles
|
||||||
|
|
||||||
.list-group.list-group-flush.mt-3.mb-3
|
.list-group.list-group-flush.mt-3.mb-3
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import slugify from 'slugify'
|
|
||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
@@ -17,14 +16,13 @@ export class ShellSettingsTabComponent {
|
|||||||
Platform = Platform
|
Platform = Platform
|
||||||
isConPTYAvailable: boolean
|
isConPTYAvailable: boolean
|
||||||
isConPTYStable: boolean
|
isConPTYStable: boolean
|
||||||
slugify = slugify
|
|
||||||
private configSubscription: Subscription
|
private configSubscription: Subscription
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
public hostApp: HostAppService,
|
public hostApp: HostAppService,
|
||||||
|
public terminal: TerminalService,
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private terminalService: TerminalService,
|
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
) {
|
) {
|
||||||
config.store.terminal.environment = config.store.terminal.environment || {}
|
config.store.terminal.environment = config.store.terminal.environment || {}
|
||||||
@@ -38,7 +36,7 @@ export class ShellSettingsTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit (): Promise<void> {
|
async ngOnInit (): Promise<void> {
|
||||||
this.shells = await this.terminalService.shells$.toPromise()
|
this.shells = await this.terminal.shells$.toPromise()
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy (): void {
|
ngOnDestroy (): void {
|
||||||
@@ -46,21 +44,22 @@ export class ShellSettingsTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async reload (): Promise<void> {
|
async reload (): Promise<void> {
|
||||||
this.profiles = await this.terminalService.getProfiles(true)
|
this.profiles = await this.terminal.getProfiles({ includeHidden: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
pickWorkingDirectory (): void {
|
async pickWorkingDirectory (): Promise<void> {
|
||||||
const shell = this.shells.find(x => x.id === this.config.store.terminal.shell)
|
const profile = await this.terminal.getProfileByID(this.config.store.terminal.profile)
|
||||||
|
const shell = this.shells.find(x => x.id === profile?.shell)
|
||||||
if (!shell) {
|
if (!shell) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const paths = this.electron.dialog.showOpenDialog(
|
const paths = (await this.electron.dialog.showOpenDialog(
|
||||||
this.hostApp.getWindow(),
|
this.hostApp.getWindow(),
|
||||||
{
|
{
|
||||||
defaultPath: shell.fsBase,
|
defaultPath: shell.fsBase,
|
||||||
properties: ['openDirectory', 'showHiddenFiles'],
|
properties: ['openDirectory', 'showHiddenFiles'],
|
||||||
}
|
}
|
||||||
)
|
)).filePaths
|
||||||
if (paths) {
|
if (paths) {
|
||||||
this.config.store.terminal.workingDirectory = paths[0]
|
this.config.store.terminal.workingDirectory = paths[0]
|
||||||
}
|
}
|
||||||
@@ -69,7 +68,8 @@ export class ShellSettingsTabComponent {
|
|||||||
newProfile (shell: Shell): void {
|
newProfile (shell: Shell): void {
|
||||||
const profile: Profile = {
|
const profile: Profile = {
|
||||||
name: shell.name || '',
|
name: shell.name || '',
|
||||||
sessionOptions: this.terminalService.optionsFromShell(shell),
|
shell: shell.id,
|
||||||
|
sessionOptions: this.terminal.optionsFromShell(shell),
|
||||||
}
|
}
|
||||||
this.config.store.terminal.profiles = [profile, ...this.config.store.terminal.profiles]
|
this.config.store.terminal.profiles = [profile, ...this.config.store.terminal.profiles]
|
||||||
this.config.save()
|
this.config.save()
|
||||||
|
@@ -29,11 +29,11 @@ h3.mb-3 Terminal
|
|||||||
[value]='"audible"'
|
[value]='"audible"'
|
||||||
)
|
)
|
||||||
| Audible
|
| Audible
|
||||||
|
|
||||||
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.bell != "audible" && config.store.terminal.shell.startsWith("wsl")')
|
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.bell != "audible" && config.store.terminal.profile.startsWith("wsl")')
|
||||||
.mr-auto WSL terminal bell can only be muted via Volume Mixer
|
.mr-auto WSL terminal bell can only be muted via Volume Mixer
|
||||||
button.btn.btn-secondary((click)='openWSLVolumeMixer()') Show Mixer
|
button.btn.btn-secondary((click)='openWSLVolumeMixer()') Show Mixer
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title Right click
|
.title Right click
|
||||||
@@ -63,11 +63,11 @@ h3.mb-3 Terminal
|
|||||||
value='paste'
|
value='paste'
|
||||||
)
|
)
|
||||||
| Paste
|
| Paste
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title Paste on middle-click
|
.title Paste on middle-click
|
||||||
|
|
||||||
toggle(
|
toggle(
|
||||||
[(ngModel)]='config.store.terminal.pasteOnMiddleClick',
|
[(ngModel)]='config.store.terminal.pasteOnMiddleClick',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
@@ -76,7 +76,7 @@ h3.mb-3 Terminal
|
|||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title Auto-open a terminal on app start
|
.title Auto-open a terminal on app start
|
||||||
|
|
||||||
toggle(
|
toggle(
|
||||||
[(ngModel)]='config.store.terminal.autoOpen',
|
[(ngModel)]='config.store.terminal.autoOpen',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
@@ -85,7 +85,7 @@ h3.mb-3 Terminal
|
|||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title Restore terminal tabs on app start
|
.title Restore terminal tabs on app start
|
||||||
|
|
||||||
toggle(
|
toggle(
|
||||||
[(ngModel)]='config.store.terminal.recoverTabs',
|
[(ngModel)]='config.store.terminal.recoverTabs',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
@@ -116,7 +116,7 @@ h3.mb-3 Terminal
|
|||||||
[(ngModel)]='config.store.terminal.scrollOnInput',
|
[(ngModel)]='config.store.terminal.scrollOnInput',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
)
|
)
|
||||||
|
|
||||||
.form-line
|
.form-line
|
||||||
.header
|
.header
|
||||||
.title Use Alt key as the Meta key
|
.title Use Alt key as the Meta key
|
||||||
@@ -125,3 +125,23 @@ h3.mb-3 Terminal
|
|||||||
[(ngModel)]='config.store.terminal.altIsMeta',
|
[(ngModel)]='config.store.terminal.altIsMeta',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Word separators
|
||||||
|
.description Double-click selection will stop at these characters
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
placeholder=' ()[]{}\'"',
|
||||||
|
[(ngModel)]='config.store.terminal.wordSeparator',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
)
|
||||||
|
|
||||||
|
.form-line
|
||||||
|
.header
|
||||||
|
.title Warn on multi-line paste
|
||||||
|
.description Show a confirmation box when pasting multiple lines
|
||||||
|
toggle(
|
||||||
|
[(ngModel)]='config.store.terminal.warnOnMultilinePaste',
|
||||||
|
(ngModelChange)='config.save()',
|
||||||
|
)
|
||||||
|
@@ -31,11 +31,13 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
workingDirectory: '',
|
workingDirectory: '',
|
||||||
alwaysUseWorkingDirectory: false,
|
alwaysUseWorkingDirectory: false,
|
||||||
altIsMeta: false,
|
altIsMeta: false,
|
||||||
|
wordSeparator: ' ()[]{}\'"',
|
||||||
colorScheme: {
|
colorScheme: {
|
||||||
__nonStructural: true,
|
__nonStructural: true,
|
||||||
name: 'Material',
|
name: 'Material',
|
||||||
foreground: '#eceff1',
|
foreground: '#eceff1',
|
||||||
background: 'rgba(38, 50, 56, 1)',
|
background: 'rgba(38, 50, 56, 1)',
|
||||||
|
selection: null,
|
||||||
cursor: '#FFCC00',
|
cursor: '#FFCC00',
|
||||||
colors: [
|
colors: [
|
||||||
'#000000',
|
'#000000',
|
||||||
@@ -61,6 +63,8 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
profiles: [],
|
profiles: [],
|
||||||
useConPTY: true,
|
useConPTY: true,
|
||||||
recoverTabs: true,
|
recoverTabs: true,
|
||||||
|
warnOnMultilinePaste: true,
|
||||||
|
showDefaultProfiles: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +99,6 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
],
|
],
|
||||||
'new-tab': [
|
'new-tab': [
|
||||||
'⌘-T',
|
'⌘-T',
|
||||||
'⌘-N',
|
|
||||||
],
|
],
|
||||||
home: ['⌘-Left', 'Home'],
|
home: ['⌘-Left', 'Home'],
|
||||||
end: ['⌘-Right', 'End'],
|
end: ['⌘-Right', 'End'],
|
||||||
|
@@ -40,6 +40,7 @@ export class XTermFrontend extends Frontend {
|
|||||||
super()
|
super()
|
||||||
this.xterm = new Terminal({
|
this.xterm = new Terminal({
|
||||||
allowTransparency: true,
|
allowTransparency: true,
|
||||||
|
windowsMode: process.platform === 'win32',
|
||||||
})
|
})
|
||||||
this.xtermCore = (this.xterm as any)._core
|
this.xtermCore = (this.xterm as any)._core
|
||||||
|
|
||||||
@@ -121,6 +122,11 @@ export class XTermFrontend extends Frontend {
|
|||||||
this.xtermCore.updateCursorStyle(e)
|
this.xtermCore.updateCursorStyle(e)
|
||||||
keyboardEventHandler('keyup', e)
|
keyboardEventHandler('keyup', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.xterm.buffer.onBufferChange(() => {
|
||||||
|
const altBufferActive = this.xterm.buffer.active === this.xterm.buffer.alternate
|
||||||
|
this.alternateScreenActive.next(altBufferActive)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
attach (host: HTMLElement): void {
|
attach (host: HTMLElement): void {
|
||||||
@@ -162,10 +168,17 @@ export class XTermFrontend extends Frontend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
copySelection (): void {
|
copySelection (): void {
|
||||||
require('electron').remote.clipboard.write({
|
const text = this.getSelection()
|
||||||
text: this.getSelection(),
|
if (text.length < 1024 * 32) {
|
||||||
html: this.getSelectionAsHTML(),
|
require('electron').remote.clipboard.write({
|
||||||
})
|
text: this.getSelection(),
|
||||||
|
html: this.getSelectionAsHTML(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
require('electron').remote.clipboard.write({
|
||||||
|
text: this.getSelection(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearSelection (): void {
|
clearSelection (): void {
|
||||||
@@ -215,6 +228,7 @@ export class XTermFrontend extends Frontend {
|
|||||||
this.xterm.setOption('cursorBlink', config.terminal.cursorBlink)
|
this.xterm.setOption('cursorBlink', config.terminal.cursorBlink)
|
||||||
this.xterm.setOption('macOptionIsMeta', config.terminal.altIsMeta)
|
this.xterm.setOption('macOptionIsMeta', config.terminal.altIsMeta)
|
||||||
this.xterm.setOption('scrollback', 100000)
|
this.xterm.setOption('scrollback', 100000)
|
||||||
|
this.xterm.setOption('wordSeparator', config.terminal.wordSeparator)
|
||||||
this.configuredFontSize = config.terminal.fontSize
|
this.configuredFontSize = config.terminal.fontSize
|
||||||
this.configuredLinePadding = config.terminal.linePadding
|
this.configuredLinePadding = config.terminal.linePadding
|
||||||
this.setFontSize()
|
this.setFontSize()
|
||||||
@@ -223,6 +237,7 @@ export class XTermFrontend extends Frontend {
|
|||||||
|
|
||||||
const theme: ITheme = {
|
const theme: ITheme = {
|
||||||
foreground: config.terminal.colorScheme.foreground,
|
foreground: config.terminal.colorScheme.foreground,
|
||||||
|
selection: config.terminal.colorScheme.selection || '#88888888',
|
||||||
background: config.terminal.background === 'colorScheme' ? config.terminal.colorScheme.background : '#00000000',
|
background: config.terminal.background === 'colorScheme' ? config.terminal.colorScheme.background : '#00000000',
|
||||||
cursor: config.terminal.colorScheme.cursor,
|
cursor: config.terminal.colorScheme.cursor,
|
||||||
}
|
}
|
||||||
@@ -304,7 +319,7 @@ export class XTermFrontend extends Frontend {
|
|||||||
private getLineAsHTML (y: number, start: number, end: number): string {
|
private getLineAsHTML (y: number, start: number, end: number): string {
|
||||||
let html = '<div>'
|
let html = '<div>'
|
||||||
let lastStyle: string|null = null
|
let lastStyle: string|null = null
|
||||||
const line = (this.xterm.buffer.getLine(y) as any)._line
|
const line = (this.xterm.buffer.active.getLine(y) as any)._line
|
||||||
const cell = new CellData()
|
const cell = new CellData()
|
||||||
for (let i = start; i < end; i++) {
|
for (let i = start; i < end; i++) {
|
||||||
line.loadCell(i, cell)
|
line.loadCell(i, cell)
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import slugify from 'slugify'
|
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HotkeyDescription, HotkeyProvider } from 'terminus-core'
|
import { HotkeyDescription, HotkeyProvider } from 'terminus-core'
|
||||||
import { TerminalService } from './services/terminal.service'
|
import { TerminalService } from './services/terminal.service'
|
||||||
@@ -78,7 +77,7 @@ export class TerminalHotkeyProvider extends HotkeyProvider {
|
|||||||
return [
|
return [
|
||||||
...this.hotkeys,
|
...this.hotkeys,
|
||||||
...profiles.map(profile => ({
|
...profiles.map(profile => ({
|
||||||
id: `profile.${slugify(profile.name).toLowerCase()}`,
|
id: `profile.${this.terminal.getProfileID(profile)}`,
|
||||||
name: `New tab: ${profile.name}`,
|
name: `New tab: ${profile.name}`,
|
||||||
})),
|
})),
|
||||||
]
|
]
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import slugify from 'slugify'
|
|
||||||
|
|
||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { BrowserModule } from '@angular/platform-browser'
|
import { BrowserModule } from '@angular/platform-browser'
|
||||||
@@ -11,10 +10,12 @@ import TerminusCorePlugin, { HostAppService, ToolbarButtonProvider, TabRecoveryP
|
|||||||
import { SettingsTabProvider } from 'terminus-settings'
|
import { SettingsTabProvider } from 'terminus-settings'
|
||||||
|
|
||||||
import { AppearanceSettingsTabComponent } from './components/appearanceSettingsTab.component'
|
import { AppearanceSettingsTabComponent } from './components/appearanceSettingsTab.component'
|
||||||
|
import { ColorSchemeSettingsTabComponent } from './components/colorSchemeSettingsTab.component'
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
|
import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
|
||||||
import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.component'
|
import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.component'
|
||||||
import { ColorPickerComponent } from './components/colorPicker.component'
|
import { ColorPickerComponent } from './components/colorPicker.component'
|
||||||
|
import { ColorSchemePreviewComponent } from './components/colorSchemePreview.component'
|
||||||
import { EditProfileModalComponent } from './components/editProfileModal.component'
|
import { EditProfileModalComponent } from './components/editProfileModal.component'
|
||||||
import { EnvironmentEditorComponent } from './components/environmentEditor.component'
|
import { EnvironmentEditorComponent } from './components/environmentEditor.component'
|
||||||
import { SearchPanelComponent } from './components/searchPanel.component'
|
import { SearchPanelComponent } from './components/searchPanel.component'
|
||||||
@@ -30,7 +31,7 @@ import { TerminalDecorator } from './api/decorator'
|
|||||||
import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
|
import { TerminalContextMenuItemProvider } from './api/contextMenuProvider'
|
||||||
import { TerminalColorSchemeProvider } from './api/colorSchemeProvider'
|
import { TerminalColorSchemeProvider } from './api/colorSchemeProvider'
|
||||||
import { ShellProvider } from './api/shellProvider'
|
import { ShellProvider } from './api/shellProvider'
|
||||||
import { TerminalSettingsTabProvider, AppearanceSettingsTabProvider, ShellSettingsTabProvider } from './settings'
|
import { TerminalSettingsTabProvider, AppearanceSettingsTabProvider, ColorSchemeSettingsTabProvider, ShellSettingsTabProvider } from './settings'
|
||||||
import { DebugDecorator } from './features/debug'
|
import { DebugDecorator } from './features/debug'
|
||||||
import { PathDropDecorator } from './features/pathDrop'
|
import { PathDropDecorator } from './features/pathDrop'
|
||||||
import { ZModemDecorator } from './features/zmodem'
|
import { ZModemDecorator } from './features/zmodem'
|
||||||
@@ -68,6 +69,7 @@ import { XTermFrontend, XTermWebGLFrontend } from './frontends/xtermFrontend'
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: SettingsTabProvider, useClass: AppearanceSettingsTabProvider, multi: true },
|
{ provide: SettingsTabProvider, useClass: AppearanceSettingsTabProvider, multi: true },
|
||||||
|
{ provide: SettingsTabProvider, useClass: ColorSchemeSettingsTabProvider, multi: true },
|
||||||
{ provide: SettingsTabProvider, useClass: ShellSettingsTabProvider, multi: true },
|
{ provide: SettingsTabProvider, useClass: ShellSettingsTabProvider, multi: true },
|
||||||
{ provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true },
|
{ provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true },
|
||||||
|
|
||||||
@@ -106,14 +108,17 @@ import { XTermFrontend, XTermWebGLFrontend } from './frontends/xtermFrontend'
|
|||||||
entryComponents: [
|
entryComponents: [
|
||||||
TerminalTabComponent,
|
TerminalTabComponent,
|
||||||
AppearanceSettingsTabComponent,
|
AppearanceSettingsTabComponent,
|
||||||
|
ColorSchemeSettingsTabComponent,
|
||||||
ShellSettingsTabComponent,
|
ShellSettingsTabComponent,
|
||||||
TerminalSettingsTabComponent,
|
TerminalSettingsTabComponent,
|
||||||
EditProfileModalComponent,
|
EditProfileModalComponent,
|
||||||
] as any[],
|
] as any[],
|
||||||
declarations: [
|
declarations: [
|
||||||
ColorPickerComponent,
|
ColorPickerComponent,
|
||||||
|
ColorSchemePreviewComponent,
|
||||||
TerminalTabComponent,
|
TerminalTabComponent,
|
||||||
AppearanceSettingsTabComponent,
|
AppearanceSettingsTabComponent,
|
||||||
|
ColorSchemeSettingsTabComponent,
|
||||||
ShellSettingsTabComponent,
|
ShellSettingsTabComponent,
|
||||||
TerminalSettingsTabComponent,
|
TerminalSettingsTabComponent,
|
||||||
EditProfileModalComponent,
|
EditProfileModalComponent,
|
||||||
@@ -127,7 +132,7 @@ import { XTermFrontend, XTermWebGLFrontend } from './frontends/xtermFrontend'
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
export default class TerminalModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
|
export default class TerminalModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||||
constructor (
|
private constructor (
|
||||||
app: AppService,
|
app: AppService,
|
||||||
config: ConfigService,
|
config: ConfigService,
|
||||||
hotkeys: HotkeysService,
|
hotkeys: HotkeysService,
|
||||||
@@ -181,8 +186,7 @@ export default class TerminalModule { // eslint-disable-line @typescript-eslint/
|
|||||||
hostApp.newWindow()
|
hostApp.newWindow()
|
||||||
}
|
}
|
||||||
if (hotkey.startsWith('profile.')) {
|
if (hotkey.startsWith('profile.')) {
|
||||||
const profiles = await terminal.getProfiles()
|
const profile = await terminal.getProfileByID(hotkey.split('.')[1])
|
||||||
const profile = profiles.find(x => slugify(x.name).toLowerCase() === hotkey.split('.')[1])
|
|
||||||
if (profile) {
|
if (profile) {
|
||||||
terminal.openTabWithOptions(profile.sessionOptions)
|
terminal.openTabWithOptions(profile.sessionOptions)
|
||||||
}
|
}
|
||||||
|
@@ -332,7 +332,7 @@ export class SessionsService {
|
|||||||
logger: Logger
|
logger: Logger
|
||||||
private lastID = 0
|
private lastID = 0
|
||||||
|
|
||||||
constructor (
|
private constructor (
|
||||||
log: LogService,
|
log: LogService,
|
||||||
) {
|
) {
|
||||||
require('../bufferizedPTY')(nodePTY) // eslint-disable-line @typescript-eslint/no-var-requires
|
require('../bufferizedPTY')(nodePTY) // eslint-disable-line @typescript-eslint/no-var-requires
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user