mirror of
https://github.com/Eugeny/tabby.git
synced 2025-08-01 15:06:59 +00:00
Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
39732908a3 |
@@ -225,115 +225,6 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "LeSeulArtichaut",
|
||||
"name": "LeSeulArtichaut",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/38361244?v=4",
|
||||
"profile": "https://github.com/LeSeulArtichaut",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "CyrilTaylor",
|
||||
"name": "Cyril Taylor",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/12631466?v=4",
|
||||
"profile": "https://github.com/CyrilTaylor",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nstefanou",
|
||||
"name": "nstefanou",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/51129173?v=4",
|
||||
"profile": "https://github.com/nstefanou",
|
||||
"contributions": [
|
||||
"code",
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "3l0w",
|
||||
"name": "Gwilherm Folliot",
|
||||
"avatar_url": "https://avatars2.githubusercontent.com/u/37798980?v=4",
|
||||
"profile": "https://github.com/3l0w",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dimitory",
|
||||
"name": "Dmitry Pronin",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/475955?v=4",
|
||||
"profile": "https://github.com/Dimitory",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "JonathanBeverley",
|
||||
"name": "Jonathan Beverley",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/20328966?v=4",
|
||||
"profile": "https://github.com/JonathanBeverley",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "zend",
|
||||
"name": "Zenghai Liang",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/25160?v=4",
|
||||
"profile": "https://github.com/zend",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "matishadow",
|
||||
"name": "Mateusz Tracz",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/9083085?v=4",
|
||||
"profile": "https://about.me/matishadow",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "pinpins",
|
||||
"name": "pinpin",
|
||||
"avatar_url": "https://avatars3.githubusercontent.com/u/36234677?v=4",
|
||||
"profile": "https://zergpool.com",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "TakuroOnoda",
|
||||
"name": "Takuro Onoda",
|
||||
"avatar_url": "https://avatars0.githubusercontent.com/u/1407926?v=4",
|
||||
"profile": "https://github.com/TakuroOnoda",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
@@ -341,6 +232,5 @@
|
||||
"projectOwner": "Eugeny",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"commitConvention": "none",
|
||||
"skipCi": true
|
||||
"commitConvention": "none"
|
||||
}
|
||||
|
@@ -79,7 +79,6 @@ rules:
|
||||
args: after-used
|
||||
argsIgnorePattern: ^_
|
||||
no-undef: error
|
||||
no-var: error
|
||||
object-curly-spacing:
|
||||
- error
|
||||
- always
|
||||
@@ -99,8 +98,3 @@ rules:
|
||||
'@typescript-eslint/restrict-template-expressions': off
|
||||
'@typescript-eslint/no-dynamic-delete': 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
|
||||
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
version: 10
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
version: 10
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
|
5
.github/workflows/linux.yml
vendored
5
.github/workflows/linux.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
version: 10
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
@@ -25,6 +25,9 @@ jobs:
|
||||
- name: Build native deps
|
||||
run: scripts/build-native.js
|
||||
|
||||
- name: Build typings
|
||||
run: yarn run build:typings
|
||||
|
||||
- name: Webpack
|
||||
run: yarn run build
|
||||
|
||||
|
11
.github/workflows/macos.yml
vendored
11
.github/workflows/macos.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
version: 10
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
@@ -25,24 +25,23 @@ jobs:
|
||||
- name: Build native deps
|
||||
run: scripts/build-native.js
|
||||
|
||||
- name: Build typings
|
||||
run: yarn run build:typings
|
||||
|
||||
- name: Webpack
|
||||
run: yarn run build
|
||||
|
||||
- name: Prepackage plugins
|
||||
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
|
||||
run: scripts/build-macos.js
|
||||
if: github.repository == 'Eugeny/terminus' && github.event_name == 'push'
|
||||
env:
|
||||
#DEBUG: electron-builder,electron-builder:*
|
||||
DEBUG: electron-builder,electron-builder:*
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPSTORE_USERNAME: ${{ secrets.APPSTORE_USERNAME }}
|
||||
APPSTORE_PASSWORD: ${{ secrets.APPSTORE_PASSWORD }}
|
||||
|
||||
- name: Build packages without signing
|
||||
run: scripts/build-macos.js
|
||||
|
4
.github/workflows/windows.yml
vendored
4
.github/workflows/windows.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: Installing Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 10
|
||||
version: 10
|
||||
|
||||
- name: Build
|
||||
shell: powershell
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
mkdir artifact-setup
|
||||
mv dist/*-setup.exe artifact-setup/
|
||||
mkdir artifact-portable
|
||||
mv dist/*-portable.zip artifact-portable/
|
||||
mv dist/*-portable.exe artifact-portable/
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
name: Upload installer
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -28,5 +28,3 @@ docs/api
|
||||
.electron-symbols
|
||||
sentry.properties
|
||||
sentry-symbols.js
|
||||
|
||||
terminus-ssh/util/pagent.exe
|
||||
|
@@ -1,5 +1,5 @@
|
||||
language: node_js
|
||||
node_js: 10
|
||||
node_js: 11
|
||||
|
||||
stages:
|
||||
- Build
|
||||
|
77
README.md
77
README.md
@@ -13,13 +13,12 @@
|
||||
|
||||
**Terminus** is a highly configurable terminal emulator for Windows, macOS and Linux
|
||||
|
||||
* Integrated SSH client and connection manager
|
||||
* Theming and color schemes
|
||||
* Fully configurable shortcuts
|
||||
* Split panes
|
||||
* Remembers your tabs
|
||||
* PowerShell (and PS Core), WSL, Git-Bash, Cygwin, Cmder and CMD support
|
||||
* Direct file transfer from/to SSH sessions via Zmodem
|
||||
* Integrated SSH client and connection manager
|
||||
* Full Unicode support including double-width characters
|
||||
* Doesn't choke on fast-flowing outputs
|
||||
* Proper shell experience on Windows including tab completion (via Clink)
|
||||
@@ -35,15 +34,12 @@
|
||||
|
||||
---
|
||||
|
||||
# Portable
|
||||
|
||||
For portable in windows, user can create folder `data` at the same directory as `Terminal.exe` to save the settings.
|
||||
|
||||
# Plugins
|
||||
|
||||
Plugins and themes can be installed directly from the Settings view inside Terminus.
|
||||
|
||||
* [clickable-links](https://github.com/Eugeny/terminus-clickable-links) - makes paths and URLs in the terminal clickable
|
||||
* [shell-selector](https://github.com/Eugeny/terminus-shell-selector) - a quick shell selector pane
|
||||
* [title-control](https://github.com/kbjr/terminus-title-control) - allows modifying the title of the terminal tabs by providing a prefix, suffix, and/or strings to be removed
|
||||
* [quick-cmds](https://github.com/Domain/terminus-quick-cmds) - quickly send commands to one or all terminal tabs
|
||||
* [save-output](https://github.com/Eugeny/terminus-save-output) - record terminal output into a file
|
||||
@@ -70,61 +66,42 @@ See [HACKING.md](https://github.com/Eugeny/terminus/blob/master/HACKING.md) and
|
||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<!-- prettier-ignore -->
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><a href="http://www.russellmyers.com"><img src="https://avatars2.githubusercontent.com/u/184085?v=4" width="100px;" alt=""/><br /><sub><b>Russell Myers</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mezner" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.morwire.com"><img src="https://avatars1.githubusercontent.com/u/3991658?v=4" width="100px;" alt=""/><br /><sub><b>Austin Warren</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ehwarren" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Drachenkaetzchen"><img src="https://avatars1.githubusercontent.com/u/162974?v=4" width="100px;" alt=""/><br /><sub><b>Felicia Hummel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Drachenkaetzchen" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mikemaccana"><img src="https://avatars2.githubusercontent.com/u/172594?v=4" width="100px;" alt=""/><br /><sub><b>Mike MacCana</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mikemaccana" title="Tests">⚠️</a> <a href="#design-mikemaccana" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/yxuko"><img src="https://avatars1.githubusercontent.com/u/1786317?v=4" width="100px;" alt=""/><br /><sub><b>Yacine Kanzari</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yxuko" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/BBJip"><img src="https://avatars2.githubusercontent.com/u/32908927?v=4" width="100px;" alt=""/><br /><sub><b>BBJip</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=BBJip" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Futagirl"><img src="https://avatars2.githubusercontent.com/u/33533958?v=4" width="100px;" alt=""/><br /><sub><b>Futagirl</b></sub></a><br /><a href="#design-Futagirl" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="http://www.russellmyers.com"><img src="https://avatars2.githubusercontent.com/u/184085?v=4" width="100px;" alt="Russell Myers"/><br /><sub><b>Russell Myers</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mezner" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.morwire.com"><img src="https://avatars1.githubusercontent.com/u/3991658?v=4" width="100px;" alt="Austin Warren"/><br /><sub><b>Austin Warren</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ehwarren" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Drachenkaetzchen"><img src="https://avatars1.githubusercontent.com/u/162974?v=4" width="100px;" alt="Felicia Hummel"/><br /><sub><b>Felicia Hummel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Drachenkaetzchen" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/mikemaccana"><img src="https://avatars2.githubusercontent.com/u/172594?v=4" width="100px;" alt="Mike MacCana"/><br /><sub><b>Mike MacCana</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mikemaccana" title="Tests">⚠️</a> <a href="#design-mikemaccana" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://github.com/yxuko"><img src="https://avatars1.githubusercontent.com/u/1786317?v=4" width="100px;" alt="Yacine Kanzari"/><br /><sub><b>Yacine Kanzari</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yxuko" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/BBJip"><img src="https://avatars2.githubusercontent.com/u/32908927?v=4" width="100px;" alt="BBJip"/><br /><sub><b>BBJip</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=BBJip" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Futagirl"><img src="https://avatars2.githubusercontent.com/u/33533958?v=4" width="100px;" alt="Futagirl"/><br /><sub><b>Futagirl</b></sub></a><br /><a href="#design-Futagirl" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://www.levrik.io"><img src="https://avatars3.githubusercontent.com/u/9491603?v=4" width="100px;" alt=""/><br /><sub><b>Levin Rickert</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=levrik" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://kwonoj.github.io"><img src="https://avatars2.githubusercontent.com/u/1210596?v=4" width="100px;" alt=""/><br /><sub><b>OJ Kwon</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=kwonoj" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Domain"><img src="https://avatars2.githubusercontent.com/u/903197?v=4" width="100px;" alt=""/><br /><sub><b>domain</b></sub></a><br /><a href="#plugin-Domain" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Domain" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.jbrumond.me"><img src="https://avatars1.githubusercontent.com/u/195127?v=4" width="100px;" alt=""/><br /><sub><b>James Brumond</b></sub></a><br /><a href="#plugin-kbjr" title="Plugin/utility libraries">🔌</a></td>
|
||||
<td align="center"><a href="http://www.growingwiththeweb.com"><img src="https://avatars0.githubusercontent.com/u/2193314?v=4" width="100px;" alt=""/><br /><sub><b>Daniel Imms</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Code">💻</a> <a href="#plugin-Tyriar" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/baflo"><img src="https://avatars2.githubusercontent.com/u/834350?v=4" width="100px;" alt=""/><br /><sub><b>Florian Bachmann</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=baflo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://michael-kuehnel.de"><img src="https://avatars2.githubusercontent.com/u/441011?v=4" width="100px;" alt=""/><br /><sub><b>Michael Kühnel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mischah" title="Code">💻</a> <a href="#design-mischah" title="Design">🎨</a></td>
|
||||
<td align="center"><a href="https://www.levrik.io"><img src="https://avatars3.githubusercontent.com/u/9491603?v=4" width="100px;" alt="Levin Rickert"/><br /><sub><b>Levin Rickert</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=levrik" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://kwonoj.github.io"><img src="https://avatars2.githubusercontent.com/u/1210596?v=4" width="100px;" alt="OJ Kwon"/><br /><sub><b>OJ Kwon</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=kwonoj" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Domain"><img src="https://avatars2.githubusercontent.com/u/903197?v=4" width="100px;" alt="domain"/><br /><sub><b>domain</b></sub></a><br /><a href="#plugin-Domain" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Domain" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.jbrumond.me"><img src="https://avatars1.githubusercontent.com/u/195127?v=4" width="100px;" alt="James Brumond"/><br /><sub><b>James Brumond</b></sub></a><br /><a href="#plugin-kbjr" title="Plugin/utility libraries">🔌</a></td>
|
||||
<td align="center"><a href="http://www.growingwiththeweb.com"><img src="https://avatars0.githubusercontent.com/u/2193314?v=4" width="100px;" alt="Daniel Imms"/><br /><sub><b>Daniel Imms</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Code">💻</a> <a href="#plugin-Tyriar" title="Plugin/utility libraries">🔌</a> <a href="https://github.com/Eugeny/terminus/commits?author=Tyriar" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/baflo"><img src="https://avatars2.githubusercontent.com/u/834350?v=4" width="100px;" alt="Florian Bachmann"/><br /><sub><b>Florian Bachmann</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=baflo" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://michael-kuehnel.de"><img src="https://avatars2.githubusercontent.com/u/441011?v=4" width="100px;" alt="Michael Kühnel"/><br /><sub><b>Michael Kühnel</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=mischah" title="Code">💻</a> <a href="#design-mischah" title="Design">🎨</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/NieLeben"><img src="https://avatars3.githubusercontent.com/u/47182955?v=4" width="100px;" alt=""/><br /><sub><b>Tilmann Meyer</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=NieLeben" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.jubeat.net"><img src="https://avatars3.githubusercontent.com/u/11289158?v=4" width="100px;" alt=""/><br /><sub><b>PM Extra</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/issues?q=author%3APMExtra" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://jjuhas.keybase.pub//"><img src="https://avatars1.githubusercontent.com/u/6438760?v=4" width="100px;" alt=""/><br /><sub><b>Jonathan</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=IgnusG" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://hans-koch.me"><img src="https://avatars0.githubusercontent.com/u/1093709?v=4" width="100px;" alt=""/><br /><sub><b>Hans Koch</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hammster" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://thepuzzlemaker.info"><img src="https://avatars3.githubusercontent.com/u/12666617?v=4" width="100px;" alt=""/><br /><sub><b>Dak Smyth</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ThePuzzlemaker" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://yfwz100.github.io"><img src="https://avatars2.githubusercontent.com/u/983211?v=4" width="100px;" alt=""/><br /><sub><b>Wang Zhi</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yfwz100" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/jack1142"><img src="https://avatars0.githubusercontent.com/u/6032823?v=4" width="100px;" alt=""/><br /><sub><b>jack1142</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=jack1142" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/NieLeben"><img src="https://avatars3.githubusercontent.com/u/47182955?v=4" width="100px;" alt="Tilmann Meyer"/><br /><sub><b>Tilmann Meyer</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=NieLeben" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.jubeat.net"><img src="https://avatars3.githubusercontent.com/u/11289158?v=4" width="100px;" alt="PM Extra"/><br /><sub><b>PM Extra</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/issues?q=author%3APMExtra" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="https://jjuhas.keybase.pub//"><img src="https://avatars1.githubusercontent.com/u/6438760?v=4" width="100px;" alt="Jonathan"/><br /><sub><b>Jonathan</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=IgnusG" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://hans-koch.me"><img src="https://avatars0.githubusercontent.com/u/1093709?v=4" width="100px;" alt="Hans Koch"/><br /><sub><b>Hans Koch</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hammster" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://thepuzzlemaker.info"><img src="https://avatars3.githubusercontent.com/u/12666617?v=4" width="100px;" alt="Dak Smyth"/><br /><sub><b>Dak Smyth</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ThePuzzlemaker" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://yfwz100.github.io"><img src="https://avatars2.githubusercontent.com/u/983211?v=4" width="100px;" alt="Wang Zhi"/><br /><sub><b>Wang Zhi</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=yfwz100" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/jack1142"><img src="https://avatars0.githubusercontent.com/u/6032823?v=4" width="100px;" alt="jack1142"/><br /><sub><b>jack1142</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=jack1142" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/hdougie"><img src="https://avatars1.githubusercontent.com/u/450799?v=4" width="100px;" alt=""/><br /><sub><b>Howie Douglas</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hdougie" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://chriskaczor.com"><img src="https://avatars2.githubusercontent.com/u/180906?v=4" width="100px;" alt=""/><br /><sub><b>Chris Kaczor</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ckaczor" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.boxmein.net"><img src="https://avatars1.githubusercontent.com/u/358714?v=4" width="100px;" alt=""/><br /><sub><b>Johannes Kadak</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=boxmein" 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/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>
|
||||
<td align="center"><a href="https://github.com/3l0w"><img src="https://avatars2.githubusercontent.com/u/37798980?v=4" width="100px;" alt=""/><br /><sub><b>Gwilherm Folliot</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=3l0w" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Dimitory"><img src="https://avatars0.githubusercontent.com/u/475955?v=4" width="100px;" alt=""/><br /><sub><b>Dmitry Pronin</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=dimitory" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/JonathanBeverley"><img src="https://avatars1.githubusercontent.com/u/20328966?v=4" width="100px;" alt=""/><br /><sub><b>Jonathan Beverley</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=JonathanBeverley" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/zend"><img src="https://avatars1.githubusercontent.com/u/25160?v=4" width="100px;" alt=""/><br /><sub><b>Zenghai Liang</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=zend" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://about.me/matishadow"><img src="https://avatars0.githubusercontent.com/u/9083085?v=4" width="100px;" alt=""/><br /><sub><b>Mateusz Tracz</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=matishadow" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://zergpool.com"><img src="https://avatars3.githubusercontent.com/u/36234677?v=4" width="100px;" alt=""/><br /><sub><b>pinpin</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=pinpins" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/TakuroOnoda"><img src="https://avatars0.githubusercontent.com/u/1407926?v=4" width="100px;" alt=""/><br /><sub><b>Takuro Onoda</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=TakuroOnoda" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/hdougie"><img src="https://avatars1.githubusercontent.com/u/450799?v=4" width="100px;" alt="Howie Douglas"/><br /><sub><b>Howie Douglas</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=hdougie" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://chriskaczor.com"><img src="https://avatars2.githubusercontent.com/u/180906?v=4" width="100px;" alt="Chris Kaczor"/><br /><sub><b>Chris Kaczor</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=ckaczor" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://www.boxmein.net"><img src="https://avatars1.githubusercontent.com/u/358714?v=4" width="100px;" alt="Johannes Kadak"/><br /><sub><b>Johannes Kadak</b></sub></a><br /><a href="https://github.com/Eugeny/terminus/commits?author=boxmein" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-enable -->
|
||||
<!-- prettier-ignore-end -->
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
||||
|
@@ -8,10 +8,10 @@ html
|
||||
window.nodeRequire = require
|
||||
script(src='./preload.js')
|
||||
script(src='./bundle.js', defer)
|
||||
style#custom-css
|
||||
style.
|
||||
body { transition: 0.5s background; }
|
||||
body
|
||||
style#custom-css
|
||||
app-root
|
||||
.preload-logo
|
||||
div
|
||||
@@ -20,3 +20,5 @@ html
|
||||
sup α
|
||||
.progress
|
||||
.bar(style='width: 0%')
|
||||
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { app, ipcMain, Menu, Tray, shell, globalShortcut } from 'electron'
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import { app, ipcMain, Menu, Tray, shell } from 'electron'
|
||||
import * as electron from 'electron'
|
||||
import { loadConfig } from './config'
|
||||
import { Window, WindowOptions } from './window'
|
||||
@@ -9,38 +8,25 @@ export class Application {
|
||||
private windows: Window[] = []
|
||||
|
||||
constructor () {
|
||||
ipcMain.on('app:config-change', (_event, config) => {
|
||||
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()
|
||||
})
|
||||
}
|
||||
ipcMain.on('app:config-change', () => {
|
||||
this.broadcast('host:config-change')
|
||||
})
|
||||
|
||||
const configData = loadConfig()
|
||||
if (process.platform === 'linux') {
|
||||
app.commandLine.appendSwitch('no-sandbox')
|
||||
if (((configData.appearance || {}).opacity || 1) !== 1) {
|
||||
if (process.platform === 'linux' && ((configData.appearance || {}).opacity || 1) !== 1) {
|
||||
app.commandLine.appendSwitch('enable-transparent-visuals')
|
||||
app.disableHardwareAcceleration()
|
||||
}
|
||||
}
|
||||
|
||||
app.commandLine.appendSwitch('disable-http-cache')
|
||||
app.commandLine.appendSwitch('lang', 'EN')
|
||||
app.allowRendererProcessReuse = false
|
||||
|
||||
for (const flag of configData.flags || [['force_discrete_gpu', '0']]) {
|
||||
app.commandLine.appendSwitch(flag[0], flag[1])
|
||||
}
|
||||
}
|
||||
|
||||
init (): void {
|
||||
init () {
|
||||
electron.screen.on('display-metrics-changed', () => this.broadcast('host:display-metrics-changed'))
|
||||
}
|
||||
|
||||
@@ -54,9 +40,6 @@ export class Application {
|
||||
this.enableTray()
|
||||
}
|
||||
})
|
||||
window.closed$.subscribe(() => {
|
||||
this.windows = this.windows.filter(x => x !== window)
|
||||
})
|
||||
if (process.platform === 'darwin') {
|
||||
this.setupMenu()
|
||||
}
|
||||
@@ -64,38 +47,20 @@ export class Application {
|
||||
return window
|
||||
}
|
||||
|
||||
onGlobalHotkey (): void {
|
||||
if (this.windows.some(x => x.isFocused())) {
|
||||
broadcast (event, ...args) {
|
||||
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 {
|
||||
for (const window of this.windows) {
|
||||
window.send(event, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
async send (event: string, ...args): Promise<void> {
|
||||
async send (event, ...args) {
|
||||
if (!this.hasWindows()) {
|
||||
await this.newWindow()
|
||||
}
|
||||
this.windows.filter(w => !w.isDestroyed())[0].send(event, ...args)
|
||||
}
|
||||
|
||||
enableTray (): void {
|
||||
enableTray () {
|
||||
if (this.tray) {
|
||||
return
|
||||
}
|
||||
@@ -106,7 +71,7 @@ export class Application {
|
||||
this.tray = new Tray(`${app.getAppPath()}/assets/tray.png`)
|
||||
}
|
||||
|
||||
this.tray.on('click', () => setTimeout(() => this.focus()))
|
||||
this.tray.on('click', () => setTimeout(() => this.focus()));
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([{
|
||||
label: 'Show',
|
||||
@@ -120,28 +85,23 @@ export class Application {
|
||||
this.tray.setToolTip(`Terminus ${app.getVersion()}`)
|
||||
}
|
||||
|
||||
disableTray (): void {
|
||||
disableTray () {
|
||||
if (this.tray) {
|
||||
this.tray.destroy()
|
||||
this.tray = null
|
||||
}
|
||||
}
|
||||
|
||||
hasWindows (): boolean {
|
||||
hasWindows () {
|
||||
return !!this.windows.length
|
||||
}
|
||||
|
||||
focus (): void {
|
||||
focus () {
|
||||
for (let window of this.windows) {
|
||||
window.show()
|
||||
}
|
||||
}
|
||||
|
||||
handleSecondInstance (argv: string[], cwd: string): void {
|
||||
this.presentAllWindows()
|
||||
this.windows[this.windows.length - 1].handleSecondInstance(argv, cwd)
|
||||
}
|
||||
|
||||
private setupMenu () {
|
||||
let template: Electron.MenuItemConstructorOptions[] = [
|
||||
{
|
||||
@@ -222,7 +182,7 @@ export class Application {
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { app } from 'electron'
|
||||
|
||||
export function parseArgs (argv: string[], cwd: string): any {
|
||||
export function parseArgs (argv, cwd) {
|
||||
if (argv[0].includes('node')) {
|
||||
argv = argv.slice(1)
|
||||
}
|
||||
@@ -20,25 +20,25 @@ export function parseArgs (argv: string[], cwd: string): any {
|
||||
return yargs.option('escape', {
|
||||
alias: 'e',
|
||||
type: 'boolean',
|
||||
describe: 'Perform shell escaping',
|
||||
describe: 'Perform shell escaping'
|
||||
}).positional('text', {
|
||||
type: 'string',
|
||||
type: 'string'
|
||||
})
|
||||
})
|
||||
.version('version', '', app.getVersion())
|
||||
.option('debug', {
|
||||
alias: 'd',
|
||||
describe: 'Show DevTools on start',
|
||||
type: 'boolean',
|
||||
type: 'boolean'
|
||||
})
|
||||
.option('hidden', {
|
||||
describe: 'Start minimized',
|
||||
type: 'boolean',
|
||||
type: 'boolean'
|
||||
})
|
||||
.option('version', {
|
||||
alias: 'v',
|
||||
describe: 'Show version and exit',
|
||||
type: 'boolean',
|
||||
type: 'boolean'
|
||||
})
|
||||
.help('help')
|
||||
.parse(argv.slice(1))
|
||||
|
@@ -1,10 +1,9 @@
|
||||
import './portable'
|
||||
import './sentry'
|
||||
import './lru'
|
||||
import { app, ipcMain, Menu } from 'electron'
|
||||
import { parseArgs } from './cli'
|
||||
import { Application } from './app'
|
||||
import electronDebug = require('electron-debug')
|
||||
import * as electronDebug from 'electron-debug'
|
||||
|
||||
if (!process.env.TERMINUS_PLUGINS) {
|
||||
process.env.TERMINUS_PLUGINS = ''
|
||||
@@ -34,7 +33,7 @@ process.on('uncaughtException' as any, err => {
|
||||
})
|
||||
|
||||
app.on('second-instance', (_event, argv, cwd) => {
|
||||
application.handleSecondInstance(argv, cwd)
|
||||
application.send('host:second-instance', parseArgs(argv, cwd), cwd)
|
||||
})
|
||||
|
||||
const argv = parseArgs(process.argv, process.cwd())
|
||||
@@ -59,8 +58,8 @@ app.on('ready', () => {
|
||||
label: 'New window',
|
||||
click () {
|
||||
this.app.newWindow()
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
]))
|
||||
}
|
||||
application.init()
|
||||
|
@@ -1,15 +1,13 @@
|
||||
import * as createLRU from 'lru-cache'
|
||||
import * as fs from 'fs'
|
||||
const lru = createLRU({ max: 256, maxAge: 250 })
|
||||
const origLstat = fs.realpathSync.bind(fs)
|
||||
let lru = require('lru-cache')({ max: 256, maxAge: 250 })
|
||||
|
||||
let fs = require('fs')
|
||||
let origLstat = fs.realpathSync.bind(fs)
|
||||
|
||||
// NB: The biggest offender of thrashing realpathSync is the node module system
|
||||
// itself, which we can't get into via any sane means.
|
||||
require('fs').realpathSync = function (p) {
|
||||
let r = lru.get(p)
|
||||
if (r) {
|
||||
return r
|
||||
}
|
||||
if (r) return r
|
||||
|
||||
r = origLstat(p)
|
||||
lru.set(p, r)
|
||||
|
@@ -1,24 +0,0 @@
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
|
||||
let appPath: string | null = null
|
||||
try {
|
||||
appPath = path.dirname(require('electron').app.getPath('exe'))
|
||||
} catch {
|
||||
appPath = path.dirname(require('electron').remote.app.getPath('exe'))
|
||||
}
|
||||
|
||||
if (null != appPath) {
|
||||
if(fs.existsSync(path.join(appPath, 'terminus-data'))) {
|
||||
fs.renameSync(path.join(appPath, 'terminus-data'), path.join(appPath, 'data'))
|
||||
}
|
||||
const portableData = path.join(appPath, 'data')
|
||||
if (fs.existsSync(portableData)) {
|
||||
console.log('reset user data to ' + portableData)
|
||||
try {
|
||||
require('electron').app.setPath('userData', portableData)
|
||||
} catch {
|
||||
require('electron').remote.app.setPath('userData', portableData)
|
||||
}
|
||||
}
|
||||
}
|
7
app/lib/sentry.ts
Executable file → Normal file
7
app/lib/sentry.ts
Executable file → Normal file
@@ -1,5 +1,4 @@
|
||||
const { init } = process.type === 'main' ? require('@sentry/electron/dist/main') : require('@sentry/electron/dist/renderer')
|
||||
import * as isDev from 'electron-is-dev'
|
||||
|
||||
|
||||
const SENTRY_DSN = 'https://4717a0a7ee0b4429bd3a0f06c3d7eec3@sentry.io/181876'
|
||||
@@ -10,12 +9,10 @@ try {
|
||||
release = require('electron').remote.app.getVersion()
|
||||
}
|
||||
|
||||
if (!isDev) {
|
||||
init({
|
||||
init({
|
||||
dsn: SENTRY_DSN,
|
||||
release,
|
||||
integrations (integrations) {
|
||||
return integrations.filter(integration => integration.name !== 'Breadcrumbs')
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@@ -1,20 +1,18 @@
|
||||
import * as glasstron from 'glasstron'
|
||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||
glasstron.init()
|
||||
}
|
||||
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
import { debounceTime } from 'rxjs/operators'
|
||||
import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen } from 'electron'
|
||||
import ElectronConfig = require('electron-config')
|
||||
import { BrowserWindow, app, ipcMain, Rectangle, screen } from 'electron'
|
||||
import * as ElectronConfig from 'electron-config'
|
||||
import * as os from 'os'
|
||||
import * as path from 'path'
|
||||
|
||||
import { parseArgs } from './cli'
|
||||
import { loadConfig } from './config'
|
||||
|
||||
let SetWindowCompositionAttribute: any
|
||||
let AccentState: any
|
||||
let DwmEnableBlurBehindWindow: any
|
||||
if (process.platform === 'win32') {
|
||||
SetWindowCompositionAttribute = require('windows-swca').SetWindowCompositionAttribute
|
||||
AccentState = require('windows-swca').ACCENT_STATE
|
||||
DwmEnableBlurBehindWindow = require('windows-blurbehind').DwmEnableBlurBehindWindow
|
||||
}
|
||||
|
||||
@@ -25,20 +23,15 @@ export interface WindowOptions {
|
||||
export class Window {
|
||||
ready: Promise<void>
|
||||
private visible = new Subject<boolean>()
|
||||
private closed = new Subject<void>()
|
||||
private window: BrowserWindow
|
||||
private windowConfig: ElectronConfig
|
||||
private windowBounds: Rectangle
|
||||
private closing = false
|
||||
private lastVibrancy: {enabled: boolean, type?: string} | null = null
|
||||
private disableVibrancyWhileDragging = false
|
||||
private configStore: any
|
||||
|
||||
get visible$ (): Observable<boolean> { return this.visible }
|
||||
get closed$ (): Observable<void> { return this.closed }
|
||||
|
||||
constructor (options?: WindowOptions) {
|
||||
this.configStore = loadConfig()
|
||||
let configData = loadConfig()
|
||||
|
||||
options = options || {}
|
||||
|
||||
@@ -55,7 +48,6 @@ export class Window {
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
preload: path.join(__dirname, 'sentry.js'),
|
||||
backgroundThrottling: false,
|
||||
},
|
||||
frame: false,
|
||||
show: false,
|
||||
@@ -64,18 +56,18 @@ export class Window {
|
||||
|
||||
if (this.windowBounds) {
|
||||
Object.assign(bwOptions, this.windowBounds)
|
||||
const closestDisplay = screen.getDisplayNearestPoint( { x: this.windowBounds.x, y: this.windowBounds.y } )
|
||||
const closestDisplay = screen.getDisplayNearestPoint( {x: this.windowBounds.x, y: this.windowBounds.y} )
|
||||
|
||||
const [left1, top1, right1, bottom1] = [this.windowBounds.x, this.windowBounds.y, this.windowBounds.x + this.windowBounds.width, this.windowBounds.y + this.windowBounds.height]
|
||||
const [left2, top2, right2, bottom2] = [closestDisplay.bounds.x, closestDisplay.bounds.y, closestDisplay.bounds.x + closestDisplay.bounds.width, closestDisplay.bounds.y + closestDisplay.bounds.height]
|
||||
const [left1, top1, right1, bottom1] = [this.windowBounds.x, this.windowBounds.y, this.windowBounds.x + this.windowBounds.width, this.windowBounds.y + this.windowBounds.height];
|
||||
const [left2, top2, right2, bottom2] = [closestDisplay.bounds.x, closestDisplay.bounds.y, closestDisplay.bounds.x + closestDisplay.bounds.width, closestDisplay.bounds.y + closestDisplay.bounds.height];
|
||||
|
||||
if ((left2 > right1 || right2 < left1 || top2 > bottom1 || bottom2 < top1) && !maximized) {
|
||||
bwOptions.x = closestDisplay.bounds.width / 2 - bwOptions.width / 2
|
||||
bwOptions.y = closestDisplay.bounds.height / 2 - bwOptions.height / 2
|
||||
bwOptions.x = closestDisplay.bounds.width / 2 - bwOptions.width / 2;
|
||||
bwOptions.y = closestDisplay.bounds.height / 2 - bwOptions.height / 2;
|
||||
}
|
||||
}
|
||||
|
||||
if ((this.configStore.appearance || {}).frame === 'native') {
|
||||
if ((configData.appearance || {}).frame === 'native') {
|
||||
bwOptions.frame = true
|
||||
} else {
|
||||
if (process.platform === 'darwin') {
|
||||
@@ -83,12 +75,15 @@ export class Window {
|
||||
}
|
||||
}
|
||||
|
||||
this.window = new BrowserWindow(bwOptions)
|
||||
if (process.platform === 'linux') {
|
||||
bwOptions.backgroundColor = '#131d27'
|
||||
}
|
||||
|
||||
this.window = new BrowserWindow(bwOptions)
|
||||
this.window.once('ready-to-show', () => {
|
||||
if (process.platform === 'darwin') {
|
||||
this.window.setVibrancy('window')
|
||||
} else if (process.platform === 'win32' && (this.configStore.appearance || {}).vibrancy) {
|
||||
} else if (process.platform === 'win32' && (configData.appearance || {}).vibrancy) {
|
||||
this.setVibrancy(true)
|
||||
}
|
||||
|
||||
@@ -99,13 +94,6 @@ export class Window {
|
||||
this.window.show()
|
||||
}
|
||||
this.window.focus()
|
||||
this.window.moveTop()
|
||||
}
|
||||
})
|
||||
|
||||
this.window.on('blur', () => {
|
||||
if (this.configStore.appearance?.dockHideOnBlur) {
|
||||
this.hide()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -128,90 +116,41 @@ export class Window {
|
||||
})
|
||||
}
|
||||
|
||||
setVibrancy (enabled: boolean, type?: string): void {
|
||||
this.lastVibrancy = { enabled, type }
|
||||
setVibrancy (enabled: boolean, type?: string) {
|
||||
if (process.platform === 'win32') {
|
||||
if (parseFloat(os.release()) >= 10) {
|
||||
glasstron.update(this.window, {
|
||||
windows: { blurType: enabled ? type === 'fluent' ? 'acrylic' : 'blurbehind' : null },
|
||||
})
|
||||
let attribValue = AccentState.ACCENT_DISABLED
|
||||
if (enabled) {
|
||||
if (parseInt(os.release().split('.')[2]) >= 17063 && type === 'fluent') {
|
||||
attribValue = AccentState.ACCENT_ENABLE_ACRYLICBLURBEHIND
|
||||
} else {
|
||||
attribValue = AccentState.ACCENT_ENABLE_BLURBEHIND
|
||||
}
|
||||
}
|
||||
SetWindowCompositionAttribute(this.window.getNativeWindowHandle(), attribValue, 0x00000000)
|
||||
} else {
|
||||
DwmEnableBlurBehindWindow(this.window, enabled)
|
||||
}
|
||||
} else if (process.platform ==='linux') {
|
||||
glasstron.update(this.window, {
|
||||
linux: { requestBlur: enabled },
|
||||
})
|
||||
this.window.setBackgroundColor(enabled ? '#00000000' : '#131d27')
|
||||
} else {
|
||||
this.window.setVibrancy(enabled ? 'dark' : null as any) // electron issue 20269
|
||||
}
|
||||
}
|
||||
|
||||
show (): void {
|
||||
show () {
|
||||
this.window.show()
|
||||
this.window.moveTop()
|
||||
}
|
||||
|
||||
focus (): void {
|
||||
focus () {
|
||||
this.window.focus()
|
||||
}
|
||||
|
||||
send (event: string, ...args): void {
|
||||
send (event, ...args) {
|
||||
if (!this.window) {
|
||||
return
|
||||
}
|
||||
this.window.webContents.send(event, ...args)
|
||||
if (event === 'host:config-change') {
|
||||
this.configStore = args[0]
|
||||
}
|
||||
}
|
||||
|
||||
isDestroyed (): boolean {
|
||||
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 {
|
||||
this.send('host:second-instance', parseArgs(argv, cwd), cwd)
|
||||
isDestroyed () {
|
||||
return !this.window || this.window.isDestroyed();
|
||||
}
|
||||
|
||||
private setupWindowManagement () {
|
||||
@@ -263,10 +202,6 @@ export class Window {
|
||||
}
|
||||
})
|
||||
|
||||
this.window.on('focus', () => {
|
||||
this.send('host:window-focused')
|
||||
})
|
||||
|
||||
ipcMain.on('window-focus', event => {
|
||||
if (!this.window || event.sender !== this.window.webContents) {
|
||||
return
|
||||
@@ -354,35 +289,10 @@ export class Window {
|
||||
})
|
||||
|
||||
this.window.webContents.on('new-window', event => event.preventDefault())
|
||||
|
||||
ipcMain.on('window-set-disable-vibrancy-while-dragging', (_event, value) => {
|
||||
this.disableVibrancyWhileDragging = value
|
||||
})
|
||||
|
||||
this.window.on('will-move', () => {
|
||||
if (!this.lastVibrancy?.enabled || !this.disableVibrancyWhileDragging) {
|
||||
return
|
||||
}
|
||||
let timeout: number|null = null
|
||||
const oldVibrancy = this.lastVibrancy
|
||||
this.setVibrancy(false)
|
||||
const onMove = () => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
timeout = setTimeout(() => {
|
||||
this.window.off('move', onMove)
|
||||
this.setVibrancy(oldVibrancy.enabled, oldVibrancy.type)
|
||||
}, 500)
|
||||
}
|
||||
this.window.on('move', onMove)
|
||||
})
|
||||
}
|
||||
|
||||
private destroy () {
|
||||
this.window = null
|
||||
this.closed.next()
|
||||
this.visible.complete()
|
||||
this.closed.complete()
|
||||
}
|
||||
}
|
||||
|
@@ -13,42 +13,43 @@
|
||||
"watch": "webpack --progress --color --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "9.1.9",
|
||||
"@angular/common": "9.1.11",
|
||||
"@angular/compiler": "9.1.9",
|
||||
"@angular/core": "9.1.9",
|
||||
"@angular/forms": "9.1.11",
|
||||
"@angular/platform-browser": "9.1.9",
|
||||
"@angular/platform-browser-dynamic": "9.1.9",
|
||||
"@ng-bootstrap/ng-bootstrap": "^6.1.0",
|
||||
"@angular/animations": "9.0.0-rc.5",
|
||||
"@angular/common": "9.0.0-rc.5",
|
||||
"@angular/compiler": "9.0.0-rc.5",
|
||||
"@angular/core": "9.0.0-rc.5",
|
||||
"@angular/forms": "9.0.0-rc.5",
|
||||
"@angular/platform-browser": "9.0.0-rc.5",
|
||||
"@angular/platform-browser-dynamic": "9.0.0-rc.5",
|
||||
"@ng-bootstrap/ng-bootstrap": "^5.1.4",
|
||||
"devtron": "1.4.0",
|
||||
"electron-config": "2.0.0",
|
||||
"electron-debug": "^3.0.1",
|
||||
"electron-is-dev": "1.1.0",
|
||||
"electron-updater": "^4.2.0",
|
||||
"fontmanager-redux": "0.4.0",
|
||||
"glasstron": "sentialx/Glasstron#n-api",
|
||||
"js-yaml": "3.14.0",
|
||||
"keytar": "^6.0.1",
|
||||
"js-yaml": "3.13.1",
|
||||
"keytar": "^5.0.0",
|
||||
"mz": "^2.7.0",
|
||||
"ngx-toastr": "^12.0.1",
|
||||
"@terminus-term/node-pty": "0.10.0-beta9",
|
||||
"ngx-toastr": "^11.2.1",
|
||||
"node-pty": "^0.10.0-beta2",
|
||||
"npm": "6.9.0",
|
||||
"path": "0.12.7",
|
||||
"rxjs": "^6.5.5",
|
||||
"rxjs-compat": "^6.6.0",
|
||||
"yargs": "^15.4.1",
|
||||
"zone.js": "^0.10.3"
|
||||
"rxjs": "^6.5.3",
|
||||
"rxjs-compat": "^6.5.3",
|
||||
"yargs": "^15.0.2",
|
||||
"zone.js": "^0.10.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"macos-native-processlist": "^2.0.0",
|
||||
"serialport": "^9.0.0",
|
||||
"macos-native-processlist": "^1.0.2",
|
||||
"windows-blurbehind": "^1.0.1",
|
||||
"windows-native-registry": "^3.0.0",
|
||||
"windows-process-tree": "^0.2.4"
|
||||
"windows-native-registry": "^1.0.16",
|
||||
"windows-process-tree": "^0.2.4",
|
||||
"windows-swca": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/localize": "^9.0.0-rc.7",
|
||||
"@types/mz": "0.0.32",
|
||||
"@types/node": "12.7.12",
|
||||
"node-abi": "^2.18.0"
|
||||
"node-abi": "^2.13.0"
|
||||
}
|
||||
}
|
||||
|
@@ -1,13 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { NgModule } from '@angular/core'
|
||||
import { NgModule, Compiler, Inject, Injector, ɵcreateInjector as createInjector } from '@angular/core'
|
||||
import '@angular/localize/init'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { BrowserModule } from '@angular/platform-browser'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ToastrModule } from 'ngx-toastr'
|
||||
|
||||
export function getRootModule (plugins: any[]) {
|
||||
const imports = [
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
...plugins,
|
||||
CommonModule,
|
||||
NgbModule,
|
||||
ToastrModule.forRoot({
|
||||
positionClass: 'toast-bottom-center',
|
||||
@@ -15,7 +16,33 @@ export function getRootModule (plugins: any[]) {
|
||||
preventDuplicates: true,
|
||||
extendedTimeOut: 5000,
|
||||
}),
|
||||
]
|
||||
],
|
||||
})
|
||||
export class RootModule {
|
||||
constructor (
|
||||
private compiler: Compiler,
|
||||
private injector: Injector,
|
||||
@Inject('plugins') private plugins: any[],
|
||||
) { }
|
||||
|
||||
async ngDoBootstrap (app) {
|
||||
console.log('bootstrap', app)
|
||||
for (let plugin of this.plugins) {
|
||||
console.log(plugin)
|
||||
// try {
|
||||
const injector = createInjector(plugin, this.injector)
|
||||
console.log(injector)
|
||||
const module = await this.compiler.compileModuleAsync(plugin)
|
||||
console.log(module)
|
||||
// } catch (e) {
|
||||
// console.error('Failed loading', plugin, e)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function setupRootModule (plugins: any[]) {
|
||||
const bootstrap = [
|
||||
...plugins.filter(x => x.bootstrap).map(x => x.bootstrap),
|
||||
]
|
||||
@@ -23,11 +50,4 @@ export function getRootModule (plugins: any[]) {
|
||||
if (bootstrap.length === 0) {
|
||||
throw new Error('Did not find any bootstrap components. Are there any plugins installed?')
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports,
|
||||
bootstrap,
|
||||
}) class RootModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||
|
||||
return RootModule
|
||||
}
|
||||
|
@@ -1,9 +1,77 @@
|
||||
import '../lib/lru'
|
||||
import 'core-js/proposals/reflect-metadata'
|
||||
import 'source-sans-pro/source-sans-pro.css'
|
||||
import 'source-code-pro/source-code-pro.css'
|
||||
import '@fortawesome/fontawesome-free/css/solid.css'
|
||||
import '@fortawesome/fontawesome-free/css/brands.css'
|
||||
import '@fortawesome/fontawesome-free/css/regular.css'
|
||||
import '@fortawesome/fontawesome-free/css/fontawesome.css'
|
||||
import 'ngx-toastr/toastr.css'
|
||||
import './preload.scss'
|
||||
import * as path from 'path'
|
||||
|
||||
const nodeModule = require('module') // eslint-disable-line @typescript-eslint/no-var-requires
|
||||
const nodeRequire = (global as any).require
|
||||
|
||||
|
||||
const builtinModules = [
|
||||
'@angular/animations',
|
||||
'@angular/common',
|
||||
'@angular/compiler',
|
||||
'@angular/core',
|
||||
'@angular/forms',
|
||||
'@angular/localize',
|
||||
'@angular/platform-browser',
|
||||
'@angular/platform-browser-dynamic',
|
||||
'@ng-bootstrap/ng-bootstrap',
|
||||
'ngx-toastr',
|
||||
'rxjs',
|
||||
'rxjs/operators',
|
||||
'rxjs/internal/observable/fromEvent',
|
||||
'rxjs/internal/observable/merge',
|
||||
'rxjs-compat/Subject',
|
||||
'zone.js/dist/zone.js',
|
||||
'terminus-core',
|
||||
// 'terminus-settings',
|
||||
// 'terminus-terminal',
|
||||
]
|
||||
|
||||
const cachedBuiltinModules = {}
|
||||
|
||||
if (process.env.TERMINUS_DEV) {
|
||||
console.info(path.dirname(require('electron').remote.app.getAppPath()))
|
||||
nodeModule.globalPaths.unshift(path.dirname(require('electron').remote.app.getAppPath()))
|
||||
nodeModule.globalPaths.unshift(path.join(require('electron').remote.app.getAppPath(), 'node_modules'))
|
||||
}
|
||||
|
||||
const originalRequire = (global as any).require
|
||||
;(global as any).require = function (query: string) {
|
||||
if (cachedBuiltinModules[query]) {
|
||||
return cachedBuiltinModules[query]
|
||||
}
|
||||
return originalRequire.apply(this, arguments)
|
||||
}
|
||||
|
||||
const originalModuleRequire = nodeModule.prototype.require
|
||||
nodeModule.prototype.require = function (query: string) {
|
||||
if (cachedBuiltinModules[query]) {
|
||||
return cachedBuiltinModules[query]
|
||||
}
|
||||
return originalModuleRequire.call(this, query)
|
||||
}
|
||||
|
||||
global['require'].resolve = originalRequire.resolve
|
||||
nodeModule.prototype.require.resolve = originalModuleRequire.resolve
|
||||
|
||||
builtinModules.forEach(m => {
|
||||
const label = 'Caching ' + m
|
||||
console.time(label)
|
||||
try {
|
||||
console.log(m + '/__ivy_ngcc__/fesm5/' + m.split('/')[1] + '.js')
|
||||
cachedBuiltinModules[m] = nodeRequire(m + '/__ivy_ngcc__/fesm5/' + m.split('/')[1] + '.js')
|
||||
console.log('loaded ivy')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
cachedBuiltinModules[m] = nodeRequire(m)
|
||||
}
|
||||
console.timeEnd(label)
|
||||
})
|
||||
|
@@ -1,17 +1,12 @@
|
||||
import 'zone.js'
|
||||
import 'core-js/proposals/reflect-metadata'
|
||||
import 'rxjs'
|
||||
|
||||
import * as isDev from 'electron-is-dev'
|
||||
|
||||
import './global.scss'
|
||||
import './toastr.scss'
|
||||
|
||||
import { enableProdMode, NgModuleRef, ApplicationRef } from '@angular/core'
|
||||
import { enableDebugTools } from '@angular/platform-browser'
|
||||
import { enableProdMode, NgModuleRef } from '@angular/core'
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
||||
|
||||
import { getRootModule } from './app.module'
|
||||
import { setupRootModule, RootModule } from './app.module'
|
||||
import { findPlugins, loadPlugins, PluginInfo } from './plugins'
|
||||
|
||||
// Always land on the start view
|
||||
@@ -33,19 +28,14 @@ async function bootstrap (plugins: PluginInfo[], safeMode = false): Promise<NgMo
|
||||
if (safeMode) {
|
||||
plugins = plugins.filter(x => x.isBuiltin)
|
||||
}
|
||||
const pluginsModules = await loadPlugins(plugins, (current, total) => {
|
||||
const pluginModules = await loadPlugins(plugins, (current, total) => {
|
||||
(document.querySelector('.progress .bar') as HTMLElement).style.width = `${100 * current / total}%` // eslint-disable-line
|
||||
})
|
||||
const module = getRootModule(pluginsModules)
|
||||
window['rootModule'] = module
|
||||
return platformBrowserDynamic().bootstrapModule(module).then(moduleRef => {
|
||||
if (isDev) {
|
||||
const applicationRef = moduleRef.injector.get(ApplicationRef)
|
||||
const componentRef = applicationRef.components[0]
|
||||
enableDebugTools(componentRef)
|
||||
}
|
||||
return moduleRef
|
||||
})
|
||||
setupRootModule(pluginModules)
|
||||
window['rootModule'] = RootModule
|
||||
return platformBrowserDynamic([
|
||||
{ provide: 'plugins', useValue: pluginModules },
|
||||
]).bootstrapModule(RootModule)
|
||||
}
|
||||
|
||||
findPlugins().then(async plugins => {
|
||||
|
@@ -14,14 +14,12 @@ function normalizePath (path: string): string {
|
||||
|
||||
global['module'].paths.map((x: string) => nodeModule.globalPaths.push(normalizePath(x)))
|
||||
|
||||
if (process.env.TERMINUS_DEV) {
|
||||
nodeModule.globalPaths.unshift(path.dirname(require('electron').remote.app.getAppPath()))
|
||||
}
|
||||
|
||||
const builtinPluginsPath = process.env.TERMINUS_DEV ? path.dirname(require('electron').remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
|
||||
|
||||
const userPluginsPath = path.join(
|
||||
require('electron').remote.app.getPath('userData'),
|
||||
require('electron').remote.app.getPath('appData'),
|
||||
'terminus',
|
||||
'plugins',
|
||||
)
|
||||
|
||||
@@ -51,49 +49,6 @@ export interface PluginInfo {
|
||||
info?: any
|
||||
}
|
||||
|
||||
const builtinModules = [
|
||||
'@angular/animations',
|
||||
'@angular/common',
|
||||
'@angular/compiler',
|
||||
'@angular/core',
|
||||
'@angular/forms',
|
||||
'@angular/platform-browser',
|
||||
'@angular/platform-browser-dynamic',
|
||||
'@ng-bootstrap/ng-bootstrap',
|
||||
'ngx-toastr',
|
||||
'rxjs',
|
||||
'rxjs/operators',
|
||||
'rxjs-compat/Subject',
|
||||
'terminus-core',
|
||||
'terminus-settings',
|
||||
'terminus-terminal',
|
||||
'zone.js/dist/zone.js',
|
||||
]
|
||||
|
||||
const cachedBuiltinModules = {}
|
||||
builtinModules.forEach(m => {
|
||||
const label = 'Caching ' + m
|
||||
console.time(label)
|
||||
cachedBuiltinModules[m] = nodeRequire(m)
|
||||
console.timeEnd(label)
|
||||
})
|
||||
|
||||
const originalRequire = (global as any).require
|
||||
;(global as any).require = function (query: string) {
|
||||
if (cachedBuiltinModules[query]) {
|
||||
return cachedBuiltinModules[query]
|
||||
}
|
||||
return originalRequire.apply(this, arguments)
|
||||
}
|
||||
|
||||
const originalModuleRequire = nodeModule.prototype.require
|
||||
nodeModule.prototype.require = function (query: string) {
|
||||
if (cachedBuiltinModules[query]) {
|
||||
return cachedBuiltinModules[query]
|
||||
}
|
||||
return originalModuleRequire.call(this, query)
|
||||
}
|
||||
|
||||
export async function findPlugins (): Promise<PluginInfo[]> {
|
||||
const paths = nodeModule.globalPaths
|
||||
let foundPlugins: PluginInfo[] = []
|
||||
@@ -166,6 +121,7 @@ export async function loadPlugins (foundPlugins: PluginInfo[], progress: Progres
|
||||
progress(0, 1)
|
||||
let index = 0
|
||||
for (const foundPlugin of foundPlugins) {
|
||||
if (foundPlugin.name !== 'core') continue
|
||||
console.info(`Loading ${foundPlugin.name}: ${nodeRequire.resolve(foundPlugin.path)}`)
|
||||
progress(index, foundPlugins.length)
|
||||
try {
|
||||
|
@@ -1,6 +0,0 @@
|
||||
import { Component } from '@angular/core'
|
||||
|
||||
@Component({
|
||||
template: '<app-root></app-root>',
|
||||
})
|
||||
export class RootComponent { } // eslint-disable-line @typescript-eslint/no-extraneous-class
|
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src",
|
||||
"module": "commonjs",
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node",
|
||||
"target": "es2015",
|
||||
"declaration": false,
|
||||
"noImplicitAny": false,
|
||||
@@ -19,7 +20,10 @@
|
||||
"es2015.iterable",
|
||||
"es2017",
|
||||
"es7"
|
||||
]
|
||||
],
|
||||
"paths": {
|
||||
"*": ["../../app/node_modules/*"]
|
||||
}
|
||||
},
|
||||
"compileOnSave": false,
|
||||
"exclude": [
|
||||
@@ -28,5 +32,9 @@
|
||||
"*/node_modules",
|
||||
"terminus*",
|
||||
"platforms"
|
||||
]
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": true,
|
||||
"disableTypeScriptVersionCheck": true
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const { AngularCompilerPlugin } = require('@ngtools/webpack')
|
||||
|
||||
module.exports = {
|
||||
name: 'terminus',
|
||||
@@ -28,13 +29,8 @@ module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: {
|
||||
loader: 'awesome-typescript-loader',
|
||||
options: {
|
||||
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||
},
|
||||
},
|
||||
test: /(?:\.ngfactory\.js|\.ngfactory|\.ngstyle\.js|\.ts)$/,
|
||||
loader: '@ngtools/webpack',
|
||||
},
|
||||
{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
|
||||
{ test: /\.css$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
|
||||
@@ -82,5 +78,10 @@ module.exports = {
|
||||
new webpack.DefinePlugin({
|
||||
'process.type': '"renderer"'
|
||||
}),
|
||||
new AngularCompilerPlugin({
|
||||
tsConfigPath: path.resolve(__dirname, 'tsconfig.json'),
|
||||
entryModule: 'src/index#default',
|
||||
sourceMap: true,
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
@@ -37,7 +37,6 @@ module.exports = {
|
||||
'electron-config': 'commonjs electron-config',
|
||||
'electron-vibrancy': 'commonjs electron-vibrancy',
|
||||
fs: 'commonjs fs',
|
||||
glasstron: 'commonjs glasstron',
|
||||
mz: 'commonjs mz',
|
||||
path: 'commonjs path',
|
||||
yargs: 'commonjs yargs',
|
||||
|
746
app/yarn.lock
746
app/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,4 @@
|
||||
#!/bin/bash
|
||||
cat > '/usr/bin/${executable}' << END
|
||||
#!/bin/sh
|
||||
'/opt/${productFilename}/${executable}' --no-sandbox $@
|
||||
END
|
||||
|
||||
chmod +x '/usr/bin/${executable}'
|
||||
# Link to the binary
|
||||
ln -sf '/opt/${productFilename}/${executable}' '/usr/bin/${executable}'
|
||||
|
@@ -1,16 +0,0 @@
|
||||
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) {
|
||||
// notarize the app on Mac OS only.
|
||||
if (process.platform !== 'darwin' || !process.env.GITHUB_REF || !process.env.GITHUB_REF.startsWith('refs/tags/')) {
|
||||
if (process.platform !== 'darwin' || process.env.GITHUB_REF !== 'refs/heads/master' || process.env.GITHUB_REF && !process.env.GITHUB_REF.startsWith('refs/tags/')) {
|
||||
return
|
||||
}
|
||||
console.log('afterSign hook triggered', params)
|
||||
|
||||
let appId = 'org.terminus'
|
||||
|
||||
let appPath = path.join(params.appOutDir, params._pathOverride || `${params.packager.appInfo.productFilename}.app`)
|
||||
let appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`)
|
||||
if (!fs.existsSync(appPath)) {
|
||||
throw new Error(`Cannot find application at: ${appPath}`)
|
||||
}
|
||||
|
@@ -10,9 +10,5 @@
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.microphone</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@@ -1,71 +0,0 @@
|
||||
---
|
||||
appId: org.terminus
|
||||
productName: Terminus
|
||||
compression: normal
|
||||
afterSign: "./build/mac/afterSignHook.js"
|
||||
afterAllArtifactBuild: "./build/mac/afterBuildHook.js"
|
||||
files:
|
||||
- "**/*"
|
||||
- dist
|
||||
extraResources:
|
||||
- builtin-plugins
|
||||
- extras
|
||||
publish:
|
||||
- provider: github
|
||||
|
||||
win:
|
||||
icon: "./build/windows/icon.ico"
|
||||
artifactName: terminus-${version}-portable.${ext}
|
||||
rfc3161TimeStampServer: http://sha256timestamp.ws.symantec.com/sha256/timestamp
|
||||
nsis:
|
||||
oneClick: false
|
||||
artifactName: terminus-${version}-setup.${ext}
|
||||
installerIcon: "./build/windows/icon.ico"
|
||||
|
||||
mac:
|
||||
category: public.app-category.video
|
||||
icon: "./build/mac/icon.icns"
|
||||
artifactName: terminus-${version}-macos.${ext}
|
||||
hardenedRuntime: true
|
||||
entitlements: "./build/mac/entitlements.plist"
|
||||
entitlementsInherit: "./build/mac/entitlements.plist"
|
||||
extendInfo:
|
||||
NSRequiresAquaSystemAppearance: false
|
||||
NSCameraUsageDescription: "A subprocess requests access to the device's camera."
|
||||
NSMicrophoneUsageDescription: "A subprocess requests access to the device's microphone."
|
||||
NSLocationUsageDescription: "A subprocess requests access to the user's location information."
|
||||
NSDesktopFolderUsageDescription: "A subprocess requests access to the user's Desktop folder."
|
||||
NSDocumentsFolderUsageDescription: "A subprocess requests access to the user's Documents folder."
|
||||
NSDownloadsFolderUsageDescription: "A subprocess requests access to the user's Downloads folder."
|
||||
NSNetworkVolumesUsageDescription: 'A subprocess requests access to files on a network volume.'
|
||||
NSRemovableVolumesUsageDescription: 'A subprocess requests access to files on a removable volume.'
|
||||
|
||||
pkg:
|
||||
artifactName: terminus-${version}-macos.pkg
|
||||
|
||||
linux:
|
||||
category: Utilities
|
||||
icon: "./build/icons"
|
||||
artifactName: terminus-${version}-linux.${ext}
|
||||
executableArgs:
|
||||
- "--no-sandbox"
|
||||
snap:
|
||||
plugs:
|
||||
- default
|
||||
- system-files
|
||||
- system-observe
|
||||
deb:
|
||||
depends:
|
||||
- gconf2
|
||||
- gconf-service
|
||||
- gnome-keyring
|
||||
- libnotify4
|
||||
- libsecret-1-0
|
||||
- libappindicator1
|
||||
- libxtst6
|
||||
- libnss3
|
||||
afterInstall: build/linux/after-install.tpl
|
||||
rpm:
|
||||
depends:
|
||||
- screen
|
||||
- gnome-keyring
|
BIN
extras/UAC.exe
BIN
extras/UAC.exe
Binary file not shown.
Binary file not shown.
Binary file not shown.
148
package.json
148
package.json
@@ -1,35 +1,39 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||
"@sentry/cli": "^1.52.3",
|
||||
"@sentry/electron": "^1.5.1",
|
||||
"@angular/compiler": "9.0.0-rc.5",
|
||||
"@angular/compiler-cli": "9.0.0-rc.5",
|
||||
"@angular/core": "9.0.0-rc.5",
|
||||
"@fortawesome/fontawesome-free": "^5.11.2",
|
||||
"@ngtools/webpack": "9.0.0-rc.5",
|
||||
"@sentry/cli": "^1.49.0",
|
||||
"@sentry/electron": "^1.0.0",
|
||||
"@types/electron-config": "^3.2.2",
|
||||
"@types/electron-debug": "^2.1.0",
|
||||
"@types/js-yaml": "^3.12.4",
|
||||
"@types/js-yaml": "^3.12.1",
|
||||
"@types/node": "12.7.12",
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^3.8.0",
|
||||
"@types/webpack-env": "1.14.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.9.0",
|
||||
"@typescript-eslint/parser": "^2.10.0",
|
||||
"apply-loader": "2.0.0",
|
||||
"awesome-typescript-loader": "^5.0.0",
|
||||
"core-js": "^3.6.5",
|
||||
"cross-env": "7.0.2",
|
||||
"css-loader": "3.4.2",
|
||||
"electron": "^8.2.5",
|
||||
"electron-builder": "22.6.1",
|
||||
"core-js": "^3.4.2",
|
||||
"cross-env": "6.0.3",
|
||||
"css-loader": "3.4.0",
|
||||
"electron": "^7.1.3",
|
||||
"electron-builder": "22.1.0",
|
||||
"electron-download": "^4.1.1",
|
||||
"electron-installer-snap": "^5.0.0",
|
||||
"electron-notarize": "^1.0.0",
|
||||
"electron-rebuild": "^1.10.1",
|
||||
"eslint": "^7.6.0",
|
||||
"eslint-plugin-import": "^2.21.1",
|
||||
"electron-installer-snap": "^4.1.0",
|
||||
"electron-notarize": "^0.1.1",
|
||||
"electron-rebuild": "^1.8.5",
|
||||
"eslint": "^6.7.1",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"file-loader": "^5.0.2",
|
||||
"graceful-fs": "^4.2.4",
|
||||
"graceful-fs": "^4.2.2",
|
||||
"html-loader": "0.5.5",
|
||||
"json-loader": "0.5.7",
|
||||
"node-abi": "^2.18.0",
|
||||
"node-gyp": "^7.0.0",
|
||||
"node-sass": "^4.14.1",
|
||||
"node-abi": "^2.12.0",
|
||||
"node-gyp": "^6.0.1",
|
||||
"node-sass": "^4.13.0",
|
||||
"npmlog": "4.1.2",
|
||||
"npx": "^10.2.0",
|
||||
"pug": "^2.0.4",
|
||||
@@ -37,38 +41,104 @@
|
||||
"pug-lint": "^2.6.0",
|
||||
"pug-loader": "^2.4.0",
|
||||
"pug-static-loader": "2.0.0",
|
||||
"raw-loader": "4.0.1",
|
||||
"raw-loader": "4.0.0",
|
||||
"sass-loader": "^8.0.0",
|
||||
"shelljs": "0.8.4",
|
||||
"shelljs": "0.8.3",
|
||||
"source-code-pro": "^2.30.2",
|
||||
"source-sans-pro": "3.6.0",
|
||||
"style-loader": "^1.1.4",
|
||||
"style-loader": "^1.0.1",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"to-string-loader": "1.1.6",
|
||||
"tslib": "^2.0.0",
|
||||
"typedoc": "^0.18.0",
|
||||
"typescript": "^3.9.3",
|
||||
"tslib": "^1.10.0",
|
||||
"typedoc": "^0.15.3",
|
||||
"typescript": "^3.7.3",
|
||||
"url-loader": "^3.0.0",
|
||||
"val-loader": "2.1.1",
|
||||
"webpack": "^5.0.0-beta.18",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"yaml-loader": "0.6.0"
|
||||
"val-loader": "2.1.0",
|
||||
"webpack": "4",
|
||||
"webpack-cli": "^3.3.10",
|
||||
"yaml-loader": "0.5.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"*/node-abi": "^2.14.0"
|
||||
"*/node-abi": "^2.8.0"
|
||||
},
|
||||
"build": {
|
||||
"appId": "org.terminus",
|
||||
"productName": "Terminus",
|
||||
"compression": "normal",
|
||||
"afterSign": "./build/mac/afterSignHook.js",
|
||||
"files": [
|
||||
"**/*",
|
||||
"dist"
|
||||
],
|
||||
"extraResources": [
|
||||
"builtin-plugins",
|
||||
"extras"
|
||||
],
|
||||
"win": {
|
||||
"icon": "./build/windows/icon.ico",
|
||||
"artifactName": "terminus-${version}-setup.exe",
|
||||
"rfc3161TimeStampServer": "http://sha256timestamp.ws.symantec.com/sha256/timestamp"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"artifactName": "terminus-${version}-setup.${ext}",
|
||||
"installerIcon": "./build/windows/icon.ico"
|
||||
},
|
||||
"publish": [
|
||||
{
|
||||
"provider": "github"
|
||||
}
|
||||
],
|
||||
"portable": {
|
||||
"artifactName": "terminus-${version}-portable.exe"
|
||||
},
|
||||
"mac": {
|
||||
"category": "public.app-category.video",
|
||||
"icon": "./build/mac/icon.icns",
|
||||
"artifactName": "terminus-${version}-macos.${ext}",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "./build/mac/entitlements.plist",
|
||||
"entitlementsInherit": "./build/mac/entitlements.plist",
|
||||
"extendInfo": {
|
||||
"NSRequiresAquaSystemAppearance": false
|
||||
}
|
||||
},
|
||||
"pkg": {
|
||||
"artifactName": "terminus-${version}-macos.pkg"
|
||||
},
|
||||
"linux": {
|
||||
"category": "Utilities",
|
||||
"icon": "./build/icons",
|
||||
"artifactName": "terminus-${version}-linux.${ext}"
|
||||
},
|
||||
"deb": {
|
||||
"depends": [
|
||||
"gconf2",
|
||||
"gconf-service",
|
||||
"libnotify4",
|
||||
"libsecret-1-0",
|
||||
"libappindicator1",
|
||||
"libxtst6",
|
||||
"libnss3"
|
||||
],
|
||||
"afterInstall": "build/linux/after-install.tpl"
|
||||
},
|
||||
"rpm": {
|
||||
"depends": [
|
||||
"screen",
|
||||
"gnome-python2-gnomekeyring"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run build:typings && webpack --color --config app/webpack.main.config.js && webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js && webpack --color --config terminus-ssh/webpack.config.js && webpack --color --config terminus-serial/webpack.config.js",
|
||||
"build:typings": "node scripts/build-typings.js",
|
||||
"build": "npm run build:typings && webpack --color --config app/webpack.main.config.js && webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js && webpack --color --config terminus-ssh/webpack.config.js",
|
||||
"build:typings": "tsc --project terminus-core/tsconfig.typings.json && tsc --project terminus-settings/tsconfig.typings.json && tsc --project terminus-terminal/tsconfig.typings.json && tsc --project terminus-plugin-manager/tsconfig.typings.json && tsc --project terminus-ssh/tsconfig.typings.json",
|
||||
"watch": "cross-env TERMINUS_DEV=1 webpack --progress --color --watch",
|
||||
"start": "cross-env TERMINUS_DEV=1 electron app --debug",
|
||||
"start:prod": "electron app --debug",
|
||||
"prod": "cross-env TERMINUS_DEV=1 electron app",
|
||||
"docs": "typedoc --out docs/api terminus-core/src && typedoc --out docs/api/terminal --tsconfig terminus-terminal/tsconfig.typings.json terminus-terminal/src && typedoc --out docs/api/settings --tsconfig terminus-settings/tsconfig.typings.json terminus-settings/src",
|
||||
"lint": "eslint --ext ts */src */lib",
|
||||
"lint": "eslint --ext ts */src",
|
||||
"postinstall": "node ./scripts/install-deps.js"
|
||||
},
|
||||
"repository": "eugeny/terminus",
|
||||
"author": "Eugene Pankov",
|
||||
"license": "MIT"
|
||||
"repository": "eugeny/terminus"
|
||||
}
|
||||
|
@@ -1,10 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
const builder = require('electron-builder').build
|
||||
const vars = require('./vars')
|
||||
const fs = require('fs')
|
||||
const signHook = require('../build/mac/afterSignHook')
|
||||
|
||||
const isTag = (process.env.GITHUB_REF || '').startsWith('refs/tags/')
|
||||
const isCI = !!process.env.GITHUB_REF
|
||||
|
||||
builder({
|
||||
dir: true,
|
||||
@@ -15,7 +14,4 @@ builder({
|
||||
},
|
||||
},
|
||||
publish: isTag ? 'always' : 'onTag',
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
process.exit(1)
|
||||
})
|
||||
}).catch(() => process.exit(1))
|
||||
|
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
const sh = require('shelljs')
|
||||
const vars = require('./vars')
|
||||
const log = require('npmlog')
|
||||
|
||||
vars.builtinPlugins.forEach(plugin => {
|
||||
log.info('typings', plugin)
|
||||
sh.exec(`npx tsc --project ${plugin}/tsconfig.typings.json`)
|
||||
})
|
@@ -7,7 +7,7 @@ const isCI = !!process.env.GITHUB_REF
|
||||
|
||||
builder({
|
||||
dir: true,
|
||||
win: ['nsis', 'zip'],
|
||||
win: ['nsis', 'portable'],
|
||||
config: {
|
||||
extraMetadata: {
|
||||
version: vars.version,
|
||||
|
@@ -21,10 +21,5 @@ exports.builtinPlugins = [
|
||||
'terminus-community-color-schemes',
|
||||
'terminus-plugin-manager',
|
||||
'terminus-ssh',
|
||||
'terminus-serial',
|
||||
]
|
||||
exports.bundledModules = [
|
||||
'@angular',
|
||||
'@ng-bootstrap',
|
||||
]
|
||||
exports.electronVersion = electronInfo.version
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "terminus-community-color-schemes",
|
||||
"version": "1.0.104-nightly.0",
|
||||
"version": "1.0.98-nightly.0",
|
||||
"description": "Community color schemes for Terminus",
|
||||
"keywords": [
|
||||
"terminus-builtin-plugin"
|
||||
|
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"exclude": ["node_modules", "dist", "typings"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"emitDeclarationOnly": true,
|
||||
"declaration": true,
|
||||
"declarationDir": "./typings",
|
||||
"paths": {
|
||||
"terminus-*": ["../../terminus-*"],
|
||||
"*": ["../../app/node_modules/*"]
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@ module.exports = {
|
||||
target: 'node',
|
||||
entry: 'src/index.ts',
|
||||
context: __dirname,
|
||||
devtool: 'cheap-module-source-map',
|
||||
devtool: 'eval-cheap-module-source-map',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'index.js',
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "terminus-core",
|
||||
"version": "1.0.104-nightly.0",
|
||||
"version": "1.0.98-nightly.0",
|
||||
"description": "Terminus core",
|
||||
"keywords": [
|
||||
"terminus-builtin-plugin"
|
||||
@@ -30,7 +30,7 @@
|
||||
"ng2-dnd": "^5.0.2",
|
||||
"ngx-perfect-scrollbar": "^8.0.0",
|
||||
"shell-escape": "^0.2.0",
|
||||
"uuid": "^8.0.0",
|
||||
"uuid": "^3.3.2",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@@ -1,13 +1,11 @@
|
||||
export { BaseTabComponent, BaseTabProcess } from '../components/baseTab.component'
|
||||
export { TabHeaderComponent } from '../components/tabHeader.component'
|
||||
export { SplitTabComponent, SplitContainer } from '../components/splitTab.component'
|
||||
export { TabRecoveryProvider, RecoveredTab, RecoveryToken } from './tabRecovery'
|
||||
export { TabRecoveryProvider, RecoveredTab } from './tabRecovery'
|
||||
export { ToolbarButtonProvider, ToolbarButton } from './toolbarButtonProvider'
|
||||
export { ConfigProvider } from './configProvider'
|
||||
export { HotkeyProvider, HotkeyDescription } from './hotkeyProvider'
|
||||
export { Theme } from './theme'
|
||||
export { TabContextMenuItemProvider } from './tabContextMenuProvider'
|
||||
export { SelectorOption } from './selector'
|
||||
|
||||
export { AppService } from '../services/app.service'
|
||||
export { ConfigService } from '../services/config.service'
|
||||
@@ -20,4 +18,3 @@ export { HostAppService, Platform } from '../services/hostApp.service'
|
||||
export { ShellIntegrationService } from '../services/shellIntegration.service'
|
||||
export { ThemesService } from '../services/themes.service'
|
||||
export { TabsService } from '../services/tabs.service'
|
||||
export * from '../utils'
|
||||
|
@@ -1,8 +0,0 @@
|
||||
export interface SelectorOption<T> {
|
||||
name: string
|
||||
description?: string
|
||||
result?: T
|
||||
icon?: string
|
||||
freeInputPattern?: string
|
||||
callback?: (string?) => void
|
||||
}
|
@@ -12,12 +12,6 @@ export interface RecoveredTab {
|
||||
options?: any
|
||||
}
|
||||
|
||||
export interface RecoveryToken {
|
||||
[_: string]: any
|
||||
type: string
|
||||
tabColor?: string|null
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend to enable recovery for your custom tab.
|
||||
* This works in conjunction with [[getRecoveryToken()]]
|
||||
@@ -40,5 +34,5 @@ export abstract class TabRecoveryProvider {
|
||||
* @returns [[RecoveredTab]] descriptor containing tab type and component inputs
|
||||
* or `null` if this token is from a different tab type or is not supported
|
||||
*/
|
||||
abstract async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null>
|
||||
abstract async recover (recoveryToken: any): Promise<RecoveredTab|null>
|
||||
}
|
||||
|
@@ -4,18 +4,14 @@ title-bar(
|
||||
)
|
||||
|
||||
.content(
|
||||
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top" || config.store.appearance.tabsLocation == "left"',
|
||||
[class.tabs-on-side]='hasVerticalTabs()',
|
||||
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top"'
|
||||
)
|
||||
.tab-bar
|
||||
.inset.background(*ngIf='hostApp.platform == Platform.macOS \
|
||||
&& config.store.appearance.frame == "thin" \
|
||||
&& (config.store.appearance.tabsLocation == "top" || config.store.appearance.tabsLocation == "left")')
|
||||
.inset.background(*ngIf='hostApp.platform == Platform.macOS && config.store.appearance.frame == "thin" && config.store.appearance.tabsLocation == "top"')
|
||||
.tabs(
|
||||
*ngIf='config.store.appearance.tabsLocation != "bottom"'
|
||||
dnd-sortable-container,
|
||||
[sortableData]='app.tabs',
|
||||
)
|
||||
//- [sortableData]='app.tabs',
|
||||
tab-header(
|
||||
*ngFor='let tab of app.tabs; let idx = index',
|
||||
dnd-sortable,
|
||||
@@ -28,7 +24,6 @@ title-bar(
|
||||
[active]='tab == app.activeTab',
|
||||
[hasActivity]='tab.activity$|async',
|
||||
@animateTab,
|
||||
[@.disabled]='hasVerticalTabs()',
|
||||
(click)='app.selectTab(tab)',
|
||||
[class.fully-draggable]='hostApp.platform != Platform.macOS',
|
||||
[class.drag-region]='hostApp.platform == Platform.macOS && !tabsDragging',
|
||||
@@ -43,7 +38,7 @@ title-bar(
|
||||
button.btn.btn-secondary.btn-tab-bar(
|
||||
[title]='button.title',
|
||||
(click)='button.click && button.click()',
|
||||
[fastHtmlBind]='button.icon',
|
||||
[innerHTML]='sanitizeIcon(button.icon)',
|
||||
ngbDropdownToggle,
|
||||
)
|
||||
div(*ngIf='button.submenu', ngbDropdownMenu)
|
||||
@@ -54,7 +49,7 @@ title-bar(
|
||||
)
|
||||
.icon-wrapper(
|
||||
*ngIf='hasIcons(button.submenuItems)',
|
||||
[fastHtmlBind]='item.icon'
|
||||
[innerHTML]='sanitizeIcon(item.icon)'
|
||||
)
|
||||
div([class.ml-3]='hasIcons(button.submenuItems)') {{item.title}}
|
||||
|
||||
@@ -69,7 +64,7 @@ title-bar(
|
||||
button.btn.btn-secondary.btn-tab-bar(
|
||||
[title]='button.title',
|
||||
(click)='button.click && button.click()',
|
||||
[fastHtmlBind]='button.icon',
|
||||
[innerHTML]='sanitizeIcon(button.icon)',
|
||||
ngbDropdownToggle,
|
||||
)
|
||||
div(*ngIf='button.submenu', ngbDropdownMenu)
|
||||
@@ -80,7 +75,7 @@ title-bar(
|
||||
)
|
||||
.icon-wrapper(
|
||||
*ngIf='hasIcons(button.submenuItems)',
|
||||
[fastHtmlBind]='item.icon'
|
||||
[innerHTML]='sanitizeIcon(item.icon)'
|
||||
)
|
||||
div([class.ml-3]='hasIcons(button.submenuItems)') {{item.title}}
|
||||
|
||||
@@ -88,12 +83,11 @@ title-bar(
|
||||
*ngIf='updatesAvailable',
|
||||
title='Update available - Click to install',
|
||||
(click)='updateApp()',
|
||||
[fastHtmlBind]='updateIcon'
|
||||
[innerHTML]='sanitizeIcon(updateIcon)'
|
||||
)
|
||||
|
||||
window-controls.background(
|
||||
*ngIf='config.store.appearance.frame == "thin" \
|
||||
&& (hostApp.platform == Platform.Windows || hostApp.platform == Platform.Linux)',
|
||||
*ngIf='config.store.appearance.frame == "thin" && (hostApp.platform == Platform.Windows || hostApp.platform == Platform.Linux)',
|
||||
)
|
||||
|
||||
start-page(*ngIf='ready && app.tabs.length == 0')
|
||||
|
@@ -15,18 +15,10 @@
|
||||
|
||||
$tabs-height: 38px;
|
||||
$tab-border-radius: 4px;
|
||||
$side-tab-width: 200px;
|
||||
|
||||
.wrap {
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
flex: auto;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
@@ -34,50 +26,15 @@ $side-tab-width: 200px;
|
||||
&.tabs-on-top {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.tabs-on-side {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
&.tabs-on-top {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.content.tabs-on-side > .tab-bar {
|
||||
height: 100%;
|
||||
width: $side-tab-width;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
flex-direction: column;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
|
||||
.tabs {
|
||||
width: $side-tab-width;
|
||||
flex: none;
|
||||
flex-direction: column;
|
||||
|
||||
tab-header {
|
||||
flex: 0 0 $tabs-height;
|
||||
}
|
||||
}
|
||||
|
||||
.drag-space {
|
||||
flex: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.tab-bar {
|
||||
flex: none;
|
||||
height: $tabs-height;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.btn-tab-bar {
|
||||
line-height: $tabs-height + 2px;
|
||||
height: $tabs-height;
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
@@ -107,7 +64,6 @@ $side-tab-width: 200px;
|
||||
&>.drag-space {
|
||||
min-width: 1px;
|
||||
flex: 1 0 1%;
|
||||
margin-top: 2px; // for window resizing
|
||||
-webkit-app-region: drag;
|
||||
|
||||
&.persistent {
|
||||
@@ -117,10 +73,7 @@ $side-tab-width: 200px;
|
||||
|
||||
& > .inset {
|
||||
width: 85px;
|
||||
height: $tabs-height;
|
||||
flex: none;
|
||||
opacity: 0;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
window-controls {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Component, Inject, Input, HostListener, HostBinding } from '@angular/core'
|
||||
import { trigger, style, animate, transition, state } from '@angular/animations'
|
||||
import { DomSanitizer } from '@angular/platform-browser'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
|
||||
import { ElectronService } from '../services/electron.service'
|
||||
@@ -20,8 +20,8 @@ import { AppService, ToolbarButton, ToolbarButtonProvider } from '../api'
|
||||
/** @hidden */
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: require('./appRoot.component.pug'),
|
||||
styles: [require('./appRoot.component.scss')],
|
||||
templateUrl: './appRoot.component.pug',
|
||||
styleUrls: ['./appRoot.component.scss'],
|
||||
animations: [
|
||||
trigger('animateTab', [
|
||||
state('in', style({
|
||||
@@ -75,6 +75,7 @@ export class AppRootComponent {
|
||||
public hostApp: HostAppService,
|
||||
public config: ConfigService,
|
||||
public app: AppService,
|
||||
private domSanitizer: DomSanitizer,
|
||||
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
||||
log: LogService,
|
||||
ngbModal: NgbModal,
|
||||
@@ -108,15 +109,6 @@ export class AppRootComponent {
|
||||
if (hotkey === 'previous-tab') {
|
||||
this.app.previousTab()
|
||||
}
|
||||
if (hotkey === 'move-tab-left') {
|
||||
this.app.moveSelectedTabLeft()
|
||||
}
|
||||
if (hotkey === 'move-tab-right') {
|
||||
this.app.moveSelectedTabRight()
|
||||
}
|
||||
if (hotkey === 'reopen-tab') {
|
||||
this.app.reopenLastTab()
|
||||
}
|
||||
}
|
||||
if (hotkey === 'toggle-fullscreen') {
|
||||
this.hostApp.toggleFullscreen()
|
||||
@@ -128,8 +120,17 @@ export class AppRootComponent {
|
||||
this.docking.dock()
|
||||
})
|
||||
|
||||
this.hostApp.secondInstance$.subscribe(() => {
|
||||
this.presentWindow()
|
||||
})
|
||||
this.hotkeys.globalHotkey.subscribe(() => {
|
||||
this.onGlobalHotkey()
|
||||
})
|
||||
|
||||
this.hostApp.windowCloseRequest$.subscribe(async () => {
|
||||
this.app.closeWindow()
|
||||
if (await this.app.closeAllTabs()) {
|
||||
this.hostApp.closeWindow()
|
||||
}
|
||||
})
|
||||
|
||||
if (window['safeModeReason']) {
|
||||
@@ -168,6 +169,40 @@ 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().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 () {
|
||||
this.ready = true
|
||||
|
||||
@@ -184,10 +219,6 @@ export class AppRootComponent {
|
||||
return false
|
||||
}
|
||||
|
||||
hasVerticalTabs () {
|
||||
return this.config.store.appearance.tabsLocation === 'left' || this.config.store.appearance.tabsLocation === 'right'
|
||||
}
|
||||
|
||||
async updateApp () {
|
||||
if ((await this.electron.showMessageBox(
|
||||
this.hostApp.getWindow(),
|
||||
@@ -223,6 +254,10 @@ export class AppRootComponent {
|
||||
return submenuItems.some(x => !!x.icon)
|
||||
}
|
||||
|
||||
sanitizeIcon (icon: string): any {
|
||||
return this.domSanitizer.bypassSecurityTrustHtml(icon || '')
|
||||
}
|
||||
|
||||
private getToolbarButtons (aboveZero: boolean): ToolbarButton[] {
|
||||
let buttons: ToolbarButton[] = []
|
||||
this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => {
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { Observable, Subject } from 'rxjs'
|
||||
import { ViewRef } from '@angular/core'
|
||||
import { RecoveryToken } from '../api/tabRecovery'
|
||||
|
||||
/**
|
||||
* Represents an active "process" inside a tab,
|
||||
@@ -14,11 +13,6 @@ export interface BaseTabProcess {
|
||||
* Abstract base class for custom tab components
|
||||
*/
|
||||
export abstract class BaseTabComponent {
|
||||
/**
|
||||
* Parent tab (usually a SplitTabComponent)
|
||||
*/
|
||||
parent: BaseTabComponent|null = null
|
||||
|
||||
/**
|
||||
* Current tab title
|
||||
*/
|
||||
@@ -44,7 +38,7 @@ export abstract class BaseTabComponent {
|
||||
*/
|
||||
color: string|null = null
|
||||
|
||||
hasFocus = false
|
||||
protected hasFocus = false
|
||||
|
||||
/**
|
||||
* Ping this if your recovery state has been changed and you want
|
||||
@@ -68,7 +62,7 @@ export abstract class BaseTabComponent {
|
||||
get destroyed$ (): Observable<void> { return this.destroyed }
|
||||
get recoveryStateChangedHint$ (): Observable<void> { return this.recoveryStateChangedHint }
|
||||
|
||||
protected constructor () {
|
||||
constructor () {
|
||||
this.focused$.subscribe(() => {
|
||||
this.hasFocus = true
|
||||
})
|
||||
@@ -77,7 +71,7 @@ export abstract class BaseTabComponent {
|
||||
})
|
||||
}
|
||||
|
||||
setTitle (title: string): void {
|
||||
setTitle (title: string) {
|
||||
this.title = title
|
||||
if (!this.customTitle) {
|
||||
this.titleChange.next(title)
|
||||
@@ -89,7 +83,7 @@ export abstract class BaseTabComponent {
|
||||
*
|
||||
* @param {type} progress: value between 0 and 1, or `null` to remove
|
||||
*/
|
||||
setProgress (progress: number|null): void {
|
||||
setProgress (progress: number|null) {
|
||||
this.progress.next(progress)
|
||||
if (progress) {
|
||||
if (this.progressClearTimeout) {
|
||||
@@ -124,7 +118,7 @@ export abstract class BaseTabComponent {
|
||||
* @return JSON serializable tab state representation
|
||||
* for your [[TabRecoveryProvider]] to parse
|
||||
*/
|
||||
async getRecoveryToken (): Promise<RecoveryToken|null> {
|
||||
async getRecoveryToken (): Promise<any> {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -142,11 +136,11 @@ export abstract class BaseTabComponent {
|
||||
return true
|
||||
}
|
||||
|
||||
emitFocused (): void {
|
||||
emitFocused () {
|
||||
this.focused.next()
|
||||
}
|
||||
|
||||
emitBlurred (): void {
|
||||
emitBlurred () {
|
||||
this.blurred.next()
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { NgZone, Component, Input, HostBinding, HostListener } from '@angular/core'
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
|
||||
|
@@ -1,11 +1,10 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Component, Input, ElementRef, ViewChild } from '@angular/core'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
selector: 'rename-tab-modal',
|
||||
template: require('./renameTabModal.component.pug'),
|
||||
templateUrl: './renameTabModal.component.pug',
|
||||
})
|
||||
export class RenameTabModalComponent {
|
||||
@Input() value: string
|
||||
|
@@ -3,7 +3,7 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
template: require('./safeModeModal.component.pug'),
|
||||
templateUrl: './safeModeModal.component.pug',
|
||||
})
|
||||
export class SafeModeModalComponent {
|
||||
@Input() error: Error
|
||||
@@ -14,7 +14,7 @@ export class SafeModeModalComponent {
|
||||
this.error = window['safeModeReason']
|
||||
}
|
||||
|
||||
close (): void {
|
||||
close () {
|
||||
this.modalInstance.dismiss()
|
||||
}
|
||||
}
|
||||
|
@@ -1,26 +0,0 @@
|
||||
.modal-body
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='filter',
|
||||
autofocus,
|
||||
[placeholder]='name',
|
||||
(ngModelChange)='onFilterChange()'
|
||||
)
|
||||
|
||||
.list-group.mt-3(*ngIf='filteredOptions.length')
|
||||
a.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||
#item,
|
||||
(click)='selectOption(option)',
|
||||
[class.active]='selectedIndex == i',
|
||||
*ngFor='let option of filteredOptions; let i = index'
|
||||
)
|
||||
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}}
|
@@ -1,13 +0,0 @@
|
||||
.list-group {
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 1.25rem;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 10px;
|
||||
}
|
@@ -1,78 +0,0 @@
|
||||
import { Component, Input, HostListener, ViewChildren, QueryList, ElementRef } from '@angular/core'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { SelectorOption } from '../api/selector'
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
template: require('./selectorModal.component.pug'),
|
||||
styles: [require('./selectorModal.component.scss')],
|
||||
})
|
||||
export class SelectorModalComponent<T> {
|
||||
@Input() options: SelectorOption<T>[]
|
||||
@Input() filteredOptions: SelectorOption<T>[]
|
||||
@Input() filter = ''
|
||||
@Input() name: string
|
||||
@Input() selectedIndex = 0
|
||||
@ViewChildren('item') itemChildren: QueryList<ElementRef>
|
||||
|
||||
constructor (
|
||||
public modalInstance: NgbActiveModal,
|
||||
) { }
|
||||
|
||||
ngOnInit (): void {
|
||||
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 {
|
||||
const f = this.filter.trim().toLowerCase()
|
||||
if (!f) {
|
||||
this.filteredOptions = this.options.filter(x => !x.freeInputPattern)
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||
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)
|
||||
}
|
||||
|
||||
getOptionText (option: SelectorOption<T>): string {
|
||||
if (option.freeInputPattern) {
|
||||
return option.freeInputPattern.replace('%s', this.filter)
|
||||
}
|
||||
return option.name
|
||||
}
|
||||
|
||||
selectOption (option: SelectorOption<T>): void {
|
||||
option.callback?.(this.filter)
|
||||
this.modalInstance.close(option.result)
|
||||
}
|
||||
|
||||
close (): void {
|
||||
this.modalInstance.dismiss()
|
||||
}
|
||||
|
||||
iconIsSVG (icon: string): boolean {
|
||||
return icon?.startsWith('<')
|
||||
}
|
||||
}
|
@@ -3,24 +3,3 @@
|
||||
position: relative;
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
::ng-deep split-tab > .child {
|
||||
position: absolute;
|
||||
transition: 0.125s all;
|
||||
opacity: .75;
|
||||
|
||||
&.focused {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.minimized {
|
||||
opacity: .1;
|
||||
}
|
||||
|
||||
&.maximized {
|
||||
z-index: 2;
|
||||
box-shadow: rgba(0, 0, 0, 0.25) 0px 0px 30px;
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Observable, Subject, Subscription } from 'rxjs'
|
||||
import { Component, Injectable, ViewChild, ViewContainerRef, EmbeddedViewRef, AfterViewInit, OnDestroy } from '@angular/core'
|
||||
import { Component, Injectable, ViewChild, ViewContainerRef, EmbeddedViewRef, OnInit, OnDestroy } from '@angular/core'
|
||||
import { BaseTabComponent, BaseTabProcess } from './baseTab.component'
|
||||
import { TabRecoveryProvider, RecoveredTab, RecoveryToken } from '../api/tabRecovery'
|
||||
import { TabRecoveryProvider, RecoveredTab } from '../api/tabRecovery'
|
||||
import { TabsService } from '../services/tabs.service'
|
||||
import { HotkeysService } from '../services/hotkeys.service'
|
||||
import { TabRecoveryService } from '../services/tabRecovery.service'
|
||||
@@ -48,7 +48,7 @@ export class SplitContainer {
|
||||
/**
|
||||
* Remove unnecessarily nested child containers and renormalizes [[ratios]]
|
||||
*/
|
||||
normalize (): void {
|
||||
normalize () {
|
||||
for (let i = 0; i < this.children.length; i++) {
|
||||
const child = this.children[i]
|
||||
|
||||
@@ -93,7 +93,7 @@ export class SplitContainer {
|
||||
return s
|
||||
}
|
||||
|
||||
async serialize (): Promise<RecoveryToken> {
|
||||
async serialize () {
|
||||
const children: any[] = []
|
||||
for (const child of this.children) {
|
||||
if (child instanceof SplitContainer) {
|
||||
@@ -138,11 +138,9 @@ export interface SplitSpannerInfo {
|
||||
(change)='onSpannerAdjusted(spanner)'
|
||||
></split-tab-spanner>
|
||||
`,
|
||||
styles: [require('./splitTab.component.scss')],
|
||||
styleUrls: ['./splitTab.component.scss'],
|
||||
})
|
||||
export class SplitTabComponent extends BaseTabComponent implements AfterViewInit, OnDestroy {
|
||||
static DIRECTIONS: SplitDirection[] = ['t', 'r', 'b', 'l']
|
||||
|
||||
export class SplitTabComponent extends BaseTabComponent implements OnInit, OnDestroy {
|
||||
/** @hidden */
|
||||
@ViewChild('vc', { read: ViewContainerRef }) viewContainer: ViewContainerRef
|
||||
|
||||
@@ -157,12 +155,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
/** @hidden */
|
||||
_spanners: SplitSpannerInfo[] = []
|
||||
|
||||
/** @hidden */
|
||||
_allFocusMode = false
|
||||
|
||||
/** @hidden */
|
||||
private focusedTab: BaseTabComponent
|
||||
private maximizedTab: BaseTabComponent|null = null
|
||||
private hotkeysSubscription: Subscription
|
||||
private viewRefs: Map<BaseTabComponent, EmbeddedViewRef<any>> = new Map()
|
||||
|
||||
@@ -170,7 +163,6 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
private tabRemoved = new Subject<BaseTabComponent>()
|
||||
private splitAdjusted = new Subject<SplitSpannerInfo>()
|
||||
private focusChanged = new Subject<BaseTabComponent>()
|
||||
private initialized = new Subject<void>()
|
||||
|
||||
get tabAdded$ (): Observable<BaseTabComponent> { return this.tabAdded }
|
||||
get tabRemoved$ (): Observable<BaseTabComponent> { return this.tabRemoved }
|
||||
@@ -185,11 +177,6 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
*/
|
||||
get focusChanged$ (): Observable<BaseTabComponent> { return this.focusChanged }
|
||||
|
||||
/**
|
||||
* Fired once tab layout is created and child tabs can be added
|
||||
*/
|
||||
get initialized$ (): Observable<void> { return this.initialized }
|
||||
|
||||
/** @hidden */
|
||||
constructor (
|
||||
private hotkeys: HotkeysService,
|
||||
@@ -239,13 +226,6 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
case 'pane-nav-down':
|
||||
this.navigate('b')
|
||||
break
|
||||
case 'pane-maximize':
|
||||
if (this.maximizedTab) {
|
||||
this.maximize(null)
|
||||
} else if (this.getAllTabs().length > 1) {
|
||||
this.maximize(this.focusedTab)
|
||||
}
|
||||
break
|
||||
case 'close-pane':
|
||||
this.removeTab(this.focusedTab)
|
||||
break
|
||||
@@ -254,29 +234,26 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
async ngAfterViewInit (): Promise<void> {
|
||||
async ngOnInit () {
|
||||
if (this._recoveredState) {
|
||||
await this.recoverContainer(this.root, this._recoveredState)
|
||||
this.layout()
|
||||
setTimeout(() => {
|
||||
setImmediate(() => {
|
||||
if (this.hasFocus) {
|
||||
for (const tab of this.getAllTabs()) {
|
||||
this.focus(tab)
|
||||
this.getAllTabs().forEach(x => x.emitFocused())
|
||||
this.focusAnyIn(this.root)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
this.initialized.next()
|
||||
this.initialized.complete()
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
ngOnDestroy (): void {
|
||||
ngOnDestroy () {
|
||||
this.hotkeysSubscription.unsubscribe()
|
||||
}
|
||||
|
||||
/** @returns Flat list of all sub-tabs */
|
||||
getAllTabs (): BaseTabComponent[] {
|
||||
getAllTabs () {
|
||||
return this.root.getAllTabs()
|
||||
}
|
||||
|
||||
@@ -284,11 +261,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
return this.focusedTab
|
||||
}
|
||||
|
||||
getMaximizedTab (): BaseTabComponent|null {
|
||||
return this.maximizedTab
|
||||
}
|
||||
|
||||
focus (tab: BaseTabComponent): void {
|
||||
focus (tab: BaseTabComponent) {
|
||||
this.focusedTab = tab
|
||||
for (const x of this.getAllTabs()) {
|
||||
if (x !== tab) {
|
||||
@@ -299,22 +272,13 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
tab.emitFocused()
|
||||
this.focusChanged.next(tab)
|
||||
}
|
||||
|
||||
if (this.maximizedTab !== tab) {
|
||||
this.maximizedTab = null
|
||||
}
|
||||
this.layout()
|
||||
}
|
||||
|
||||
maximize (tab: BaseTabComponent|null): void {
|
||||
this.maximizedTab = tab
|
||||
this.layout()
|
||||
}
|
||||
|
||||
/**
|
||||
* Focuses the first available tab inside the given [[SplitContainer]]
|
||||
*/
|
||||
focusAnyIn (parent: BaseTabComponent | SplitContainer): void {
|
||||
focusAnyIn (parent: BaseTabComponent | SplitContainer) {
|
||||
if (!parent) {
|
||||
return
|
||||
}
|
||||
@@ -328,9 +292,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
/**
|
||||
* Inserts a new `tab` to the `side` of the `relative` tab
|
||||
*/
|
||||
async addTab (tab: BaseTabComponent, relative: BaseTabComponent|null, side: SplitDirection): Promise<void> {
|
||||
tab.parent = this
|
||||
|
||||
addTab (tab: BaseTabComponent, relative: BaseTabComponent|null, side: SplitDirection) {
|
||||
let target = (relative ? this.getParentOf(relative) : null) || this.root
|
||||
let insertIndex = relative ? target.children.indexOf(relative) : -1
|
||||
|
||||
@@ -360,9 +322,6 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
target.children.splice(insertIndex, 0, tab)
|
||||
|
||||
this.recoveryStateChangedHint.next()
|
||||
|
||||
await this.initialized$.toPromise()
|
||||
|
||||
this.attachTabView(tab)
|
||||
|
||||
setImmediate(() => {
|
||||
@@ -372,7 +331,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
})
|
||||
}
|
||||
|
||||
removeTab (tab: BaseTabComponent): void {
|
||||
removeTab (tab: BaseTabComponent) {
|
||||
const parent = this.getParentOf(tab)
|
||||
if (!parent) {
|
||||
return
|
||||
@@ -382,11 +341,11 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
parent.children.splice(index, 1)
|
||||
|
||||
this.detachTabView(tab)
|
||||
tab.parent = null
|
||||
|
||||
this.layout()
|
||||
|
||||
this.tabRemoved.next(tab)
|
||||
|
||||
if (this.root.children.length === 0) {
|
||||
this.destroy()
|
||||
} else {
|
||||
@@ -397,7 +356,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
/**
|
||||
* Moves focus in the given direction
|
||||
*/
|
||||
navigate (dir: SplitDirection): void {
|
||||
navigate (dir: SplitDirection) {
|
||||
let rel: BaseTabComponent | SplitContainer = this.focusedTab
|
||||
let parent = this.getParentOf(rel)
|
||||
if (!parent) {
|
||||
@@ -430,12 +389,11 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
}
|
||||
}
|
||||
|
||||
async splitTab (tab: BaseTabComponent, dir: SplitDirection): Promise<BaseTabComponent|null> {
|
||||
async splitTab (tab: BaseTabComponent, dir: SplitDirection) {
|
||||
const newTab = await this.tabsService.duplicate(tab)
|
||||
if (newTab) {
|
||||
this.addTab(newTab, tab, dir)
|
||||
}
|
||||
return newTab
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -473,24 +431,11 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
onSpannerAdjusted (spanner: SplitSpannerInfo): void {
|
||||
onSpannerAdjusted (spanner: SplitSpannerInfo) {
|
||||
this.layout()
|
||||
this.splitAdjusted.next(spanner)
|
||||
}
|
||||
|
||||
destroy (): void {
|
||||
super.destroy()
|
||||
for (const x of this.getAllTabs()) {
|
||||
x.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
layout (): void {
|
||||
this.root.normalize()
|
||||
this._spanners = []
|
||||
this.layoutInternal(this.root, 0, 0, 100, 100)
|
||||
}
|
||||
|
||||
private attachTabView (tab: BaseTabComponent) {
|
||||
const ref = this.viewContainer.insert(tab.hostView) as EmbeddedViewRef<any> // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
this.viewRefs.set(tab, ref)
|
||||
@@ -516,6 +461,12 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
}
|
||||
}
|
||||
|
||||
private layout () {
|
||||
this.root.normalize()
|
||||
this._spanners = []
|
||||
this.layoutInternal(this.root, 0, 0, 100, 100)
|
||||
}
|
||||
|
||||
private layoutInternal (root: SplitContainer, x: number, y: number, w: number, h: number) {
|
||||
const size = root.orientation === 'v' ? h : w
|
||||
const sizes = root.ratios.map(x => x * size)
|
||||
@@ -534,25 +485,14 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
if (child instanceof SplitContainer) {
|
||||
this.layoutInternal(child, childX, childY, childW, childH)
|
||||
} else {
|
||||
const viewRef = this.viewRefs.get(child)
|
||||
if (viewRef) {
|
||||
const element = viewRef.rootNodes[0]
|
||||
element.classList.toggle('child', true)
|
||||
element.classList.toggle('maximized', child === this.maximizedTab)
|
||||
element.classList.toggle('minimized', this.maximizedTab && child !== this.maximizedTab)
|
||||
element.classList.toggle('focused', this._allFocusMode || child === this.focusedTab)
|
||||
const element = this.viewRefs.get(child)!.rootNodes[0]
|
||||
element.style.position = 'absolute'
|
||||
element.style.left = `${childX}%`
|
||||
element.style.top = `${childY}%`
|
||||
element.style.width = `${childW}%`
|
||||
element.style.height = `${childH}%`
|
||||
|
||||
if (child === this.maximizedTab) {
|
||||
element.style.left = '5%'
|
||||
element.style.top = '5%'
|
||||
element.style.width = '90%'
|
||||
element.style.height = '90%'
|
||||
}
|
||||
}
|
||||
element.style.opacity = child === this.focusedTab ? 1 : 0.75
|
||||
}
|
||||
offset += sizes[i]
|
||||
|
||||
@@ -580,24 +520,19 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
if (recovered) {
|
||||
const tab = this.tabsService.create(recovered.type, recovered.options)
|
||||
children.push(tab)
|
||||
tab.parent = this
|
||||
this.attachTabView(tab)
|
||||
} else {
|
||||
state.ratios.splice(state.children.indexOf(childState), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
while (root.ratios.length < root.children.length) {
|
||||
root.ratios.push(1)
|
||||
}
|
||||
root.normalize()
|
||||
}
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class SplitTabRecoveryProvider extends TabRecoveryProvider {
|
||||
async recover (recoveryToken: RecoveryToken): Promise<RecoveredTab|null> {
|
||||
async recover (recoveryToken: any): Promise<RecoveredTab|null> {
|
||||
if (recoveryToken && recoveryToken.type === 'app:split-tab') {
|
||||
return {
|
||||
type: SplitTabComponent,
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Component, Input, HostBinding, ElementRef, Output, EventEmitter } from '@angular/core'
|
||||
import { SplitContainer } from './splitTab.component'
|
||||
|
||||
@@ -6,7 +5,7 @@ import { SplitContainer } from './splitTab.component'
|
||||
@Component({
|
||||
selector: 'split-tab-spanner',
|
||||
template: '',
|
||||
styles: [require('./splitTabSpanner.component.scss')],
|
||||
styleUrls: ['./splitTabSpanner.component.scss'],
|
||||
})
|
||||
export class SplitTabSpannerComponent {
|
||||
@Input() container: SplitContainer
|
||||
@@ -17,17 +16,13 @@ export class SplitTabSpannerComponent {
|
||||
@HostBinding('class.v') isVertical = true
|
||||
@HostBinding('style.left') cssLeft: string
|
||||
@HostBinding('style.top') cssTop: string
|
||||
@HostBinding('style.width') cssWidth: string | null
|
||||
@HostBinding('style.height') cssHeight: string | null
|
||||
@HostBinding('style.width') cssWidth: string
|
||||
@HostBinding('style.height') cssHeight: string
|
||||
private marginOffset = -5
|
||||
|
||||
constructor (private element: ElementRef) { }
|
||||
|
||||
ngAfterViewInit () {
|
||||
this.element.nativeElement.addEventListener('dblclick', () => {
|
||||
this.reset()
|
||||
})
|
||||
|
||||
this.element.nativeElement.addEventListener('mousedown', (e: MouseEvent) => {
|
||||
this.isActive = true
|
||||
const start = this.isVertical ? e.pageY : e.pageX
|
||||
@@ -54,16 +49,14 @@ export class SplitTabSpannerComponent {
|
||||
diff = Math.max(diff, -this.container.ratios[this.index - 1] + 0.1)
|
||||
diff = Math.min(diff, this.container.ratios[this.index] - 0.1)
|
||||
|
||||
if (diff) {
|
||||
this.container.ratios[this.index - 1] += diff
|
||||
this.container.ratios[this.index] -= diff
|
||||
this.change.emit()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('mouseup', offHandler, { passive: true })
|
||||
document.addEventListener('mouseup', offHandler)
|
||||
this.element.nativeElement.parentElement.addEventListener('mousemove', dragHandler)
|
||||
}, { passive: true })
|
||||
})
|
||||
}
|
||||
|
||||
ngOnChanges () {
|
||||
@@ -86,17 +79,10 @@ export class SplitTabSpannerComponent {
|
||||
}
|
||||
}
|
||||
|
||||
reset () {
|
||||
const ratio = (this.container.ratios[this.index - 1] + this.container.ratios[this.index]) / 2
|
||||
this.container.ratios[this.index - 1] = ratio
|
||||
this.container.ratios[this.index] = ratio
|
||||
this.change.emit()
|
||||
}
|
||||
|
||||
private setDimensions (x: number, y: number, w: number, h: number) {
|
||||
this.cssLeft = `${x}%`
|
||||
this.cssTop = `${y}%`
|
||||
this.cssWidth = w ? `${w}%` : null
|
||||
this.cssHeight = h ? `${h}%` : null
|
||||
this.cssWidth = w ? `${w}%` : 'initial'
|
||||
this.cssHeight = h ? `${h}%` : 'initial'
|
||||
}
|
||||
}
|
||||
|
@@ -7,13 +7,13 @@ import { ToolbarButton, ToolbarButtonProvider } from '../api'
|
||||
/** @hidden */
|
||||
@Component({
|
||||
selector: 'start-page',
|
||||
template: require('./startPage.component.pug'),
|
||||
styles: [require('./startPage.component.scss')],
|
||||
templateUrl: './startPage.component.pug',
|
||||
styleUrls: ['./startPage.component.scss'],
|
||||
})
|
||||
export class StartPageComponent {
|
||||
version: string
|
||||
|
||||
private constructor (
|
||||
constructor (
|
||||
private config: ConfigService,
|
||||
private domSanitizer: DomSanitizer,
|
||||
public homeBase: HomeBaseService,
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Component, Input, ViewChild, HostBinding, ViewContainerRef, OnChanges } from '@angular/core'
|
||||
import { BaseTabComponent } from '../components/baseTab.component'
|
||||
|
||||
@@ -11,9 +10,9 @@ import { BaseTabComponent } from '../components/baseTab.component'
|
||||
</perfect-scrollbar-->
|
||||
<ng-template #placeholder></ng-template>
|
||||
`,
|
||||
styles: [
|
||||
require('./tabBody.component.scss'),
|
||||
require('./tabBody.deep.component.css'),
|
||||
styleUrls: [
|
||||
'./tabBody.component.scss',
|
||||
'./tabBody.deep.component.css',
|
||||
],
|
||||
})
|
||||
export class TabBodyComponent implements OnChanges {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
.progressbar([style.width]='progress + "%"', *ngIf='progress != null')
|
||||
.index(*ngIf='!config.store.terminal.hideTabIndex',
|
||||
.index(
|
||||
#handle,
|
||||
[style.background-color]='tab.color',
|
||||
) {{index + 1}}
|
||||
.name([title]='tab.customTitle || tab.title') {{tab.customTitle || tab.title}}
|
||||
button(*ngIf='!config.store.terminal.hideCloseButton',(click)='app.closeTab(tab, true)') ×
|
||||
button((click)='app.closeTab(tab, true)') ×
|
||||
|
@@ -13,11 +13,6 @@ $tabs-height: 38px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
&.vertical {
|
||||
flex: none;
|
||||
height: $tabs-height;
|
||||
}
|
||||
|
||||
.index {
|
||||
flex: none;
|
||||
font-weight: bold;
|
||||
@@ -25,7 +20,7 @@ $tabs-height: 38px;
|
||||
cursor: -webkit-grab;
|
||||
|
||||
margin-left: 10px;
|
||||
width: 22px;
|
||||
width: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
transition: 0.25s all;
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Component, Input, Optional, Inject, HostBinding, HostListener, ViewChild, ElementRef } from '@angular/core'
|
||||
import { SortableComponent } from 'ng2-dnd'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
@@ -9,7 +8,6 @@ import { HotkeysService } from '../services/hotkeys.service'
|
||||
import { ElectronService } from '../services/electron.service'
|
||||
import { AppService } from '../services/app.service'
|
||||
import { HostAppService, Platform } from '../services/hostApp.service'
|
||||
import { ConfigService } from '../services/config.service'
|
||||
|
||||
/** @hidden */
|
||||
export interface SortableComponentProxy {
|
||||
@@ -19,8 +17,8 @@ export interface SortableComponentProxy {
|
||||
/** @hidden */
|
||||
@Component({
|
||||
selector: 'tab-header',
|
||||
template: require('./tabHeader.component.pug'),
|
||||
styles: [require('./tabHeader.component.scss')],
|
||||
templateUrl: './tabHeader.component.pug',
|
||||
styleUrls: ['./tabHeader.component.scss'],
|
||||
})
|
||||
export class TabHeaderComponent {
|
||||
@Input() index: number
|
||||
@@ -32,7 +30,6 @@ export class TabHeaderComponent {
|
||||
|
||||
private constructor (
|
||||
public app: AppService,
|
||||
public config: ConfigService,
|
||||
private electron: ElectronService,
|
||||
private hostApp: HostAppService,
|
||||
private ngbModal: NgbModal,
|
||||
@@ -51,15 +48,12 @@ export class TabHeaderComponent {
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.tab.progress$.subscribe(progress => {
|
||||
this.progress = progress
|
||||
})
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
if (this.hostApp.platform === Platform.macOS) {
|
||||
this.parentDraggable.setDragHandle(this.handle.nativeElement)
|
||||
}
|
||||
this.tab.progress$.subscribe(progress => {
|
||||
this.progress = progress
|
||||
})
|
||||
}
|
||||
|
||||
showRenameTabModal (): void {
|
||||
|
@@ -1,9 +1,12 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { HostAppService } from '../services/hostApp.service'
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
selector: 'title-bar',
|
||||
template: require('./titleBar.component.pug'),
|
||||
styles: [require('./titleBar.component.scss')],
|
||||
templateUrl: './titleBar.component.pug',
|
||||
styleUrls: ['./titleBar.component.scss'],
|
||||
})
|
||||
export class TitleBarComponent { } // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||
export class TitleBarComponent {
|
||||
constructor (public hostApp: HostAppService) { }
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ import { CheckboxComponent } from './checkbox.component'
|
||||
<label class="custom-control-label"></label>
|
||||
</div>
|
||||
`,
|
||||
styles: [require('./toggle.component.scss')],
|
||||
styleUrls: ['./toggle.component.scss'],
|
||||
providers: [
|
||||
{ provide: NG_VALUE_ACCESSOR, useExisting: ToggleComponent, multi: true },
|
||||
],
|
||||
|
@@ -1,36 +1,19 @@
|
||||
.container.mt-5.mb-5
|
||||
.mb-4
|
||||
.mb-4
|
||||
.terminus-logo
|
||||
h1.terminus-title Terminus
|
||||
sup α
|
||||
|
||||
.container
|
||||
.text-center.mb-5 Thank you for downloading Terminus!
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Enable analytics
|
||||
.description Help track the number of Terminus installs across the world!
|
||||
toggle([(ngModel)]='config.store.enableAnalytics')
|
||||
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Enable global hotkey (#[strong Ctrl-Space])
|
||||
.description Toggles the Terminus window visibility
|
||||
toggle([(ngModel)]='enableGlobalHotkey')
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Enable #[strong SSH] plugin
|
||||
.description Adds an SSH connection manager UI to Terminus
|
||||
toggle([(ngModel)]='enableSSH')
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Enable #[strong Serial] plugin
|
||||
.description Allows attaching Terminus to serial ports
|
||||
toggle([(ngModel)]='enableSerial')
|
||||
|
||||
.description Help us track the number of Terminus installs across the world!
|
||||
toggle(
|
||||
[(ngModel)]='config.store.enableAnalytics',
|
||||
(ngModelChange)='config.save(); config.requestRestart()',
|
||||
)
|
||||
|
||||
.text-center.mt-5
|
||||
button.btn.btn-primary((click)='closeAndDisable()') Close and never show again
|
||||
|
@@ -2,7 +2,5 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: auto;
|
||||
flex: auto;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
flex: 0 1 500px;
|
||||
}
|
||||
|
@@ -1,43 +1,26 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Component } from '@angular/core'
|
||||
import { BaseTabComponent } from './baseTab.component'
|
||||
import { ConfigService } from '../services/config.service'
|
||||
import { HostAppService } from '../services/hostApp.service'
|
||||
import { AppService } from '../services/app.service'
|
||||
|
||||
/** @hidden */
|
||||
@Component({
|
||||
selector: 'welcome-page',
|
||||
template: require('./welcomeTab.component.pug'),
|
||||
styles: [require('./welcomeTab.component.scss')],
|
||||
templateUrl: './welcomeTab.component.pug',
|
||||
styleUrls: ['./welcomeTab.component.scss'],
|
||||
})
|
||||
export class WelcomeTabComponent extends BaseTabComponent {
|
||||
enableSSH = false
|
||||
enableSerial = false
|
||||
enableGlobalHotkey = true
|
||||
|
||||
constructor (
|
||||
private hostApp: HostAppService,
|
||||
private app: AppService,
|
||||
public config: ConfigService,
|
||||
) {
|
||||
super()
|
||||
this.setTitle('Welcome')
|
||||
this.enableSSH = !config.store.pluginBlacklist.includes('ssh')
|
||||
this.enableSerial = !config.store.pluginBlacklist.includes('serial')
|
||||
}
|
||||
|
||||
closeAndDisable () {
|
||||
this.config.store.enableWelcomeTab = false
|
||||
this.config.store.pluginBlacklist = []
|
||||
if (!this.enableSSH) {
|
||||
this.config.store.pluginBlacklist.push('ssh')
|
||||
}
|
||||
if (!this.enableSerial) {
|
||||
this.config.store.pluginBlacklist.push('serial')
|
||||
}
|
||||
if (!this.enableGlobalHotkey) {
|
||||
this.config.store.hotkeys['toggle-window'] = []
|
||||
}
|
||||
this.config.save()
|
||||
this.hostApp.getWindow().reload()
|
||||
this.app.closeTab(this)
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Component } from '@angular/core'
|
||||
import { HostAppService } from '../services/hostApp.service'
|
||||
import { AppService } from '../services/app.service'
|
||||
@@ -6,13 +5,15 @@ import { AppService } from '../services/app.service'
|
||||
/** @hidden */
|
||||
@Component({
|
||||
selector: 'window-controls',
|
||||
template: require('./windowControls.component.pug'),
|
||||
styles: [require('./windowControls.component.scss')],
|
||||
templateUrl: './windowControls.component.pug',
|
||||
styleUrls: ['./windowControls.component.scss'],
|
||||
})
|
||||
export class WindowControlsComponent {
|
||||
private constructor (public hostApp: HostAppService, public app: AppService) { }
|
||||
constructor (public hostApp: HostAppService, public app: AppService) { }
|
||||
|
||||
async closeWindow () {
|
||||
this.app.closeWindow()
|
||||
if (await this.app.closeAllTabs()) {
|
||||
this.hostApp.closeWindow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,8 +7,6 @@ hotkeys:
|
||||
- 'F11'
|
||||
close-tab:
|
||||
- 'Ctrl-Shift-W'
|
||||
reopen-tab:
|
||||
- 'Ctrl-Shift-T'
|
||||
toggle-last-tab: []
|
||||
rename-tab:
|
||||
- 'Ctrl-Shift-R'
|
||||
@@ -18,10 +16,6 @@ hotkeys:
|
||||
previous-tab:
|
||||
- 'Ctrl-Shift-Left'
|
||||
- 'Ctrl-Shift-Tab'
|
||||
move-tab-left:
|
||||
- 'Ctrl-Shift-PageUp'
|
||||
move-tab-right:
|
||||
- 'Ctrl-Shift-PageDown'
|
||||
tab-1:
|
||||
- 'Alt-1'
|
||||
tab-2:
|
||||
@@ -56,7 +50,5 @@ hotkeys:
|
||||
- 'Ctrl-Alt-Up'
|
||||
pane-nav-left:
|
||||
- 'Ctrl-Alt-Left'
|
||||
pane-maximize:
|
||||
- 'Ctrl-Alt-Enter'
|
||||
close-pane: []
|
||||
pluginBlacklist: ['ssh']
|
||||
|
@@ -7,8 +7,6 @@ hotkeys:
|
||||
- 'Ctrl+⌘+F'
|
||||
close-tab:
|
||||
- '⌘-W'
|
||||
reopen-tab:
|
||||
- '⌘-Shift-T'
|
||||
toggle-last-tab: []
|
||||
rename-tab:
|
||||
- '⌘-R'
|
||||
@@ -16,10 +14,6 @@ hotkeys:
|
||||
- 'Ctrl-Tab'
|
||||
previous-tab:
|
||||
- 'Ctrl-Shift-Tab'
|
||||
move-tab-left:
|
||||
- '⌘-Shift-Left'
|
||||
move-tab-right:
|
||||
- '⌘-Shift-Right'
|
||||
tab-1:
|
||||
- '⌘-1'
|
||||
tab-2:
|
||||
@@ -54,8 +48,6 @@ hotkeys:
|
||||
- '⌘-⌥-Up'
|
||||
pane-nav-left:
|
||||
- '⌘-⌥-Left'
|
||||
pane-maximize:
|
||||
- '⌘-⌥-Enter'
|
||||
close-pane:
|
||||
- '⌘-Shift-W'
|
||||
pluginBlacklist: ['ssh']
|
||||
|
@@ -5,11 +5,8 @@ hotkeys:
|
||||
- 'Ctrl+Space'
|
||||
toggle-fullscreen:
|
||||
- 'F11'
|
||||
- 'Alt-Enter'
|
||||
close-tab:
|
||||
- 'Ctrl-Shift-W'
|
||||
reopen-tab:
|
||||
- 'Ctrl-Shift-T'
|
||||
toggle-last-tab: []
|
||||
rename-tab:
|
||||
- 'Ctrl-Shift-R'
|
||||
@@ -19,10 +16,6 @@ hotkeys:
|
||||
previous-tab:
|
||||
- 'Ctrl-Shift-Left'
|
||||
- 'Ctrl-Shift-Tab'
|
||||
move-tab-left:
|
||||
- 'Ctrl-Shift-PageUp'
|
||||
move-tab-right:
|
||||
- 'Ctrl-Shift-PageDown'
|
||||
tab-1:
|
||||
- 'Alt-1'
|
||||
tab-2:
|
||||
@@ -57,7 +50,5 @@ hotkeys:
|
||||
- 'Ctrl-Alt-Up'
|
||||
pane-nav-left:
|
||||
- 'Ctrl-Alt-Left'
|
||||
pane-maximize:
|
||||
- 'Ctrl-Alt-Enter'
|
||||
close-pane: []
|
||||
pluginBlacklist: []
|
||||
|
@@ -2,15 +2,13 @@ appearance:
|
||||
dock: off
|
||||
dockScreen: current
|
||||
dockFill: 0.5
|
||||
dockHideOnBlur: false
|
||||
dockAlwaysOnTop: true
|
||||
tabsLocation: top
|
||||
cycleTabs: true
|
||||
theme: Standard
|
||||
frame: thin
|
||||
css: '/* * { color: blue !important; } */'
|
||||
opacity: 1.0
|
||||
vibrancy: true
|
||||
vibrancy: false
|
||||
vibrancyType: 'blur'
|
||||
enableAnalytics: true
|
||||
enableWelcomeTab: true
|
||||
|
@@ -7,7 +7,7 @@ import { Directive, AfterViewInit, ElementRef } from '@angular/core'
|
||||
export class AutofocusDirective implements AfterViewInit {
|
||||
constructor (private el: ElementRef) { }
|
||||
|
||||
ngAfterViewInit (): void {
|
||||
ngAfterViewInit () {
|
||||
this.el.nativeElement.blur()
|
||||
setTimeout(() => {
|
||||
this.el.nativeElement.focus()
|
||||
|
@@ -1,14 +0,0 @@
|
||||
import { Directive, Input, ElementRef, OnChanges } from '@angular/core'
|
||||
|
||||
/** @hidden */
|
||||
@Directive({
|
||||
selector: '[fastHtmlBind]',
|
||||
})
|
||||
export class FastHtmlBindDirective implements OnChanges {
|
||||
@Input() fastHtmlBind: string
|
||||
constructor (private el: ElementRef) { }
|
||||
|
||||
ngOnChanges (): void {
|
||||
this.el.nativeElement.innerHTML = this.fastHtmlBind || ''
|
||||
}
|
||||
}
|
@@ -25,10 +25,6 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
||||
id: 'close-tab',
|
||||
name: 'Close tab',
|
||||
},
|
||||
{
|
||||
id: 'reopen-tab',
|
||||
name: 'Reopen last tab',
|
||||
},
|
||||
{
|
||||
id: 'toggle-last-tab',
|
||||
name: 'Toggle last tab',
|
||||
@@ -41,14 +37,6 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
||||
id: 'previous-tab',
|
||||
name: 'Previous tab',
|
||||
},
|
||||
{
|
||||
id: 'move-tab-left',
|
||||
name: 'Move tab to the left',
|
||||
},
|
||||
{
|
||||
id: 'move-tab-right',
|
||||
name: 'Move tab to the right',
|
||||
},
|
||||
{
|
||||
id: 'tab-1',
|
||||
name: 'Tab 1',
|
||||
@@ -105,10 +93,6 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
||||
id: 'split-top',
|
||||
name: 'Split to the top',
|
||||
},
|
||||
{
|
||||
id: 'pane-maximize',
|
||||
name: 'Maximize the active pane',
|
||||
},
|
||||
{
|
||||
id: 'pane-nav-up',
|
||||
name: 'Focus the pane above',
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import { NgModule, ModuleWithProviders } from '@angular/core'
|
||||
import { NgModule } from '@angular/core'
|
||||
import { BrowserModule } from '@angular/platform-browser'
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar'
|
||||
import { DndModule } from 'ng2-dnd'
|
||||
// import { DndModule } from 'ng2-dnd'
|
||||
|
||||
import { AppRootComponent } from './components/appRoot.component'
|
||||
import { CheckboxComponent } from './components/checkbox.component'
|
||||
@@ -16,18 +17,16 @@ import { TitleBarComponent } from './components/titleBar.component'
|
||||
import { ToggleComponent } from './components/toggle.component'
|
||||
import { WindowControlsComponent } from './components/windowControls.component'
|
||||
import { RenameTabModalComponent } from './components/renameTabModal.component'
|
||||
import { SelectorModalComponent } from './components/selectorModal.component'
|
||||
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
|
||||
import { SplitTabSpannerComponent } from './components/splitTabSpanner.component'
|
||||
import { WelcomeTabComponent } from './components/welcomeTab.component'
|
||||
|
||||
import { AutofocusDirective } from './directives/autofocus.directive'
|
||||
import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive'
|
||||
|
||||
import { HotkeyProvider } from './api/hotkeyProvider'
|
||||
import { ConfigProvider } from './api/configProvider'
|
||||
import { Theme } from './api/theme'
|
||||
import { TabContextMenuItemProvider } from './api/tabContextMenuProvider'
|
||||
// import { TabContextMenuItemProvider } from './api/tabContextMenuProvider'
|
||||
import { TabRecoveryProvider } from './api/tabRecovery'
|
||||
|
||||
import { AppService } from './services/app.service'
|
||||
@@ -36,7 +35,7 @@ import { ConfigService } from './services/config.service'
|
||||
import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
|
||||
import { CoreConfigProvider } from './config'
|
||||
import { AppHotkeyProvider } from './hotkeys'
|
||||
import { TaskCompletionContextMenu, CommonOptionsContextMenu, TabManagementContextMenu } from './tabContextMenu'
|
||||
// import { TaskCompletionContextMenu, CommonOptionsContextMenu, CloseContextMenu } from './tabContextMenu'
|
||||
|
||||
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
||||
import 'ng2-dnd/bundles/style.css'
|
||||
@@ -53,9 +52,9 @@ const PROVIDERS = [
|
||||
{ provide: Theme, useClass: StandardCompactTheme, multi: true },
|
||||
{ provide: Theme, useClass: PaperTheme, multi: true },
|
||||
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
|
||||
{ provide: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true },
|
||||
{ provide: TabContextMenuItemProvider, useClass: TabManagementContextMenu, multi: true },
|
||||
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
||||
// { provide: TabContextMenuItemProvider, useClass: CommonOptionsContextMenu, multi: true },
|
||||
// { provide: TabContextMenuItemProvider, useClass: CloseContextMenu, multi: true },
|
||||
// { provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
||||
{ provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
|
||||
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
|
||||
]
|
||||
@@ -68,8 +67,9 @@ const PROVIDERS = [
|
||||
FormsModule,
|
||||
NgbModule,
|
||||
PerfectScrollbarModule,
|
||||
DndModule.forRoot(),
|
||||
// DndModule,
|
||||
],
|
||||
providers: PROVIDERS,
|
||||
declarations: [
|
||||
AppRootComponent as any,
|
||||
CheckboxComponent,
|
||||
@@ -82,8 +82,6 @@ const PROVIDERS = [
|
||||
RenameTabModalComponent,
|
||||
SafeModeModalComponent,
|
||||
AutofocusDirective,
|
||||
FastHtmlBindDirective,
|
||||
SelectorModalComponent,
|
||||
SplitTabComponent,
|
||||
SplitTabSpannerComponent,
|
||||
WelcomeTabComponent,
|
||||
@@ -91,7 +89,6 @@ const PROVIDERS = [
|
||||
entryComponents: [
|
||||
RenameTabModalComponent,
|
||||
SafeModeModalComponent,
|
||||
SelectorModalComponent,
|
||||
SplitTabComponent,
|
||||
WelcomeTabComponent,
|
||||
],
|
||||
@@ -101,7 +98,7 @@ const PROVIDERS = [
|
||||
AutofocusDirective,
|
||||
],
|
||||
})
|
||||
export default class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||
export class AppModule { // eslint-disable-line @typescript-eslint/no-extraneous-class
|
||||
constructor (app: AppService, config: ConfigService) {
|
||||
app.ready$.subscribe(() => {
|
||||
if (config.store.enableWelcomeTab) {
|
||||
@@ -109,18 +106,17 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static forRoot (): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: AppModule,
|
||||
providers: PROVIDERS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AppModule
|
||||
|
||||
export { AppRootComponent as bootstrap }
|
||||
export * from './api'
|
||||
|
||||
// Deprecations
|
||||
export { ToolbarButton as IToolbarButton } from './api'
|
||||
export { HotkeyDescription as IHotkeyDescription } from './api'
|
||||
|
||||
export function fakeBootstrap () {
|
||||
return platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
}
|
||||
|
@@ -2,13 +2,9 @@
|
||||
import { Observable, Subject, AsyncSubject } from 'rxjs'
|
||||
import { takeUntil } from 'rxjs/operators'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
|
||||
import { BaseTabComponent } from '../components/baseTab.component'
|
||||
import { SplitTabComponent } from '../components/splitTab.component'
|
||||
import { SelectorModalComponent } from '../components/selectorModal.component'
|
||||
import { SelectorOption } from '../api/selector'
|
||||
import { RecoveryToken } from '../api/tabRecovery'
|
||||
|
||||
import { ConfigService } from './config.service'
|
||||
import { HostAppService } from './hostApp.service'
|
||||
@@ -50,7 +46,6 @@ export class AppService {
|
||||
|
||||
private lastTabIndex = 0
|
||||
private _activeTab: BaseTabComponent
|
||||
private closedTabsStack: RecoveryToken[] = []
|
||||
|
||||
private activeTabChange = new Subject<BaseTabComponent>()
|
||||
private tabsChanged = new Subject<void>()
|
||||
@@ -69,53 +64,38 @@ export class AppService {
|
||||
get ready$ (): Observable<void> { return this.ready }
|
||||
|
||||
/** @hidden */
|
||||
private constructor (
|
||||
constructor (
|
||||
private config: ConfigService,
|
||||
private hostApp: HostAppService,
|
||||
private tabRecovery: TabRecoveryService,
|
||||
private tabsService: TabsService,
|
||||
private ngbModal: NgbModal,
|
||||
) {
|
||||
this.tabsChanged$.subscribe(() => {
|
||||
this.tabRecovery.saveTabs(this.tabs)
|
||||
})
|
||||
setInterval(() => {
|
||||
this.tabRecovery.saveTabs(this.tabs)
|
||||
}, 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
|
||||
this.startTabStorage()
|
||||
})
|
||||
} else {
|
||||
/** Continue to store the tabs even if the setting is currently off */
|
||||
this.tabRecovery.enabled = true
|
||||
this.startTabStorage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hostApp.windowFocused$.subscribe(() => {
|
||||
this._activeTab?.emitFocused()
|
||||
})
|
||||
|
||||
this.tabClosed$.subscribe(async tab => {
|
||||
const token = await tab.getRecoveryToken()
|
||||
if (token) {
|
||||
this.closedTabsStack.push(token)
|
||||
}
|
||||
startTabStorage () {
|
||||
this.tabsChanged$.subscribe(() => {
|
||||
this.tabRecovery.saveTabs(this.tabs)
|
||||
})
|
||||
setInterval(() => {
|
||||
this.tabRecovery.saveTabs(this.tabs)
|
||||
}, 30000)
|
||||
}
|
||||
|
||||
addTabRaw (tab: BaseTabComponent, index: number|null = null): void {
|
||||
if (index !== null) {
|
||||
this.tabs.splice(index, 0, tab)
|
||||
} else {
|
||||
addTabRaw (tab: BaseTabComponent) {
|
||||
this.tabs.push(tab)
|
||||
}
|
||||
|
||||
this.selectTab(tab)
|
||||
this.tabsChanged.next()
|
||||
this.tabOpened.next(tab)
|
||||
@@ -141,18 +121,13 @@ export class AppService {
|
||||
this.tabsChanged.next()
|
||||
this.tabClosed.next(tab)
|
||||
})
|
||||
|
||||
if (tab instanceof SplitTabComponent) {
|
||||
tab.tabAdded$.subscribe(() => this.emitTabsChanged())
|
||||
tab.tabRemoved$.subscribe(() => this.emitTabsChanged())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new tab **without** wrapping it in a SplitTabComponent
|
||||
* @param inputs Properties to be assigned on the new tab component instance
|
||||
*/
|
||||
openNewTabRaw (type: TabComponentType, inputs?: Record<string, any>): BaseTabComponent {
|
||||
openNewTabRaw (type: TabComponentType, inputs?: any): BaseTabComponent {
|
||||
const tab = this.tabsService.create(type, inputs)
|
||||
this.addTabRaw(tab)
|
||||
return tab
|
||||
@@ -162,7 +137,7 @@ export class AppService {
|
||||
* Adds a new tab while wrapping it in a SplitTabComponent
|
||||
* @param inputs Properties to be assigned on the new tab component instance
|
||||
*/
|
||||
openNewTab (type: TabComponentType, inputs?: Record<string, any>): BaseTabComponent {
|
||||
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
|
||||
const splitTab = this.tabsService.create(SplitTabComponent) as SplitTabComponent
|
||||
const tab = this.tabsService.create(type, inputs)
|
||||
splitTab.addTab(tab, null, 'r')
|
||||
@@ -170,24 +145,7 @@ export class AppService {
|
||||
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) {
|
||||
if (this._activeTab === tab) {
|
||||
this._activeTab.emitFocused()
|
||||
return
|
||||
@@ -211,26 +169,15 @@ export class AppService {
|
||||
}
|
||||
}
|
||||
|
||||
getParentTab (tab: BaseTabComponent): SplitTabComponent|null {
|
||||
for (const topLevelTab of this.tabs) {
|
||||
if (topLevelTab instanceof SplitTabComponent) {
|
||||
if (topLevelTab.getAllTabs().includes(tab)) {
|
||||
return topLevelTab
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** Switches between the current tab and the previously active one */
|
||||
toggleLastTab (): void {
|
||||
toggleLastTab () {
|
||||
if (!this.lastTabIndex || this.lastTabIndex >= this.tabs.length) {
|
||||
this.lastTabIndex = 0
|
||||
}
|
||||
this.selectTab(this.tabs[this.lastTabIndex])
|
||||
}
|
||||
|
||||
nextTab (): void {
|
||||
nextTab () {
|
||||
if (this.tabs.length > 1) {
|
||||
const tabIndex = this.tabs.indexOf(this._activeTab)
|
||||
if (tabIndex < this.tabs.length - 1) {
|
||||
@@ -241,7 +188,7 @@ export class AppService {
|
||||
}
|
||||
}
|
||||
|
||||
previousTab (): void {
|
||||
previousTab () {
|
||||
if (this.tabs.length > 1) {
|
||||
const tabIndex = this.tabs.indexOf(this._activeTab)
|
||||
if (tabIndex > 0) {
|
||||
@@ -252,37 +199,8 @@ export class AppService {
|
||||
}
|
||||
}
|
||||
|
||||
moveSelectedTabLeft (): void {
|
||||
if (this.tabs.length > 1) {
|
||||
const tabIndex = this.tabs.indexOf(this._activeTab)
|
||||
if (tabIndex > 0) {
|
||||
this.swapTabs(this._activeTab, this.tabs[tabIndex - 1])
|
||||
} else if (this.config.store.appearance.cycleTabs) {
|
||||
this.swapTabs(this._activeTab, this.tabs[this.tabs.length - 1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveSelectedTabRight (): void {
|
||||
if (this.tabs.length > 1) {
|
||||
const tabIndex = this.tabs.indexOf(this._activeTab)
|
||||
if (tabIndex < this.tabs.length - 1) {
|
||||
this.swapTabs(this._activeTab, this.tabs[tabIndex + 1])
|
||||
} else if (this.config.store.appearance.cycleTabs) {
|
||||
this.swapTabs(this._activeTab, this.tabs[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
swapTabs (a: BaseTabComponent, b: BaseTabComponent): void {
|
||||
const i1 = this.tabs.indexOf(a)
|
||||
const i2 = this.tabs.indexOf(b)
|
||||
this.tabs[i1] = b
|
||||
this.tabs[i2] = a
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
emitTabsChanged (): void {
|
||||
emitTabsChanged () {
|
||||
this.tabsChanged.next()
|
||||
}
|
||||
|
||||
@@ -296,12 +214,11 @@ export class AppService {
|
||||
tab.destroy()
|
||||
}
|
||||
|
||||
async duplicateTab (tab: BaseTabComponent): Promise<BaseTabComponent|null> {
|
||||
async duplicateTab (tab: BaseTabComponent) {
|
||||
const dup = await this.tabsService.duplicate(tab)
|
||||
if (dup) {
|
||||
this.addTabRaw(dup, this.tabs.indexOf(tab) + 1)
|
||||
this.addTabRaw(dup)
|
||||
}
|
||||
return dup
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,18 +236,8 @@ export class AppService {
|
||||
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 */
|
||||
emitReady (): void {
|
||||
emitReady () {
|
||||
this.ready.next()
|
||||
this.ready.complete()
|
||||
this.hostApp.emitReady()
|
||||
@@ -351,15 +258,7 @@ export class AppService {
|
||||
return this.completionObservers.get(tab)!.done$
|
||||
}
|
||||
|
||||
stopObservingTabCompletion (tab: BaseTabComponent): void {
|
||||
stopObservingTabCompletion (tab: BaseTabComponent) {
|
||||
this.completionObservers.delete(tab)
|
||||
}
|
||||
|
||||
showSelector <T> (name: string, options: SelectorOption<T>[]): Promise<T> {
|
||||
const modal = this.ngbModal.open(SelectorModalComponent)
|
||||
const instance: SelectorModalComponent<T> = modal.componentInstance
|
||||
instance.name = name
|
||||
instance.options = options
|
||||
return modal.result as Promise<T>
|
||||
}
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ function isNonStructuralObjectMember (v): boolean {
|
||||
|
||||
/** @hidden */
|
||||
export class ConfigProxy {
|
||||
constructor (real: Record<string, any>, defaults: Record<string, any>) {
|
||||
constructor (real: any, defaults: any) {
|
||||
for (const key in defaults) {
|
||||
if (isStructuralMember(defaults[key])) {
|
||||
if (!real[key]) {
|
||||
@@ -71,10 +71,8 @@ export class ConfigProxy {
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
||||
getValue (_key: string): any { }
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
||||
setValue (_key: string, _value: any) { }
|
||||
getValue (_key: string): any { } // eslint-disable-line @typescript-eslint/no-empty-function
|
||||
setValue (_key: string, _value: any) { } // eslint-disable-line @typescript-eslint/no-empty-function
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -102,7 +100,7 @@ export class ConfigService {
|
||||
get changed$ (): Observable<void> { return this.changed }
|
||||
|
||||
/** @hidden */
|
||||
private constructor (
|
||||
constructor (
|
||||
electron: ElectronService,
|
||||
private hostApp: HostAppService,
|
||||
@Inject(ConfigProvider) configProviders: ConfigProvider[],
|
||||
@@ -126,23 +124,8 @@ export class ConfigService {
|
||||
})
|
||||
}
|
||||
|
||||
getDefaults (): Record<string, any> {
|
||||
const cleanup = o => {
|
||||
if (o instanceof Array) {
|
||||
return o.map(cleanup)
|
||||
} else if (o instanceof Object) {
|
||||
const r = {}
|
||||
for (const k of Object.keys(o)) {
|
||||
if (k !== '__nonStructural') {
|
||||
r[k] = cleanup(o[k])
|
||||
}
|
||||
}
|
||||
return r
|
||||
} else {
|
||||
return o
|
||||
}
|
||||
}
|
||||
return cleanup(this.defaults)
|
||||
getDefaults () {
|
||||
return this.defaults
|
||||
}
|
||||
|
||||
load (): void {
|
||||
@@ -155,11 +138,9 @@ export class ConfigService {
|
||||
}
|
||||
|
||||
save (): void {
|
||||
// Scrub undefined values
|
||||
this._store = JSON.parse(JSON.stringify(this._store))
|
||||
fs.writeFileSync(this.path, yaml.safeDump(this._store), 'utf8')
|
||||
this.emitChange()
|
||||
this.hostApp.broadcastConfigChange(this.store)
|
||||
this.hostApp.broadcastConfigChange()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,11 +173,11 @@ export class ConfigService {
|
||||
enabledServices<T extends object> (services: T[]): T[] {
|
||||
if (!this.servicesCache) {
|
||||
this.servicesCache = {}
|
||||
const ngModule = window['rootModule'].ɵinj
|
||||
const ngModule = window['rootModule'].ngInjectorDef
|
||||
for (const imp of ngModule.imports) {
|
||||
const module = imp['ngModule'] || imp
|
||||
if (module.ɵinj?.providers) {
|
||||
this.servicesCache[module['pluginName']] = module.ɵinj.providers.map(provider => {
|
||||
if (module.ngInjectorDef && module.ngInjectorDef.providers) {
|
||||
this.servicesCache[module['pluginName']] = module.ngInjectorDef.providers.map(provider => {
|
||||
return provider['useClass'] || provider
|
||||
})
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ import { HostAppService, Bounds } from '../services/hostApp.service'
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DockingService {
|
||||
/** @hidden */
|
||||
private constructor (
|
||||
constructor (
|
||||
private electron: ElectronService,
|
||||
private config: ConfigService,
|
||||
private hostApp: HostAppService,
|
||||
@@ -15,7 +15,7 @@ export class DockingService {
|
||||
electron.screen.on('display-metrics-changed', () => this.repositionWindow())
|
||||
}
|
||||
|
||||
dock (): void {
|
||||
dock () {
|
||||
const dockSide = this.config.store.appearance.dock
|
||||
|
||||
if (dockSide === 'off') {
|
||||
@@ -53,25 +53,22 @@ export class DockingService {
|
||||
newBounds.y = display.bounds.y
|
||||
}
|
||||
|
||||
const alwaysOnTop = this.config.store.appearance.dockAlwaysOnTop
|
||||
|
||||
this.hostApp.setAlwaysOnTop(alwaysOnTop)
|
||||
this.hostApp.setAlwaysOnTop(true)
|
||||
setImmediate(() => {
|
||||
this.hostApp.setBounds(newBounds)
|
||||
})
|
||||
}
|
||||
|
||||
getCurrentScreen (): Electron.Display {
|
||||
getCurrentScreen () {
|
||||
return this.electron.screen.getDisplayNearestPoint(this.electron.screen.getCursorScreenPoint())
|
||||
}
|
||||
|
||||
getScreens (): Electron.Display[] {
|
||||
getScreens () {
|
||||
const primaryDisplayID = this.electron.screen.getPrimaryDisplay().id
|
||||
return this.electron.screen.getAllDisplays().sort((a, b) =>
|
||||
a.bounds.x === b.bounds.x ? a.bounds.y - b.bounds.y : a.bounds.x - b.bounds.x
|
||||
).map((display, index) => {
|
||||
).map((display,index) => {
|
||||
return {
|
||||
...display,
|
||||
id: display.id,
|
||||
name: display.id === primaryDisplayID ? 'Primary Display' : `Display ${index +1}`,
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ export class ElectronService {
|
||||
private electron: any
|
||||
|
||||
/** @hidden */
|
||||
private constructor () {
|
||||
constructor () {
|
||||
this.electron = require('electron')
|
||||
this.remote = this.electron.remote
|
||||
this.app = this.remote.app
|
||||
@@ -43,6 +43,15 @@ export class ElectronService {
|
||||
this.MenuItem = this.remote.MenuItem
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes OS focus from Terminus' window
|
||||
*/
|
||||
loseFocus () {
|
||||
if (process.platform === 'darwin') {
|
||||
this.remote.Menu.sendActionToFirstResponder('hide:')
|
||||
}
|
||||
}
|
||||
|
||||
async showMessageBox (
|
||||
browserWindow: Electron.BrowserWindow,
|
||||
options: Electron.MessageBoxOptions
|
||||
|
@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'
|
||||
import { ElectronService } from './electron.service'
|
||||
import { ConfigService } from './config.service'
|
||||
import * as mixpanel from 'mixpanel'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import * as uuidv4 from 'uuid/v4'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HomeBaseService {
|
||||
@@ -11,7 +11,7 @@ export class HomeBaseService {
|
||||
mixpanel: any
|
||||
|
||||
/** @hidden */
|
||||
private constructor (
|
||||
constructor (
|
||||
private electron: ElectronService,
|
||||
private config: ConfigService,
|
||||
) {
|
||||
@@ -22,29 +22,24 @@ export class HomeBaseService {
|
||||
}
|
||||
}
|
||||
|
||||
openGitHub (): void {
|
||||
openGitHub () {
|
||||
this.electron.shell.openExternal('https://github.com/eugeny/terminus')
|
||||
}
|
||||
|
||||
reportBug (): void {
|
||||
reportBug () {
|
||||
let body = `Version: ${this.appVersion}\n`
|
||||
body += `Platform: ${os.platform()} ${os.release()}\n`
|
||||
const label = {
|
||||
aix: 'OS: IBM AIX',
|
||||
android: 'OS: Android',
|
||||
darwin: 'OS: macOS',
|
||||
freebsd: 'OS: FreeBSD',
|
||||
windows: 'OS: Windows',
|
||||
linux: 'OS: Linux',
|
||||
openbsd: 'OS: OpenBSD',
|
||||
sunos: 'OS: Solaris',
|
||||
win32: 'OS: Windows',
|
||||
}[os.platform()]
|
||||
const plugins = (window as any).installedPlugins.filter(x => !x.isBuiltin).map(x => x.name)
|
||||
body += `Plugins: ${plugins.join(', ') || 'none'}\n\n`
|
||||
this.electron.shell.openExternal(`https://github.com/eugeny/terminus/issues/new?body=${encodeURIComponent(body)}&labels=${label}`)
|
||||
}
|
||||
|
||||
enableAnalytics (): void {
|
||||
enableAnalytics () {
|
||||
if (!window.localStorage.analyticsUserID) {
|
||||
window.localStorage.analyticsUserID = uuidv4()
|
||||
}
|
||||
@@ -56,7 +51,7 @@ export class HomeBaseService {
|
||||
this.mixpanel.track('launch', this.getAnalyticsProperties())
|
||||
}
|
||||
|
||||
getAnalyticsProperties (): Record<string, string> {
|
||||
getAnalyticsProperties () {
|
||||
return {
|
||||
distinct_id: window.localStorage.analyticsUserID, // eslint-disable-line @typescript-eslint/camelcase
|
||||
platform: process.platform,
|
||||
|
@@ -4,7 +4,6 @@ import { Observable, Subject } from 'rxjs'
|
||||
import { Injectable, NgZone, EventEmitter } from '@angular/core'
|
||||
import { ElectronService } from './electron.service'
|
||||
import { Logger, LogService } from './log.service'
|
||||
import { isWindowsBuild, WIN_BUILD_FLUENT_BG_MOVE_BUG_FIXED, WIN_BUILD_FLUENT_BG_SUPPORTED } from '../utils'
|
||||
|
||||
export enum Platform {
|
||||
Linux, macOS, Windows,
|
||||
@@ -40,7 +39,6 @@ export class HostAppService {
|
||||
private configChangeBroadcast = new Subject<void>()
|
||||
private windowCloseRequest = new Subject<void>()
|
||||
private windowMoved = new Subject<void>()
|
||||
private windowFocused = new Subject<void>()
|
||||
private displayMetricsChanged = new Subject<void>()
|
||||
private logger: Logger
|
||||
private windowId: number
|
||||
@@ -87,8 +85,6 @@ export class HostAppService {
|
||||
|
||||
get windowMoved$ (): Observable<void> { return this.windowMoved }
|
||||
|
||||
get windowFocused$ (): Observable<void> { return this.windowFocused }
|
||||
|
||||
get displayMetricsChanged$ (): Observable<void> { return this.displayMetricsChanged }
|
||||
|
||||
private constructor (
|
||||
@@ -132,10 +128,6 @@ export class HostAppService {
|
||||
this.zone.run(() => this.windowMoved.next())
|
||||
})
|
||||
|
||||
electron.ipcRenderer.on('host:window-focused', () => {
|
||||
this.zone.run(() => this.windowFocused.next())
|
||||
})
|
||||
|
||||
electron.ipcRenderer.on('host:display-metrics-changed', () => {
|
||||
this.zone.run(() => this.displayMetricsChanged.next())
|
||||
})
|
||||
@@ -165,60 +157,53 @@ export class HostAppService {
|
||||
electron.ipcRenderer.on('host:config-change', () => this.zone.run(() => {
|
||||
this.configChangeBroadcast.next()
|
||||
}))
|
||||
|
||||
if (
|
||||
isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED) &&
|
||||
!isWindowsBuild(WIN_BUILD_FLUENT_BG_MOVE_BUG_FIXED)
|
||||
) {
|
||||
electron.ipcRenderer.send('window-set-disable-vibrancy-while-dragging', true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current remote [[BrowserWindow]]
|
||||
*/
|
||||
getWindow (): Electron.BrowserWindow {
|
||||
getWindow () {
|
||||
return this.electron.BrowserWindow.fromId(this.windowId)
|
||||
}
|
||||
|
||||
newWindow (): void {
|
||||
newWindow () {
|
||||
this.electron.ipcRenderer.send('app:new-window')
|
||||
}
|
||||
|
||||
toggleFullscreen (): void {
|
||||
toggleFullscreen () {
|
||||
const window = this.getWindow()
|
||||
window.setFullScreen(!this.isFullScreen)
|
||||
}
|
||||
|
||||
openDevTools (): void {
|
||||
openDevTools () {
|
||||
this.getWindow().webContents.openDevTools({ mode: 'undocked' })
|
||||
}
|
||||
|
||||
focusWindow (): void {
|
||||
focusWindow () {
|
||||
this.electron.ipcRenderer.send('window-focus')
|
||||
}
|
||||
|
||||
minimize (): void {
|
||||
minimize () {
|
||||
this.electron.ipcRenderer.send('window-minimize')
|
||||
}
|
||||
|
||||
maximize (): void {
|
||||
maximize () {
|
||||
this.electron.ipcRenderer.send('window-maximize')
|
||||
}
|
||||
|
||||
unmaximize (): void {
|
||||
unmaximize () {
|
||||
this.electron.ipcRenderer.send('window-unmaximize')
|
||||
}
|
||||
|
||||
toggleMaximize (): void {
|
||||
toggleMaximize () {
|
||||
this.electron.ipcRenderer.send('window-toggle-maximize')
|
||||
}
|
||||
|
||||
setBounds (bounds: Bounds): void {
|
||||
setBounds (bounds: Bounds) {
|
||||
this.electron.ipcRenderer.send('window-set-bounds', bounds)
|
||||
}
|
||||
|
||||
setAlwaysOnTop (flag: boolean): void {
|
||||
setAlwaysOnTop (flag: boolean) {
|
||||
this.electron.ipcRenderer.send('window-set-always-on-top', flag)
|
||||
}
|
||||
|
||||
@@ -227,50 +212,48 @@ export class HostAppService {
|
||||
*
|
||||
* @param type `null`, or `fluent` when supported (Windowd only)
|
||||
*/
|
||||
setVibrancy (enable: boolean, type: string|null): void {
|
||||
if (!isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED)) {
|
||||
type = null
|
||||
}
|
||||
setVibrancy (enable: boolean, type: string) {
|
||||
document.body.classList.toggle('vibrant', enable)
|
||||
if (this.platform === Platform.macOS) {
|
||||
this.getWindow().setVibrancy(enable ? 'dark' : null as any) // electron issue 20269
|
||||
}
|
||||
if (this.platform === Platform.Windows) {
|
||||
this.electron.ipcRenderer.send('window-set-vibrancy', enable, type)
|
||||
}
|
||||
}
|
||||
|
||||
setTitle (title: string): void {
|
||||
setTitle (title: string) {
|
||||
this.electron.ipcRenderer.send('window-set-title', title)
|
||||
}
|
||||
|
||||
setTouchBar (touchBar: Electron.TouchBar): void {
|
||||
setTouchBar (touchBar: Electron.TouchBar) {
|
||||
this.getWindow().setTouchBar(touchBar)
|
||||
}
|
||||
|
||||
popupContextMenu (menuDefinition: Electron.MenuItemConstructorOptions[]): void {
|
||||
popupContextMenu (menuDefinition: Electron.MenuItemConstructorOptions[]) {
|
||||
this.electron.Menu.buildFromTemplate(menuDefinition).popup({})
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies other windows of config file changes
|
||||
*/
|
||||
broadcastConfigChange (configStore: {[k: string]: any}): void {
|
||||
this.electron.ipcRenderer.send('app:config-change', configStore)
|
||||
broadcastConfigChange () {
|
||||
this.electron.ipcRenderer.send('app:config-change')
|
||||
}
|
||||
|
||||
emitReady (): void {
|
||||
emitReady () {
|
||||
this.electron.ipcRenderer.send('app:ready')
|
||||
}
|
||||
|
||||
bringToFront (): void {
|
||||
bringToFront () {
|
||||
this.electron.ipcRenderer.send('window-bring-to-front')
|
||||
}
|
||||
|
||||
closeWindow (): void {
|
||||
closeWindow () {
|
||||
this.electron.ipcRenderer.send('window-close')
|
||||
}
|
||||
|
||||
registerGlobalHotkey (specs: string[]): void {
|
||||
this.electron.ipcRenderer.send('app:register-global-hotkey', specs)
|
||||
}
|
||||
|
||||
relaunch (): void {
|
||||
relaunch () {
|
||||
if (this.isPortable) {
|
||||
this.electron.app.relaunch({ execPath: process.env.PORTABLE_EXECUTABLE_FILE })
|
||||
} else {
|
||||
@@ -279,7 +262,7 @@ export class HostAppService {
|
||||
this.electron.app.exit()
|
||||
}
|
||||
|
||||
quit (): void {
|
||||
quit () {
|
||||
this.logger.info('Quitting')
|
||||
this.electron.app.quit()
|
||||
}
|
||||
|
@@ -1,10 +1,8 @@
|
||||
import { Injectable, Inject, NgZone, EventEmitter } from '@angular/core'
|
||||
import { Observable, Subject } from 'rxjs'
|
||||
import { HotkeyDescription, HotkeyProvider } from '../api/hotkeyProvider'
|
||||
import { stringifyKeySequence } from './hotkeys.util'
|
||||
import { ConfigService } from './config.service'
|
||||
import { ElectronService } from './electron.service'
|
||||
import { HostAppService } from './hostApp.service'
|
||||
import { ConfigService } from '../services/config.service'
|
||||
import { ElectronService } from '../services/electron.service'
|
||||
|
||||
export interface PartialHotkeyMatch {
|
||||
id: string
|
||||
@@ -22,23 +20,14 @@ interface EventBufferEntry {
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HotkeysService {
|
||||
key = new EventEmitter<KeyboardEvent>()
|
||||
|
||||
/** @hidden */
|
||||
matchedHotkey = new EventEmitter<string>()
|
||||
|
||||
/**
|
||||
* Fired for each recognized hotkey
|
||||
*/
|
||||
get hotkey$ (): Observable<string> { return this._hotkey }
|
||||
|
||||
private _hotkey = new Subject<string>()
|
||||
globalHotkey = new EventEmitter<void>()
|
||||
private currentKeystrokes: EventBufferEntry[] = []
|
||||
private disabledLevel = 0
|
||||
private hotkeyDescriptions: HotkeyDescription[] = []
|
||||
|
||||
private constructor (
|
||||
private zone: NgZone,
|
||||
private hostApp: HostAppService,
|
||||
private electron: ElectronService,
|
||||
private config: ConfigService,
|
||||
@Inject(HotkeyProvider) private hotkeyProviders: HotkeyProvider[],
|
||||
@@ -60,9 +49,6 @@ export class HotkeysService {
|
||||
this.getHotkeyDescriptions().then(hotkeys => {
|
||||
this.hotkeyDescriptions = hotkeys
|
||||
})
|
||||
|
||||
// deprecated
|
||||
this.hotkey$.subscribe(h => this.matchedHotkey.emit(h))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,7 +57,7 @@ export class HotkeysService {
|
||||
* @param name DOM event name
|
||||
* @param nativeEvent event object
|
||||
*/
|
||||
pushKeystroke (name: string, nativeEvent: KeyboardEvent): void {
|
||||
pushKeystroke (name: string, nativeEvent: KeyboardEvent) {
|
||||
(nativeEvent as any).event = name
|
||||
this.currentKeystrokes.push({ event: nativeEvent, time: performance.now() })
|
||||
}
|
||||
@@ -79,26 +65,26 @@ export class HotkeysService {
|
||||
/**
|
||||
* Check the buffer for new complete keystrokes
|
||||
*/
|
||||
processKeystrokes (): void {
|
||||
processKeystrokes () {
|
||||
if (this.isEnabled()) {
|
||||
this.zone.run(() => {
|
||||
const matched = this.getCurrentFullyMatchedHotkey()
|
||||
if (matched) {
|
||||
console.log('Matched hotkey', matched)
|
||||
this._hotkey.next(matched)
|
||||
this.matchedHotkey.emit(matched)
|
||||
this.clearCurrentKeystrokes()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
emitKeyEvent (nativeEvent: KeyboardEvent): void {
|
||||
emitKeyEvent (nativeEvent: KeyboardEvent) {
|
||||
this.zone.run(() => {
|
||||
this.key.emit(nativeEvent)
|
||||
})
|
||||
}
|
||||
|
||||
clearCurrentKeystrokes (): void {
|
||||
clearCurrentKeystrokes () {
|
||||
this.currentKeystrokes = []
|
||||
}
|
||||
|
||||
@@ -156,15 +142,15 @@ export class HotkeysService {
|
||||
return this.hotkeyDescriptions.filter((x) => x.id === id)[0]
|
||||
}
|
||||
|
||||
enable (): void {
|
||||
enable () {
|
||||
this.disabledLevel--
|
||||
}
|
||||
|
||||
disable (): void {
|
||||
disable () {
|
||||
this.disabledLevel++
|
||||
}
|
||||
|
||||
isEnabled (): boolean {
|
||||
isEnabled () {
|
||||
return this.disabledLevel === 0
|
||||
}
|
||||
|
||||
@@ -183,23 +169,21 @@ export class HotkeysService {
|
||||
if (typeof value === 'string') {
|
||||
value = [value]
|
||||
}
|
||||
const specs: string[] = []
|
||||
value.forEach((item: string | string[]) => {
|
||||
item = typeof item === 'string' ? [item] : item
|
||||
|
||||
try {
|
||||
let electronKeySpec = item[0]
|
||||
electronKeySpec = electronKeySpec.replace('Meta', 'Super')
|
||||
electronKeySpec = electronKeySpec.replace('⌘', 'Command')
|
||||
electronKeySpec = electronKeySpec.replace('⌥', 'Alt')
|
||||
electronKeySpec = electronKeySpec.replace(/-/g, '+')
|
||||
specs.push(electronKeySpec)
|
||||
this.electron.globalShortcut.register(electronKeySpec, () => {
|
||||
this.globalHotkey.emit()
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Could not register the global hotkey:', err)
|
||||
}
|
||||
})
|
||||
|
||||
this.hostApp.registerGlobalHotkey(specs)
|
||||
}
|
||||
|
||||
private getHotkeysConfig () {
|
||||
@@ -219,9 +203,6 @@ export class HotkeysService {
|
||||
if (typeof value === 'string') {
|
||||
value = [value]
|
||||
}
|
||||
if (!(value instanceof Array)) {
|
||||
continue
|
||||
}
|
||||
if (value) {
|
||||
value = value.map((item: string | string[]) => typeof item === 'string' ? [item] : item)
|
||||
keys[key] = value
|
||||
|
@@ -32,27 +32,27 @@ export class Logger {
|
||||
private name: string,
|
||||
) {}
|
||||
|
||||
debug (...args: any[]): void {
|
||||
debug (...args: any[]) {
|
||||
this.doLog('debug', ...args)
|
||||
}
|
||||
|
||||
info (...args: any[]): void {
|
||||
info (...args: any[]) {
|
||||
this.doLog('info', ...args)
|
||||
}
|
||||
|
||||
warn (...args: any[]): void {
|
||||
warn (...args: any[]) {
|
||||
this.doLog('warn', ...args)
|
||||
}
|
||||
|
||||
error (...args: any[]): void {
|
||||
error (...args: any[]) {
|
||||
this.doLog('error', ...args)
|
||||
}
|
||||
|
||||
log (...args: any[]): void {
|
||||
log (...args: any[]) {
|
||||
this.doLog('log', ...args)
|
||||
}
|
||||
|
||||
private doLog (level: string, ...args: any[]): void {
|
||||
private doLog (level: string, ...args: any[]) {
|
||||
console[level](`%c[${this.name}]`, 'color: #aaa', ...args)
|
||||
if (this.winstonLogger) {
|
||||
this.winstonLogger[level](...args)
|
||||
@@ -65,7 +65,7 @@ export class LogService {
|
||||
private log: any
|
||||
|
||||
/** @hidden */
|
||||
private constructor (electron: ElectronService) {
|
||||
constructor (electron: ElectronService) {
|
||||
this.log = initializeWinston(electron)
|
||||
}
|
||||
|
||||
|
@@ -8,7 +8,7 @@ import { HostAppService, Platform } from './hostApp.service'
|
||||
/* eslint-disable block-scoped-var */
|
||||
|
||||
try {
|
||||
var wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires, no-var
|
||||
var wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires
|
||||
} catch (_) { }
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -18,22 +18,15 @@ export class ShellIntegrationService {
|
||||
private automatorWorkflowsDestination: string
|
||||
private registryKeys = [
|
||||
{
|
||||
path: 'Software\\Classes\\Directory\\Background\\shell\\Terminus',
|
||||
value: 'Open Terminus here',
|
||||
path: 'Software\\Classes\\Directory\\Background\\shell\\Open Terminus here',
|
||||
command: 'open "%V"',
|
||||
},
|
||||
{
|
||||
path: 'SOFTWARE\\Classes\\Directory\\shell\\Terminus',
|
||||
value: 'Open Terminus here',
|
||||
command: 'open "%V"',
|
||||
},
|
||||
{
|
||||
path: 'Software\\Classes\\*\\shell\\Terminus',
|
||||
value: 'Paste path into Terminus',
|
||||
path: 'Software\\Classes\\*\\shell\\Paste path into Terminus',
|
||||
command: 'paste "%V"',
|
||||
},
|
||||
]
|
||||
private constructor (
|
||||
constructor (
|
||||
private electron: ElectronService,
|
||||
private hostApp: HostAppService,
|
||||
) {
|
||||
@@ -58,7 +51,7 @@ export class ShellIntegrationService {
|
||||
return true
|
||||
}
|
||||
|
||||
async install (): Promise<void> {
|
||||
async install () {
|
||||
const exe: string = process.env.PORTABLE_EXECUTABLE_FILE || this.electron.app.getPath('exe')
|
||||
if (this.hostApp.platform === Platform.macOS) {
|
||||
for (const wf of this.automatorWorkflows) {
|
||||
@@ -68,21 +61,13 @@ export class ShellIntegrationService {
|
||||
for (const registryKey of this.registryKeys) {
|
||||
wnr.createRegistryKey(wnr.HK.CU, registryKey.path)
|
||||
wnr.createRegistryKey(wnr.HK.CU, registryKey.path + '\\command')
|
||||
wnr.setRegistryValue(wnr.HK.CU, registryKey.path, '', wnr.REG.SZ, registryKey.value)
|
||||
wnr.setRegistryValue(wnr.HK.CU, registryKey.path, 'Icon', wnr.REG.SZ, exe)
|
||||
wnr.setRegistryValue(wnr.HK.CU, registryKey.path + '\\command', '', wnr.REG.SZ, exe + ' ' + registryKey.command)
|
||||
}
|
||||
|
||||
if(wnr.getRegistryKey(wnr.HK.CU, 'Software\\Classes\\Directory\\Background\\shell\\Open Terminus here')) {
|
||||
wnr.deleteRegistryKey(wnr.HK.CU, 'Software\\Classes\\Directory\\Background\\shell\\Open Terminus here')
|
||||
}
|
||||
if(wnr.getRegistryKey(wnr.HK.CU, 'Software\\Classes\\*\\shell\\Paste path into Terminus')) {
|
||||
wnr.deleteRegistryKey(wnr.HK.CU, 'Software\\Classes\\*\\shell\\Paste path into Terminus')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async remove (): Promise<void> {
|
||||
async remove () {
|
||||
if (this.hostApp.platform === Platform.macOS) {
|
||||
for (const wf of this.automatorWorkflows) {
|
||||
await exec(`rm -rf "${this.automatorWorkflowsDestination}/${wf}"`)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
import { TabRecoveryProvider, RecoveredTab, RecoveryToken } from '../api/tabRecovery'
|
||||
import { TabRecoveryProvider, RecoveredTab } from '../api/tabRecovery'
|
||||
import { BaseTabComponent } from '../components/baseTab.component'
|
||||
import { Logger, LogService } from '../services/log.service'
|
||||
import { ConfigService } from '../services/config.service'
|
||||
@@ -8,9 +8,8 @@ import { ConfigService } from '../services/config.service'
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TabRecoveryService {
|
||||
logger: Logger
|
||||
enabled = false
|
||||
|
||||
private constructor (
|
||||
constructor (
|
||||
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[],
|
||||
private config: ConfigService,
|
||||
log: LogService
|
||||
@@ -18,41 +17,21 @@ export class TabRecoveryService {
|
||||
this.logger = log.create('tabRecovery')
|
||||
}
|
||||
|
||||
async saveTabs (tabs: BaseTabComponent[]): Promise<void> {
|
||||
if (!this.enabled) {
|
||||
return
|
||||
}
|
||||
async saveTabs (tabs: BaseTabComponent[]) {
|
||||
window.localStorage.tabsRecovery = JSON.stringify(
|
||||
await Promise.all(
|
||||
tabs
|
||||
.map(tab => {
|
||||
let token = tab.getRecoveryToken()
|
||||
if (token) {
|
||||
token = token.then(r => {
|
||||
if (r) {
|
||||
r.tabTitle = tab.title
|
||||
if (tab.color) {
|
||||
r.tabColor = tab.color
|
||||
}
|
||||
}
|
||||
return r
|
||||
})
|
||||
}
|
||||
return token
|
||||
})
|
||||
.map(tab => tab.getRecoveryToken())
|
||||
.filter(token => !!token)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
async recoverTab (token: RecoveryToken): Promise<RecoveredTab|null> {
|
||||
async recoverTab (token: any): Promise<RecoveredTab|null> {
|
||||
for (const provider of this.config.enabledServices(this.tabRecoveryProviders)) {
|
||||
try {
|
||||
const tab = await provider.recover(token)
|
||||
if (tab !== null) {
|
||||
tab.options = tab.options || {}
|
||||
tab.options.color = token.tabColor || null
|
||||
tab.options.title = token.tabTitle || ''
|
||||
if (tab) {
|
||||
return tab
|
||||
}
|
||||
} catch (error) {
|
||||
|
@@ -8,7 +8,7 @@ export type TabComponentType = new (...args: any[]) => BaseTabComponent
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TabsService {
|
||||
/** @hidden */
|
||||
private constructor (
|
||||
constructor (
|
||||
private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private injector: Injector,
|
||||
private tabRecovery: TabRecoveryService,
|
||||
@@ -17,7 +17,7 @@ export class TabsService {
|
||||
/**
|
||||
* Instantiates a tab component and assigns given inputs
|
||||
*/
|
||||
create (type: TabComponentType, inputs?: Record<string, any>): BaseTabComponent {
|
||||
create (type: TabComponentType, inputs?: any): BaseTabComponent {
|
||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(type)
|
||||
const componentRef = componentFactory.create(this.injector)
|
||||
const tab = componentRef.instance
|
||||
|
@@ -7,7 +7,7 @@ export class ThemesService {
|
||||
private styleElement: HTMLElement|null = null
|
||||
|
||||
/** @hidden */
|
||||
private constructor (
|
||||
constructor (
|
||||
private config: ConfigService,
|
||||
@Inject(Theme) private themes: Theme[],
|
||||
) {
|
||||
|
@@ -14,7 +14,7 @@ export class TouchbarService {
|
||||
private tabSegments: SegmentedControlSegment[] = []
|
||||
private nsImageCache: {[id: string]: Electron.NativeImage} = {}
|
||||
|
||||
private constructor (
|
||||
constructor (
|
||||
private app: AppService,
|
||||
private hostApp: HostAppService,
|
||||
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
||||
@@ -48,7 +48,7 @@ export class TouchbarService {
|
||||
})
|
||||
}
|
||||
|
||||
updateTabs (): void {
|
||||
updateTabs () {
|
||||
this.tabSegments = this.app.tabs.map(tab => ({
|
||||
label: this.shortenTitle(tab.title),
|
||||
}))
|
||||
@@ -56,7 +56,7 @@ export class TouchbarService {
|
||||
this.tabsSegmentedControl.selectedIndex = this.app.tabs.indexOf(this.app.activeTab)
|
||||
}
|
||||
|
||||
update (): void {
|
||||
update () {
|
||||
if (this.hostApp.platform !== Platform.macOS) {
|
||||
return
|
||||
}
|
||||
|
@@ -1,4 +1,8 @@
|
||||
import axios from 'axios'
|
||||
import * as fs from 'fs'
|
||||
import os from 'os'
|
||||
|
||||
import { spawn } from 'mz/child_process'
|
||||
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Logger, LogService } from './log.service'
|
||||
@@ -14,38 +18,35 @@ export class UpdaterService {
|
||||
private downloaded: Promise<boolean>
|
||||
private electronUpdaterAvailable = true
|
||||
private updateURL: string
|
||||
private autoUpdater
|
||||
|
||||
private constructor (
|
||||
constructor (
|
||||
log: LogService,
|
||||
private electron: ElectronService,
|
||||
private config: ConfigService,
|
||||
config: ConfigService,
|
||||
) {
|
||||
this.logger = log.create('updater')
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
this.electronUpdaterAvailable = false
|
||||
return
|
||||
}
|
||||
this.autoUpdater = electron.remote.require('electron-updater').autoUpdater
|
||||
|
||||
electron.autoUpdater.on('update-available', () => {
|
||||
this.autoUpdater.autoInstallOnAppQuit = !!config.store.enableAutomaticUpdates
|
||||
|
||||
this.autoUpdater.on('update-available', () => {
|
||||
this.logger.info('Update available')
|
||||
})
|
||||
|
||||
electron.autoUpdater.once('update-not-available', () => {
|
||||
this.autoUpdater.once('update-not-available', () => {
|
||||
this.logger.info('No updates')
|
||||
})
|
||||
|
||||
this.downloaded = new Promise<boolean>(resolve => {
|
||||
electron.autoUpdater.once('update-downloaded', () => resolve(true))
|
||||
this.autoUpdater.once('update-downloaded', () => resolve(true))
|
||||
})
|
||||
|
||||
if (config.store.enableAutomaticUpdates && this.electronUpdaterAvailable && !process.env.TERMINUS_DEV) {
|
||||
this.logger.debug('Checking for updates')
|
||||
|
||||
if (this.electronUpdaterAvailable && !process.env.TERMINUS_DEV) {
|
||||
try {
|
||||
electron.autoUpdater.setFeedURL({
|
||||
url: `https://update.electronjs.org/eugeny/terminus/${process.platform}-${process.arch}/${electron.app.getVersion()}`,
|
||||
})
|
||||
electron.autoUpdater.checkForUpdates()
|
||||
this.autoUpdater.checkForUpdates()
|
||||
} catch (e) {
|
||||
this.electronUpdaterAvailable = false
|
||||
this.logger.info('Electron updater unavailable, falling back', e)
|
||||
@@ -54,9 +55,6 @@ export class UpdaterService {
|
||||
}
|
||||
|
||||
async check (): Promise<boolean> {
|
||||
if (!this.config.store.enableAutomaticUpdates) {
|
||||
return false
|
||||
}
|
||||
if (!this.electronUpdaterAvailable) {
|
||||
this.logger.debug('Checking for updates through fallback method.')
|
||||
const response = await axios.get(UPDATES_URL)
|
||||
@@ -73,12 +71,25 @@ export class UpdaterService {
|
||||
return this.downloaded
|
||||
}
|
||||
|
||||
async update (): Promise<void> {
|
||||
async update () {
|
||||
if (!this.electronUpdaterAvailable) {
|
||||
this.electron.shell.openExternal(this.updateURL)
|
||||
} else {
|
||||
if (process.platform === 'win32') {
|
||||
let downloadpath = await this.autoUpdater.downloadUpdate()
|
||||
fs.exists(downloadpath[0], (exists) => {
|
||||
if (exists) {
|
||||
fs.copyFile(downloadpath[0], os.tmpdir() + 'terminus-installer-temp.exe', (err) => {
|
||||
if (!err) {
|
||||
spawn(os.tmpdir() + 'terminus-installer-temp.exe', ['--force-run'], { detached: true, stdio: 'ignore' })
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await this.downloaded
|
||||
this.electron.autoUpdater.quitAndInstall()
|
||||
this.autoUpdater.quitAndInstall(false, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,13 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Injectable, NgZone } from '@angular/core'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { AppService } from './services/app.service'
|
||||
import { BaseTabComponent } from './components/baseTab.component'
|
||||
import { TabHeaderComponent } from './components/tabHeader.component'
|
||||
import { SplitTabComponent, SplitDirection } from './components/splitTab.component'
|
||||
import { TabContextMenuItemProvider } from './api/tabContextMenuProvider'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
export class TabManagementContextMenu extends TabContextMenuItemProvider {
|
||||
weight = 99
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CloseContextMenu extends TabContextMenuItemProvider {
|
||||
weight = -5
|
||||
|
||||
constructor (
|
||||
private app: AppService,
|
||||
@@ -19,22 +16,14 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
|
||||
super()
|
||||
}
|
||||
|
||||
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||
let items: Electron.MenuItemConstructorOptions[] = [
|
||||
async getItems (tab: BaseTabComponent): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||
return [
|
||||
{
|
||||
label: 'Close',
|
||||
click: () => this.zone.run(() => {
|
||||
if (this.app.tabs.includes(tab)) {
|
||||
this.app.closeTab(tab, true)
|
||||
} else {
|
||||
tab.destroy()
|
||||
}
|
||||
}),
|
||||
},
|
||||
]
|
||||
if (tabHeader) {
|
||||
items = [
|
||||
...items,
|
||||
{
|
||||
label: 'Close other tabs',
|
||||
click: () => this.zone.run(() => {
|
||||
@@ -60,26 +49,6 @@ export class TabManagementContextMenu 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +63,7 @@ const COLORS = [
|
||||
]
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
||||
weight = -1
|
||||
|
||||
@@ -106,10 +75,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
||||
}
|
||||
|
||||
async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||
let items: Electron.MenuItemConstructorOptions[] = []
|
||||
if (tabHeader) {
|
||||
items = [
|
||||
...items,
|
||||
return [
|
||||
{
|
||||
label: 'Rename',
|
||||
click: () => this.zone.run(() => tabHeader?.showRenameTabModal()),
|
||||
@@ -120,7 +86,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
||||
},
|
||||
{
|
||||
label: 'Color',
|
||||
sublabel: COLORS.find(x => x.value === tab.color)?.name,
|
||||
sublabel: COLORS.find(x => x.value === tab.color)!.name,
|
||||
submenu: COLORS.map(color => ({
|
||||
label: color.name,
|
||||
type: 'radio',
|
||||
@@ -132,12 +98,10 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
||||
},
|
||||
]
|
||||
}
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
|
||||
constructor (
|
||||
private app: AppService,
|
||||
@@ -148,61 +112,36 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
|
||||
|
||||
async getItems (tab: BaseTabComponent): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||
const process = await tab.getCurrentProcess()
|
||||
let items: Electron.MenuItemConstructorOptions[] = []
|
||||
|
||||
const extTab: (BaseTabComponent & { __completionNotificationEnabled?: boolean, __outputNotificationSubscription?: Subscription|null }) = tab
|
||||
|
||||
if (process) {
|
||||
items.push({
|
||||
return [
|
||||
{
|
||||
id: 'process-name',
|
||||
enabled: false,
|
||||
label: 'Current process: ' + process.name,
|
||||
})
|
||||
items.push({
|
||||
},
|
||||
{
|
||||
label: 'Notify when done',
|
||||
type: 'checkbox',
|
||||
checked: extTab.__completionNotificationEnabled,
|
||||
checked: (tab as any).__completionNotificationEnabled,
|
||||
click: () => this.zone.run(() => {
|
||||
extTab.__completionNotificationEnabled = !extTab.__completionNotificationEnabled
|
||||
(tab as any).__completionNotificationEnabled = !(tab as any).__completionNotificationEnabled
|
||||
|
||||
if (extTab.__completionNotificationEnabled) {
|
||||
if ((tab as any).__completionNotificationEnabled) {
|
||||
this.app.observeTabCompletion(tab).subscribe(() => {
|
||||
new Notification('Process completed', {
|
||||
body: process.name,
|
||||
}).addEventListener('click', () => {
|
||||
this.app.selectTab(tab)
|
||||
})
|
||||
extTab.__completionNotificationEnabled = false
|
||||
;(tab as any).__completionNotificationEnabled = false
|
||||
})
|
||||
} else {
|
||||
this.app.stopObservingTabCompletion(tab)
|
||||
}
|
||||
}),
|
||||
})
|
||||
},
|
||||
]
|
||||
}
|
||||
items.push({
|
||||
label: 'Notify on activity',
|
||||
type: 'checkbox',
|
||||
checked: !!extTab.__outputNotificationSubscription,
|
||||
click: () => this.zone.run(() => {
|
||||
if (extTab.__outputNotificationSubscription) {
|
||||
extTab.__outputNotificationSubscription.unsubscribe()
|
||||
extTab.__outputNotificationSubscription = null
|
||||
} else {
|
||||
extTab.__outputNotificationSubscription = tab.activity$.subscribe(active => {
|
||||
if (extTab.__outputNotificationSubscription && active) {
|
||||
extTab.__outputNotificationSubscription.unsubscribe()
|
||||
extTab.__outputNotificationSubscription = null
|
||||
new Notification('Tab activity', {
|
||||
body: tab.title,
|
||||
}).addEventListener('click', () => {
|
||||
this.app.selectTab(tab)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
})
|
||||
return items
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
@@ -16,12 +16,4 @@ app-root {
|
||||
terminaltab .content {
|
||||
margin: 5px !important;
|
||||
}
|
||||
|
||||
ssh-tab .content {
|
||||
margin: 5px !important;
|
||||
}
|
||||
|
||||
serial-tab .content {
|
||||
margin: 5px !important;
|
||||
}
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ body {
|
||||
background: $body-bg;
|
||||
|
||||
&.vibrant {
|
||||
background: rgba(0,0,0,.65);
|
||||
background: rgba(0,0,0,.4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ ngb-tabset .tab-content {
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
transition: 0.0625s background;
|
||||
transition: 0.25s background;
|
||||
|
||||
i + * {
|
||||
margin-left: 10px;
|
||||
@@ -262,29 +262,6 @@ 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 {
|
||||
color: $blue;
|
||||
}
|
||||
@@ -415,7 +392,3 @@ search-panel {
|
||||
border-color: $nav-tabs-link-active-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
border-color: $list-group-border-color;
|
||||
}
|
||||
|
@@ -142,7 +142,7 @@ $nav-tabs-link-active-border-color: #eee;
|
||||
$navbar-padding-y: 0;
|
||||
$navbar-padding-x: 0;
|
||||
|
||||
$dropdown-bg: $content-bg-solid;
|
||||
$dropdown-bg: $table-bg;
|
||||
$dropdown-color: $body-color;
|
||||
$dropdown-border-width: 1px;
|
||||
$dropdown-box-shadow: 0 .5rem 1rem rgba($black,.175);
|
||||
|
@@ -2,6 +2,9 @@
|
||||
"extends": "../tsconfig.json",
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src"
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
"*": ["../../app/node_modules/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
const path = require('path')
|
||||
const { AngularCompilerPlugin } = require('@ngtools/webpack')
|
||||
|
||||
module.exports = {
|
||||
target: 'node',
|
||||
@@ -23,22 +24,26 @@ module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: {
|
||||
loader: 'awesome-typescript-loader',
|
||||
options: {
|
||||
configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||
typeRoots: [
|
||||
path.resolve(__dirname, 'node_modules/@types'),
|
||||
path.resolve(__dirname, '../node_modules/@types'),
|
||||
],
|
||||
paths: {
|
||||
"terminus-*": [path.resolve(__dirname, '../terminus-*')],
|
||||
"*": [path.resolve(__dirname, '../app/node_modules/*')],
|
||||
},
|
||||
},
|
||||
},
|
||||
test: /(?:\.ngfactory\.js|\.ngfactory|\.ngstyle\.js|\.ts)$/,
|
||||
loader: '@ngtools/webpack',
|
||||
},
|
||||
// {
|
||||
// test: /\.ts$/,
|
||||
// use: {
|
||||
// loader: 'awesome-typescript-loader',
|
||||
// options: {
|
||||
// configFileName: path.resolve(__dirname, 'tsconfig.json'),
|
||||
// typeRoots: [
|
||||
// path.resolve(__dirname, 'node_modules/@types'),
|
||||
// path.resolve(__dirname, '../node_modules/@types'),
|
||||
// ],
|
||||
// paths: {
|
||||
// "terminus-*": [path.resolve(__dirname, '../terminus-*')],
|
||||
// "*": [path.resolve(__dirname, '../app/node_modules/*')],
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
|
||||
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
|
||||
{ test: /\.css$/, use: ['to-string-loader', 'css-loader'], include: /component\.css/ },
|
||||
@@ -57,4 +62,12 @@ module.exports = {
|
||||
/^@angular/,
|
||||
/^@ng-bootstrap/,
|
||||
],
|
||||
plugins: [
|
||||
new AngularCompilerPlugin({
|
||||
tsConfigPath: path.resolve(__dirname, 'tsconfig.json'),
|
||||
entryModule: './terminus-core/src/index#AppModule',
|
||||
sourceMap: true,
|
||||
directTemplateLoading: true,
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user