mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-15 05:08:41 -09:00
Compare commits
No commits in common. "canary" and "v4.0.0-canary.2" have entirely different histories.
canary
...
v4.0.0-can
137 changed files with 6968 additions and 157648 deletions
|
|
@ -3,9 +3,7 @@
|
||||||
"react",
|
"react",
|
||||||
"prettier",
|
"prettier",
|
||||||
"@typescript-eslint",
|
"@typescript-eslint",
|
||||||
"eslint-comments",
|
"eslint-comments"
|
||||||
"lodash",
|
|
||||||
"import"
|
|
||||||
],
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
|
|
@ -34,11 +32,7 @@
|
||||||
"settings": {
|
"settings": {
|
||||||
"react": {
|
"react": {
|
||||||
"version": "detect"
|
"version": "detect"
|
||||||
},
|
}
|
||||||
"import/resolver": {
|
|
||||||
"typescript": {}
|
|
||||||
},
|
|
||||||
"import/internal-regex": "^(electron|react)$"
|
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"func-names": [
|
"func-names": [
|
||||||
|
|
@ -65,16 +59,7 @@
|
||||||
"bracketSameLine": false
|
"bracketSameLine": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"eslint-comments/no-unused-disable": "error",
|
"eslint-comments/no-unused-disable": "error"
|
||||||
"react/no-unknown-property":[
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"ignore": [
|
|
||||||
"jsx",
|
|
||||||
"global"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
|
|
@ -98,33 +83,7 @@
|
||||||
"@typescript-eslint/no-shadow": ["error"],
|
"@typescript-eslint/no-shadow": ["error"],
|
||||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
"@typescript-eslint/restrict-template-expressions": "off",
|
"@typescript-eslint/restrict-template-expressions": "off"
|
||||||
"@typescript-eslint/consistent-type-imports": [ "error", { "disallowTypeAnnotations": false } ],
|
|
||||||
"lodash/prop-shorthand": [ "error", "always" ],
|
|
||||||
"lodash/import-scope": [ "error", "method" ],
|
|
||||||
"lodash/collection-return": "error",
|
|
||||||
"lodash/collection-method-value": "error",
|
|
||||||
"import/no-extraneous-dependencies": "error",
|
|
||||||
"import/no-anonymous-default-export": "error",
|
|
||||||
"import/order": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"groups": [
|
|
||||||
"builtin",
|
|
||||||
"internal",
|
|
||||||
"external",
|
|
||||||
"parent",
|
|
||||||
"sibling",
|
|
||||||
"index"
|
|
||||||
],
|
|
||||||
"newlines-between": "always",
|
|
||||||
"alphabetize": {
|
|
||||||
"order": "asc",
|
|
||||||
"orderImportKind": "desc",
|
|
||||||
"caseInsensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
137
.github/actions/build-linux-arm/action.yml
vendored
137
.github/actions/build-linux-arm/action.yml
vendored
|
|
@ -1,137 +0,0 @@
|
||||||
name: 'Build Linux ARM'
|
|
||||||
description: 'Cross-compiles Hyper app for ARMv7l and ARM64 using arm-runner'
|
|
||||||
inputs:
|
|
||||||
node-version:
|
|
||||||
description: 'Node.js version to use'
|
|
||||||
required: true
|
|
||||||
matrix-name:
|
|
||||||
description: 'Matrix name (arch)'
|
|
||||||
required: true
|
|
||||||
matrix-cpu:
|
|
||||||
description: 'CPU architecture'
|
|
||||||
required: true
|
|
||||||
matrix-image:
|
|
||||||
description: 'Base OS image for ARM emulation'
|
|
||||||
required: true
|
|
||||||
upload-artifact:
|
|
||||||
description: 'Whether to upload artifacts'
|
|
||||||
required: false
|
|
||||||
default: 'true'
|
|
||||||
runs:
|
|
||||||
using: 'composite'
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Use Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: ${{ inputs.node-version }}
|
|
||||||
|
|
||||||
- name: Set up Python 3.10
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.10'
|
|
||||||
|
|
||||||
- name: Fix node-gyp and Python
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
python3 -m pip install packaging setuptools
|
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
|
||||||
shell: bash
|
|
||||||
id: yarn-cache-dir-path
|
|
||||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock', 'app/yarn.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-yarn-
|
|
||||||
|
|
||||||
- name: Delete old electron headers
|
|
||||||
# These can sometimes be present on a freshly-provisioned github runner, so remove them
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
rm -rf ~/.electron-gyp || true
|
|
||||||
|
|
||||||
- name: Install dependencies and tools
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
yarn install
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install libarchive-tools
|
|
||||||
|
|
||||||
- name: Rebuild node-pty and all native modules for ARM
|
|
||||||
uses: pguyot/arm-runner-action@v2.6.5
|
|
||||||
with:
|
|
||||||
image_additional_mb: 2000
|
|
||||||
base_image: ${{ inputs.matrix-image }}
|
|
||||||
cpu: ${{ inputs.matrix-cpu }}
|
|
||||||
shell: bash
|
|
||||||
copy_artifact_path: target
|
|
||||||
copy_artifact_dest: target
|
|
||||||
commands: |
|
|
||||||
# Install Python distutils in the ARM container
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y python3-pip python3-setuptools python3-dev
|
|
||||||
pip3 install setuptools --break-system-packages || pip3 install setuptools
|
|
||||||
|
|
||||||
cd target
|
|
||||||
# TODO upgrade node to 20.11.0 to match NODE_VERSION
|
|
||||||
wget https://nodejs.org/dist/v18.16.0/node-v18.16.0-linux-${{ inputs.matrix-name }}.tar.xz
|
|
||||||
tar -xJf node-v18.16.0-linux-${{ inputs.matrix-name }}.tar.xz
|
|
||||||
sudo cp node-v18.16.0-linux-${{ inputs.matrix-name }}/* /usr/local/ -R
|
|
||||||
rm node-v18.16.0-linux-${{ inputs.matrix-name }}.tar.xz
|
|
||||||
|
|
||||||
cd ..
|
|
||||||
npm run rebuild-node-pty
|
|
||||||
|
|
||||||
cd target
|
|
||||||
npx electron-rebuild
|
|
||||||
|
|
||||||
- name: Compile
|
|
||||||
shell: bash
|
|
||||||
run: yarn run build
|
|
||||||
|
|
||||||
- name: Chown rebuilt node_modules
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sudo chown -R $USER:$USER target/node_modules
|
|
||||||
|
|
||||||
- name: Prepare v8 snapshot (only armv7l)
|
|
||||||
if: ${{ inputs.matrix-name == 'armv7l' }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sudo dpkg --add-architecture i386
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y libglib2.0-0:i386 libexpat1:i386 libgcc-s1:i386
|
|
||||||
npm_config_arch=armv7l yarn run v8-snapshot:arch
|
|
||||||
|
|
||||||
- name: Build final Electron App for ARM
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
yarn run electron-builder -p never -l deb rpm AppImage pacman --${{ inputs.matrix-name }} -c electron-builder-linux-ci.json
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ env.GH_TOKEN }}
|
|
||||||
|
|
||||||
- name: Archive Build Artifacts
|
|
||||||
if: ${{ inputs.upload-artifact == 'true' }}
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: hyper-linux-${{ inputs.matrix-name }}
|
|
||||||
path: |
|
|
||||||
dist/*.snap
|
|
||||||
dist/*.AppImage
|
|
||||||
dist/*.deb
|
|
||||||
dist/*.rpm
|
|
||||||
dist/*.pacman
|
|
||||||
|
|
||||||
# - name: Run E2E Tests on Linux
|
|
||||||
# if: runner.os == 'Linux'
|
|
||||||
# uses: GabrielBB/xvfb-action@v1.7
|
|
||||||
# with:
|
|
||||||
# run: |
|
|
||||||
# yarn run test
|
|
||||||
#
|
|
||||||
172
.github/actions/build/action.yml
vendored
172
.github/actions/build/action.yml
vendored
|
|
@ -1,172 +0,0 @@
|
||||||
name: 'Build'
|
|
||||||
description: 'Builds, tests, and packages the app for release'
|
|
||||||
inputs:
|
|
||||||
node-version:
|
|
||||||
description: 'Node.js version to use'
|
|
||||||
required: true
|
|
||||||
matrix-name:
|
|
||||||
description: 'Matrix Name'
|
|
||||||
required: true
|
|
||||||
matrix-os:
|
|
||||||
description: 'Matrix OS'
|
|
||||||
required: true
|
|
||||||
upload-artifact:
|
|
||||||
description: 'Whether to upload artifacts'
|
|
||||||
required: false
|
|
||||||
default: 'true'
|
|
||||||
runs:
|
|
||||||
using: 'composite'
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Use Node.js ${{ env.NODE_VERSION }}
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
|
|
||||||
- name: Set up Python 3.10
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.10'
|
|
||||||
|
|
||||||
- name: Fix node-gyp and Python
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
if [[ "$RUNNER_OS" == "macOS" ]]; then
|
|
||||||
brew install python-setuptools python-packaging
|
|
||||||
else
|
|
||||||
python3 -m pip install $EXTRA_ARGS packaging setuptools
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
|
||||||
shell: bash
|
|
||||||
id: yarn-cache-dir-path
|
|
||||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock', 'app/yarn.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-yarn-
|
|
||||||
|
|
||||||
- name: Delete old electron headers
|
|
||||||
# These can sometimes be present on a freshly-provisioned github runner, so remove them
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
rm -rf ~/.electron-gyp || true
|
|
||||||
|
|
||||||
- name: Install
|
|
||||||
shell: bash
|
|
||||||
run: yarn install
|
|
||||||
env:
|
|
||||||
npm_config_node_gyp: ${{ github.workspace }}${{ runner.os == 'Windows' && '\node_modules\node-gyp\bin\node-gyp.js' || '/node_modules/node-gyp/bin/node-gyp.js' }}
|
|
||||||
|
|
||||||
- name: Install libarchive-tools
|
|
||||||
shell: bash
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install libarchive-tools
|
|
||||||
|
|
||||||
- name: Rebuild native modules for Electron
|
|
||||||
shell: bash
|
|
||||||
run: npx electron-rebuild
|
|
||||||
|
|
||||||
- name: Lint and Run Unit Tests
|
|
||||||
shell: bash
|
|
||||||
run: yarn run test
|
|
||||||
|
|
||||||
- name: Getting Build Icon
|
|
||||||
shell: bash
|
|
||||||
if: github.ref == 'refs/heads/canary' || github.base_ref == 'canary'
|
|
||||||
run: |
|
|
||||||
cp build/canary.ico build/icon.ico
|
|
||||||
cp build/canary.icns build/icon.icns
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
if [ -z "$CSC_LINK" ] ; then unset CSC_LINK ; fi
|
|
||||||
if [ -z "$CSC_KEY_PASSWORD" ] ; then unset CSC_KEY_PASSWORD ; fi
|
|
||||||
if [ -z "$WIN_CSC_LINK" ] ; then unset WIN_CSC_LINK ; fi
|
|
||||||
if [ -z "$WIN_CSC_KEY_PASSWORD" ] ; then unset WIN_CSC_KEY_PASSWORD ; fi
|
|
||||||
if [ -z "$APPLE_ID" ] ; then unset APPLE_ID ; fi
|
|
||||||
if [ -z "$APPLE_APP_SPECIFIC_PASSWORD" ] ; then unset APPLE_APP_SPECIFIC_PASSWORD ; fi
|
|
||||||
|
|
||||||
yarn run dist
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ env.GH_TOKEN }}
|
|
||||||
CSC_LINK: ${{ env.MAC_CERT_P12_BASE64 }}
|
|
||||||
CSC_KEY_PASSWORD: ${{ env.MAC_CERT_P12_PASSWORD }}
|
|
||||||
WIN_CSC_LINK: ${{ env.WIN_CERT_P12_BASE64 }}
|
|
||||||
WIN_CSC_KEY_PASSWORD: ${{ env.WIN_CERT_P12_PASSWORD }}
|
|
||||||
APPLE_ID: ${{ env.APPLE_ID }}
|
|
||||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ env.APPLE_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Archive Build Artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: hyper-${{ runner.os }}-${{ inputs.matrix-name }}
|
|
||||||
path: |
|
|
||||||
dist/*.dmg
|
|
||||||
dist/*.snap
|
|
||||||
dist/*.AppImage
|
|
||||||
dist/*.deb
|
|
||||||
dist/*.pacman
|
|
||||||
dist/*.exe
|
|
||||||
|
|
||||||
- name: Run E2E Tests
|
|
||||||
shell: bash
|
|
||||||
if: runner.os != 'Linux'
|
|
||||||
run: yarn run test:e2e --verbose
|
|
||||||
|
|
||||||
- name: Run E2E Tests on Linux
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
uses: GabrielBB/xvfb-action@v1.7
|
|
||||||
with:
|
|
||||||
run: |
|
|
||||||
yarn run test:e2e
|
|
||||||
env:
|
|
||||||
SHELL: /bin/bash
|
|
||||||
DEBUG: "pw:browser*"
|
|
||||||
|
|
||||||
- name: Archive E2E test screenshot
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: e2e-${{ inputs.matrix-os }}-${{ strategy.job-index }}
|
|
||||||
path: dist/tmp/*.png
|
|
||||||
|
|
||||||
- name: Save the pr number in an artifact
|
|
||||||
shell: bash
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
env:
|
|
||||||
PR_NUM: ${{ github.event.number }}
|
|
||||||
run: echo $PR_NUM > pr_num.txt
|
|
||||||
|
|
||||||
- name: Upload the pr num
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
|
||||||
name: pr_num
|
|
||||||
path: ./pr_num.txt
|
|
||||||
|
|
||||||
- uses: actions/cache/save@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
with:
|
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock', 'app/yarn.lock') }}
|
|
||||||
|
|
||||||
# - name: Run E2E Tests (non-Linux)
|
|
||||||
# if: runner.os != 'Linux'
|
|
||||||
# shell: bash
|
|
||||||
# run: yarn run test:e2e --verbose
|
|
||||||
#
|
|
||||||
# - name: Run E2E Tests on Linux
|
|
||||||
# if: runner.os == 'Linux'
|
|
||||||
# uses: GabrielBB/xvfb-action@v1.7
|
|
||||||
# with:
|
|
||||||
# run: |
|
|
||||||
# yarn run test
|
|
||||||
#
|
|
||||||
18
.github/dependabot.yml
vendored
18
.github/dependabot.yml
vendored
|
|
@ -5,31 +5,17 @@ updates:
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
time: '11:00'
|
time: '11:00'
|
||||||
|
open-pull-requests-limit: 30
|
||||||
target-branch: canary
|
target-branch: canary
|
||||||
versioning-strategy: increase
|
versioning-strategy: increase
|
||||||
commit-message:
|
|
||||||
prefix: "chore(deps-dev):"
|
|
||||||
groups:
|
|
||||||
minorAndPatch:
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
open-pull-requests-limit: 100
|
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/app"
|
directory: "/app"
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
time: '11:00'
|
time: '11:00'
|
||||||
|
open-pull-requests-limit: 30
|
||||||
target-branch: canary
|
target-branch: canary
|
||||||
versioning-strategy: increase
|
versioning-strategy: increase
|
||||||
commit-message:
|
|
||||||
prefix: "chore(deps):"
|
|
||||||
groups:
|
|
||||||
minorAndPatch:
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
open-pull-requests-limit: 100
|
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
|
|
|
||||||
9
.github/pull_request_template.md
vendored
9
.github/pull_request_template.md
vendored
|
|
@ -1 +1,8 @@
|
||||||
<!-- Please check `Allow edits from maintainers`. Thanks! -->
|
<!-- Hi there! Thanks for submitting a PR! We're excited to see what you've got for us.
|
||||||
|
|
||||||
|
- To help whoever reviews your PR, it'd be extremely helpful for you to list whether your PR is ready to be merged,
|
||||||
|
If there's anything left to do and if there are any related PRs
|
||||||
|
- It'd also be extremely helpful to enable us to update your PR incase we need to rebase or what-not by checking `Allow edits from maintainers`
|
||||||
|
- If your PR changes some API, please make a PR for hyper website too: https://github.com/vercel/hyper-site.
|
||||||
|
|
||||||
|
Thanks, again! -->
|
||||||
|
|
|
||||||
154
.github/workflows/ci.yml
vendored
154
.github/workflows/ci.yml
vendored
|
|
@ -1,154 +0,0 @@
|
||||||
name: Node CI
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
workflow_dispatch:
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ci-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
NODE_VERSION: 20.11.0
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
# CSC_LINK: ${{ secrets.MAC_CERT_P12_BASE64 }}
|
|
||||||
# CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_P12_PASSWORD }}
|
|
||||||
# WIN_CSC_LINK: ${{ secrets.WIN_CERT_P12_BASE64 }}
|
|
||||||
# WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CERT_P12_PASSWORD }}
|
|
||||||
# APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
||||||
# APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-ubuntu:
|
|
||||||
name: Build Ubuntu
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
concurrency:
|
|
||||||
group: build-ubuntu-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: ./.github/actions/build
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-os: ubuntu-latest
|
|
||||||
matrix-name: ubuntu
|
|
||||||
upload-artifact: false
|
|
||||||
|
|
||||||
build-macos:
|
|
||||||
name: Build macOS
|
|
||||||
runs-on: macos-latest
|
|
||||||
concurrency:
|
|
||||||
group: build-macos-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: ./.github/actions/build
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-os: macos-latest
|
|
||||||
matrix-name: macos
|
|
||||||
upload-artifact: false
|
|
||||||
|
|
||||||
build-windows:
|
|
||||||
name: Build Windows
|
|
||||||
runs-on: windows-latest
|
|
||||||
concurrency:
|
|
||||||
group: build-win-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: ./.github/actions/build
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-os: windows-latest
|
|
||||||
matrix-name: win
|
|
||||||
upload-artifact: false
|
|
||||||
|
|
||||||
# ARM Linux:
|
|
||||||
|
|
||||||
build-linux-armv7l:
|
|
||||||
name: Build Linux ARMv7l
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
concurrency:
|
|
||||||
group: build-linux-armv7l-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up QEMU and binfmt
|
|
||||||
run: |
|
|
||||||
# Install qemu-user-static
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y qemu-user-static binfmt-support python3-setuptools
|
|
||||||
|
|
||||||
# Install distutils for node-gyp
|
|
||||||
sudo apt-get install -y python3-distutils || sudo apt-get install -y python3-dev python-is-python3
|
|
||||||
|
|
||||||
|
|
||||||
# Download and install binfmt configurations
|
|
||||||
wget https://github.com/qemu/qemu/raw/master/scripts/qemu-binfmt-conf.sh
|
|
||||||
chmod +x qemu-binfmt-conf.sh
|
|
||||||
sudo ./qemu-binfmt-conf.sh --qemu-path /usr/bin --qemu-suffix -static --debian
|
|
||||||
|
|
||||||
# Import the binfmt configurations
|
|
||||||
sudo update-binfmts --import qemu-arm
|
|
||||||
sudo update-binfmts --import qemu-aarch64
|
|
||||||
|
|
||||||
# Verify they exist
|
|
||||||
ls -la /proc/sys/fs/binfmt_misc/qemu-arm* || true
|
|
||||||
|
|
||||||
- name: Build ARMv7l
|
|
||||||
uses: ./.github/actions/build-linux-arm
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-name: armv7l
|
|
||||||
matrix-cpu: cortex-a8
|
|
||||||
matrix-image: raspios_lite:latest
|
|
||||||
upload-artifact: false
|
|
||||||
|
|
||||||
build-linux-arm64:
|
|
||||||
name: Build Linux ARM64
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
concurrency:
|
|
||||||
group: build-linux-arm64-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build ARM64
|
|
||||||
uses: ./.github/actions/build-linux-arm
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-name: arm64
|
|
||||||
matrix-cpu: cortex-a53
|
|
||||||
matrix-image: raspios_lite_arm64:latest
|
|
||||||
upload-artifact: false
|
|
||||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
|
|
@ -35,11 +35,11 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
|
@ -50,7 +50,7 @@ jobs:
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v3
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
|
@ -64,4 +64,4 @@ jobs:
|
||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v3
|
uses: github/codeql-action/analyze@v2
|
||||||
|
|
|
||||||
6
.github/workflows/e2e_comment.yml
vendored
6
.github/workflows/e2e_comment.yml
vendored
|
|
@ -14,14 +14,14 @@ jobs:
|
||||||
WORKFLOW_RUN_INFO: ${{ toJSON(github.event.workflow_run) }}
|
WORKFLOW_RUN_INFO: ${{ toJSON(github.event.workflow_run) }}
|
||||||
run: echo "$WORKFLOW_RUN_INFO"
|
run: echo "$WORKFLOW_RUN_INFO"
|
||||||
- name: Download Artifacts
|
- name: Download Artifacts
|
||||||
uses: dawidd6/action-download-artifact@v6
|
uses: dawidd6/action-download-artifact@v2.24.2
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
workflow: nodejs.yml
|
workflow: nodejs.yml
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
name: e2e
|
name: e2e
|
||||||
- name: Get PR number
|
- name: Get PR number
|
||||||
uses: dawidd6/action-download-artifact@v6
|
uses: dawidd6/action-download-artifact@v2.24.2
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
workflow: nodejs.yml
|
workflow: nodejs.yml
|
||||||
|
|
@ -29,7 +29,7 @@ jobs:
|
||||||
name: pr_num
|
name: pr_num
|
||||||
- name: Read the pr_num file
|
- name: Read the pr_num file
|
||||||
id: pr_num_reader
|
id: pr_num_reader
|
||||||
uses: juliangruber/read-file-action@v1.1.7
|
uses: juliangruber/read-file-action@v1.1.6
|
||||||
with:
|
with:
|
||||||
path: ./pr_num.txt
|
path: ./pr_num.txt
|
||||||
- name: List images
|
- name: List images
|
||||||
|
|
|
||||||
81
.github/workflows/nodejs.yml
vendored
Normal file
81
.github/workflows/nodejs.yml
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
name: Node CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- canary
|
||||||
|
pull_request:
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ${{matrix.os}}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [16.x]
|
||||||
|
os:
|
||||||
|
- macos-12
|
||||||
|
- ubuntu-18.04
|
||||||
|
- windows-latest
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
cache: yarn
|
||||||
|
cache-dependency-path: |
|
||||||
|
yarn.lock
|
||||||
|
app/yarn.lock
|
||||||
|
- name: Install
|
||||||
|
run: yarn install
|
||||||
|
- name: Lint and Run Unit Tests
|
||||||
|
run: yarn run test
|
||||||
|
- name: Getting Build Icon
|
||||||
|
if: github.ref == 'refs/heads/canary' || github.base_ref == 'canary'
|
||||||
|
run: |
|
||||||
|
cp build/canary.ico build/icon.ico
|
||||||
|
cp build/canary.icns build/icon.icns
|
||||||
|
- name: Build
|
||||||
|
run: if [ ! -z "$CSC_LINK" ] ; then yarn run dist ; else unset CSC_LINK && unset WIN_CSC_LINK && yarn run dist --publish=never ; fi
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CSC_LINK: ${{ secrets.MAC_CERT_P12_BASE64 }}
|
||||||
|
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_P12_PASSWORD }}
|
||||||
|
WIN_CSC_LINK: ${{ secrets.WIN_CERT_P12_BASE64 }}
|
||||||
|
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CERT_P12_PASSWORD }}
|
||||||
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||||
|
- name: Archive Build Artifacts
|
||||||
|
uses: LabhanshAgrawal/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
dist/*.dmg
|
||||||
|
dist/*.snap
|
||||||
|
dist/*.AppImage
|
||||||
|
dist/*.deb
|
||||||
|
dist/*.rpm
|
||||||
|
dist/*.exe
|
||||||
|
- name: Run E2E Tests
|
||||||
|
if: runner.os != 'Linux'
|
||||||
|
run: yarn run test:e2e
|
||||||
|
- name: Archive E2E test screenshot
|
||||||
|
if: runner.os != 'Linux'
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: e2e
|
||||||
|
path: dist/tmp/*.png
|
||||||
|
- name: Save the pr number in an artifact
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
env:
|
||||||
|
PR_NUM: ${{ github.event.number }}
|
||||||
|
run: echo $PR_NUM > pr_num.txt
|
||||||
|
- name: Upload the pr num
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
with:
|
||||||
|
name: pr_num
|
||||||
|
path: ./pr_num.txt
|
||||||
194
.github/workflows/release.yml
vendored
194
.github/workflows/release.yml
vendored
|
|
@ -1,194 +0,0 @@
|
||||||
name: Create Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: release-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
NODE_VERSION: 20.11.0
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
CSC_LINK: ${{ secrets.MAC_CERT_P12_BASE64 }}
|
|
||||||
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_P12_PASSWORD }}
|
|
||||||
WIN_CSC_LINK: ${{ secrets.WIN_CERT_P12_BASE64 }}
|
|
||||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CERT_P12_PASSWORD }}
|
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
||||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-ubuntu:
|
|
||||||
name: Build Ubuntu
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: ./.github/actions/build
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-os: ubuntu-latest
|
|
||||||
matrix-name: ubuntu
|
|
||||||
upload-artifact: true
|
|
||||||
|
|
||||||
build-macos:
|
|
||||||
name: Build macOS
|
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: ./.github/actions/build
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-os: macos-latest
|
|
||||||
matrix-name: macos
|
|
||||||
upload-artifact: true
|
|
||||||
|
|
||||||
build-windows:
|
|
||||||
name: Build Windows
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: ./.github/actions/build
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-os: windows-latest
|
|
||||||
matrix-name: win
|
|
||||||
upload-artifact: true
|
|
||||||
|
|
||||||
# ARM Linux:
|
|
||||||
|
|
||||||
build-linux-armv7l:
|
|
||||||
name: Build Linux ARMv7l
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up QEMU and binfmt
|
|
||||||
run: |
|
|
||||||
# Install qemu-user-static
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y qemu-user-static binfmt-support python3-setuptools
|
|
||||||
|
|
||||||
# Install distutils for node-gyp
|
|
||||||
sudo apt-get install -y python3-distutils || sudo apt-get install -y python3-dev python-is-python3
|
|
||||||
|
|
||||||
# Download and install binfmt configurations
|
|
||||||
wget https://github.com/qemu/qemu/raw/master/scripts/qemu-binfmt-conf.sh
|
|
||||||
chmod +x qemu-binfmt-conf.sh
|
|
||||||
sudo ./qemu-binfmt-conf.sh --qemu-path /usr/bin --qemu-suffix -static --debian
|
|
||||||
|
|
||||||
# Import the binfmt configurations
|
|
||||||
sudo update-binfmts --import qemu-arm
|
|
||||||
sudo update-binfmts --import qemu-aarch64
|
|
||||||
|
|
||||||
# Verify they exist
|
|
||||||
ls -la /proc/sys/fs/binfmt_misc/qemu-arm* || true
|
|
||||||
|
|
||||||
- name: Build ARMv7l
|
|
||||||
uses: ./.github/actions/build-linux-arm
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-name: armv7l
|
|
||||||
matrix-cpu: cortex-a8
|
|
||||||
matrix-image: raspios_lite:latest
|
|
||||||
upload-artifact: true
|
|
||||||
|
|
||||||
build-linux-arm64:
|
|
||||||
name: Build Linux ARM64
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build ARM64
|
|
||||||
uses: ./.github/actions/build-linux-arm
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-name: arm64
|
|
||||||
matrix-cpu: cortex-a53
|
|
||||||
matrix-image: raspios_lite_arm64:latest
|
|
||||||
upload-artifact: true
|
|
||||||
|
|
||||||
###
|
|
||||||
|
|
||||||
upload-release:
|
|
||||||
name: Upload and Create Release
|
|
||||||
needs:
|
|
||||||
- build-ubuntu
|
|
||||||
- build-macos
|
|
||||||
- build-windows
|
|
||||||
- build-linux-armv7l
|
|
||||||
- build-linux-arm64
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Download all artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
path: ./artifacts
|
|
||||||
|
|
||||||
- name: List downloaded artifacts
|
|
||||||
run: ls -R ./artifacts
|
|
||||||
|
|
||||||
- name: Ensure non-empty artifacts
|
|
||||||
run: |
|
|
||||||
echo "Checking for zero-byte files in artifacts/..."
|
|
||||||
find artifacts/ -type f -empty -print -delete
|
|
||||||
echo "Cleaned up any zero-byte files."
|
|
||||||
echo "Remaining files:"
|
|
||||||
ls -R artifacts/
|
|
||||||
|
|
||||||
- name: Dump release assets for debugging
|
|
||||||
if: always()
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GitHub CLI needs this
|
|
||||||
run: |
|
|
||||||
echo "Fetching release assets for tag: ${{ github.ref_name }}"
|
|
||||||
gh release view "${{ github.ref_name }}" --repo "${{ github.repository }}" --json assets --jq '.assets[] | "\(.name) - \(.url)"' || true
|
|
||||||
|
|
||||||
- name: Create GitHub Release
|
|
||||||
# Pinned to work around https://github.com/softprops/action-gh-release/issues/445
|
|
||||||
uses: softprops/action-gh-release@v2.0.5
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
ACTIONS_STEP_DEBUG: true
|
|
||||||
with:
|
|
||||||
preserve_order: true
|
|
||||||
overwrite: true
|
|
||||||
name: Release ${{ github.ref_name }}
|
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
draft: true
|
|
||||||
files: |
|
|
||||||
artifacts/**/*.dmg
|
|
||||||
artifacts/**/*.snap
|
|
||||||
artifacts/**/*.AppImage
|
|
||||||
artifacts/**/*.deb
|
|
||||||
artifacts/**/*.pacman
|
|
||||||
artifacts/**/*.exe
|
|
||||||
3
.husky/pre-push
Normal file → Executable file
3
.husky/pre-push
Normal file → Executable file
|
|
@ -1 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
yarn test
|
yarn test
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
20.11.0
|
|
||||||
1
.nvmrc
1
.nvmrc
|
|
@ -1 +0,0 @@
|
||||||
20.11.0
|
|
||||||
148049
.yarn/releases/yarn-classic.cjs
vendored
148049
.yarn/releases/yarn-classic.cjs
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
||||||
yarnPath: .yarn/releases/yarn-classic.cjs
|
|
||||||
23
README.md
23
README.md
|
|
@ -1,24 +1,19 @@
|
||||||
|

|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="hyper - modern web-based terminal" height=150 src="https://github.com/user-attachments/assets/3096f20a-8116-45ce-8c5e-0f1106107484">
|
<a aria-label="Vercel logo" href="https://vercel.com">
|
||||||
</p>
|
<img src="https://img.shields.io/badge/MADE%20BY%20Vercel-000000.svg?style=for-the-badge&logo=vercel&labelColor=000000&logoWidth=20">
|
||||||
|
</a>
|
||||||
<p>
|
</p>
|
||||||
<a aria-label="Vercel logo" href="https://vercel.com"><img
|
|
||||||
src="https://img.shields.io/badge/MADE%20BY%20Vercel-000000.svg?style=for-the-badge&logo=vercel&labelColor=000000&logoWidth=20"
|
[](https://github.com/vercel/hyper/actions?query=workflow%3A%22Node+CI%22+branch%3Acanary+event%3Apush)
|
||||||
/></a> <a aria-label="Quine logo" href="https://quineglobal.com"><img
|
|
||||||
width="143" height="28" alt="forked-by-quine" src="https://github.com/user-attachments/assets/57decaa2-7d8c-4d13-ada7-ff6b964346f7"
|
|
||||||
/></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[](https://github.com/quine-global/hyper/actions/workflows/ci.yml)
|
|
||||||
|
|
||||||
[](https://changelog.com/213)
|
[](https://changelog.com/213)
|
||||||
|
|
||||||
For more details, head to: https://hyper.is
|
For more details, head to: https://hyper.is
|
||||||
|
|
||||||
## Project goals
|
## Project goals
|
||||||
|
|
||||||
The goal of the project is to create a beautiful and customizable experience for command-line interface users, built on open web standards. We have picked up where Vercel left off, and intend to first offer stability, followed by a more tailored experience. We will still support customizability, but stability of theming APIs is not a goal.
|
The goal of the project is to create a beautiful and extensible experience for command-line interface users, built on open web standards. In the beginning, our focus will be primarily around speed, stability and the development of the correct API for extension authors.
|
||||||
|
|
||||||
In the future, we anticipate the community will come up with innovative additions to enhance what could be the simplest, most powerful and well-tested interface for productivity.
|
In the future, we anticipate the community will come up with innovative additions to enhance what could be the simplest, most powerful and well-tested interface for productivity.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import {EventEmitter} from 'events';
|
|
||||||
|
|
||||||
import fetch from 'electron-fetch';
|
import fetch from 'electron-fetch';
|
||||||
|
import {EventEmitter} from 'events';
|
||||||
|
|
||||||
class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||||
updateURL!: string;
|
updateURL!: string;
|
||||||
|
|
@ -27,12 +26,13 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||||
this.emit('update-not-available');
|
this.emit('update-not-available');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return res.json().then(({name, notes, pub_date}: {name: string; notes: string; pub_date: string}) => {
|
return res.json().then(({name, notes, pub_date}) => {
|
||||||
// Only name is mandatory, needed to construct release URL.
|
// Only name is mandatory, needed to construct release URL.
|
||||||
if (!name) {
|
if (!name) {
|
||||||
throw new Error('Malformed server response: release name is missing.');
|
throw new Error('Malformed server response: release name is missing.');
|
||||||
}
|
}
|
||||||
const date = pub_date ? new Date(pub_date) : new Date();
|
// If `null` is passed to Date constructor, current time will be used. This doesn't work with `undefined`
|
||||||
|
const date = new Date(pub_date || null);
|
||||||
this.emit('update-available', {}, notes, name, date);
|
this.emit('update-available', {}, notes, name, date);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
@ -47,6 +47,4 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const autoUpdaterLinux = new AutoUpdater();
|
export default new AutoUpdater();
|
||||||
|
|
||||||
export default autoUpdaterLinux;
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import {app, Menu} from 'electron';
|
import {app, Menu, BrowserWindow} from 'electron';
|
||||||
import type {BrowserWindow} from 'electron';
|
|
||||||
|
|
||||||
import {openConfig, getConfig} from './config';
|
import {openConfig, getConfig} from './config';
|
||||||
import {updatePlugins} from './plugins';
|
import {updatePlugins} from './plugins';
|
||||||
import {installCLI} from './utils/cli-install';
|
import {installCLI} from './utils/cli-install';
|
||||||
|
|
@ -146,22 +144,6 @@ const commands: Record<string, (focusedWindow?: BrowserWindow) => void> = {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
//Profile specific commands
|
|
||||||
getConfig().profiles.forEach((profile) => {
|
|
||||||
commands[`window:new:${profile.name}`] = () => {
|
|
||||||
setTimeout(() => app.createWindow(undefined, undefined, profile.name), 0);
|
|
||||||
};
|
|
||||||
commands[`tab:new:${profile.name}`] = (focusedWindow) => {
|
|
||||||
focusedWindow?.rpc.emit('termgroup add req', {profile: profile.name});
|
|
||||||
};
|
|
||||||
commands[`pane:splitRight:${profile.name}`] = (focusedWindow) => {
|
|
||||||
focusedWindow?.rpc.emit('split request vertical', {profile: profile.name});
|
|
||||||
};
|
|
||||||
commands[`pane:splitDown:${profile.name}`] = (focusedWindow) => {
|
|
||||||
focusedWindow?.rpc.emit('split request horizontal', {profile: profile.name});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export const execCommand = (command: string, focusedWindow?: BrowserWindow) => {
|
export const execCommand = (command: string, focusedWindow?: BrowserWindow) => {
|
||||||
const fn = commands[command];
|
const fn = commands[command];
|
||||||
if (fn) {
|
if (fn) {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
import {app} from 'electron';
|
|
||||||
|
|
||||||
import chokidar from 'chokidar';
|
import chokidar from 'chokidar';
|
||||||
|
import notify from './notify';
|
||||||
import type {parsedConfig, configOptions} from '../typings/config';
|
|
||||||
|
|
||||||
import {_import, getDefaultConfig} from './config/import';
|
import {_import, getDefaultConfig} from './config/import';
|
||||||
import _openConfig from './config/open';
|
import _openConfig from './config/open';
|
||||||
import {cfgPath, cfgDir} from './config/paths';
|
import {cfgPath, cfgDir} from './config/paths';
|
||||||
import notify from './notify';
|
|
||||||
import {getColorMap} from './utils/colors';
|
import {getColorMap} from './utils/colors';
|
||||||
|
import {parsedConfig, configOptions} from '../lib/config';
|
||||||
|
import {app} from 'electron';
|
||||||
|
|
||||||
const watchers: Function[] = [];
|
const watchers: Function[] = [];
|
||||||
let cfg: parsedConfig = {} as any;
|
let cfg: parsedConfig = {} as any;
|
||||||
|
|
@ -81,30 +78,8 @@ export const getConfigDir = () => {
|
||||||
return cfgDir;
|
return cfgDir;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDefaultProfile = () => {
|
|
||||||
return cfg.config.defaultProfile || cfg.config.profiles[0]?.name || 'default';
|
|
||||||
};
|
|
||||||
|
|
||||||
// get config for the default profile, keeping it for backward compatibility
|
|
||||||
export const getConfig = () => {
|
export const getConfig = () => {
|
||||||
return getProfileConfig(getDefaultProfile());
|
return cfg.config;
|
||||||
};
|
|
||||||
|
|
||||||
export const getProfiles = () => {
|
|
||||||
return cfg.config.profiles;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getProfileConfig = (profileName: string): configOptions => {
|
|
||||||
const {profiles, defaultProfile, ...baseConfig} = cfg.config;
|
|
||||||
const profileConfig = profiles.find((p) => p.name === profileName)?.config || {};
|
|
||||||
for (const key in profileConfig) {
|
|
||||||
if (typeof baseConfig[key] === 'object' && !Array.isArray(baseConfig[key])) {
|
|
||||||
baseConfig[key] = {...baseConfig[key], ...profileConfig[key]};
|
|
||||||
} else {
|
|
||||||
baseConfig[key] = profileConfig[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {...baseConfig, defaultProfile, profiles};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const openConfig = () => {
|
export const openConfig = () => {
|
||||||
|
|
|
||||||
|
|
@ -61,15 +61,7 @@
|
||||||
"disableAutoUpdates": false,
|
"disableAutoUpdates": false,
|
||||||
"autoUpdatePlugins": true,
|
"autoUpdatePlugins": true,
|
||||||
"preserveCWD": true,
|
"preserveCWD": true,
|
||||||
"screenReaderMode": false,
|
"screenReaderMode": false
|
||||||
"imageSupport": true,
|
|
||||||
"defaultProfile": "default",
|
|
||||||
"profiles": [
|
|
||||||
{
|
|
||||||
"name": "default",
|
|
||||||
"config": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"plugins": [],
|
"plugins": [],
|
||||||
"localPlugins": [],
|
"localPlugins": [],
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import {readFileSync, mkdirpSync} from 'fs-extra';
|
import {readFileSync} from 'fs-extra';
|
||||||
|
import {sync as mkdirpSync} from 'mkdirp';
|
||||||
import type {rawConfig} from '../../typings/config';
|
|
||||||
import notify from '../notify';
|
|
||||||
|
|
||||||
import {_init} from './init';
|
|
||||||
import {migrateHyper3Config} from './migrate';
|
|
||||||
import {defaultCfg, cfgPath, plugs, defaultPlatformKeyPath} from './paths';
|
import {defaultCfg, cfgPath, plugs, defaultPlatformKeyPath} from './paths';
|
||||||
|
import {_init} from './init';
|
||||||
|
import notify from '../notify';
|
||||||
|
import {rawConfig} from '../../lib/config';
|
||||||
|
import {migrateHyper3Config} from './migrate';
|
||||||
|
|
||||||
let defaultConfig: rawConfig;
|
let defaultConfig: rawConfig;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
import vm from 'vm';
|
import vm from 'vm';
|
||||||
|
|
||||||
import merge from 'lodash/merge';
|
|
||||||
|
|
||||||
import type {parsedConfig, rawConfig, configOptions} from '../../typings/config';
|
|
||||||
import notify from '../notify';
|
import notify from '../notify';
|
||||||
import mapKeys from '../utils/map-keys';
|
import mapKeys from '../utils/map-keys';
|
||||||
|
import {parsedConfig, rawConfig, configOptions} from '../../lib/config';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
const _extract = (script?: vm.Script): Record<string, any> => {
|
const _extract = (script?: vm.Script): Record<string, any> => {
|
||||||
const module: Record<string, any> = {};
|
const module: Record<string, any> = {};
|
||||||
script?.runInNewContext({module}, {displayErrors: true});
|
script?.runInNewContext({module});
|
||||||
if (!module.exports) {
|
if (!module.exports) {
|
||||||
throw new Error('Error reading configuration: `module.exports` not set');
|
throw new Error('Error reading configuration: `module.exports` not set');
|
||||||
}
|
}
|
||||||
|
|
@ -18,10 +16,10 @@ const _extract = (script?: vm.Script): Record<string, any> => {
|
||||||
|
|
||||||
const _syntaxValidation = (cfg: string) => {
|
const _syntaxValidation = (cfg: string) => {
|
||||||
try {
|
try {
|
||||||
return new vm.Script(cfg, {filename: '.hyper.js'});
|
return new vm.Script(cfg, {filename: '.hyper.js', displayErrors: true});
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
const err = _err as {name: string};
|
const err = _err as {name: string};
|
||||||
notify(`Error loading config: ${err.name}`, JSON.stringify(err), {error: err});
|
notify(`Error loading config: ${err.name}`, `${err}`, {error: err});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -34,19 +32,7 @@ const _init = (userCfg: rawConfig, defaultCfg: rawConfig): parsedConfig => {
|
||||||
return {
|
return {
|
||||||
config: (() => {
|
config: (() => {
|
||||||
if (userCfg?.config) {
|
if (userCfg?.config) {
|
||||||
const conf = userCfg.config;
|
return _.merge({}, defaultCfg.config, userCfg.config);
|
||||||
conf.defaultProfile = conf.defaultProfile || 'default';
|
|
||||||
conf.profiles = conf.profiles || [];
|
|
||||||
conf.profiles = conf.profiles.length > 0 ? conf.profiles : [{name: 'default', config: {}}];
|
|
||||||
conf.profiles = conf.profiles.map((p, i) => ({
|
|
||||||
...p,
|
|
||||||
name: p.name || `profile-${i + 1}`,
|
|
||||||
config: p.config || {}
|
|
||||||
}));
|
|
||||||
if (!conf.profiles.map((p) => p.name).includes(conf.defaultProfile)) {
|
|
||||||
conf.defaultProfile = conf.profiles[0].name;
|
|
||||||
}
|
|
||||||
return merge({}, defaultCfg.config, conf);
|
|
||||||
} else {
|
} else {
|
||||||
notify('Error reading configuration: `config` key is missing');
|
notify('Error reading configuration: `config` key is missing');
|
||||||
return defaultCfg.config || ({} as configOptions);
|
return defaultCfg.config || ({} as configOptions);
|
||||||
|
|
@ -55,8 +41,8 @@ const _init = (userCfg: rawConfig, defaultCfg: rawConfig): parsedConfig => {
|
||||||
// Merging platform specific keymaps with user defined keymaps
|
// Merging platform specific keymaps with user defined keymaps
|
||||||
keymaps: mapKeys({...defaultCfg.keymaps, ...userCfg?.keymaps}),
|
keymaps: mapKeys({...defaultCfg.keymaps, ...userCfg?.keymaps}),
|
||||||
// Ignore undefined values in plugin and localPlugins array Issue #1862
|
// Ignore undefined values in plugin and localPlugins array Issue #1862
|
||||||
plugins: userCfg?.plugins?.filter(Boolean) || [],
|
plugins: (userCfg?.plugins && userCfg.plugins.filter(Boolean)) || [],
|
||||||
localPlugins: userCfg?.localPlugins?.filter(Boolean) || []
|
localPlugins: (userCfg?.localPlugins && userCfg.localPlugins.filter(Boolean)) || []
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
import {dirname, resolve} from 'path';
|
|
||||||
|
|
||||||
import {builders, namedTypes} from 'ast-types';
|
|
||||||
import type {ExpressionKind} from 'ast-types/lib/gen/kinds';
|
|
||||||
import {copy, copySync, existsSync, readFileSync, writeFileSync} from 'fs-extra';
|
|
||||||
import merge from 'lodash/merge';
|
|
||||||
import {parse, prettyPrint} from 'recast';
|
import {parse, prettyPrint} from 'recast';
|
||||||
|
import {builders, namedTypes} from 'ast-types';
|
||||||
import * as babelParser from 'recast/parsers/babel';
|
import * as babelParser from 'recast/parsers/babel';
|
||||||
|
import {copy, copySync, existsSync, readFileSync, writeFileSync} from 'fs-extra';
|
||||||
|
import {dirname, resolve} from 'path';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
import notify from '../notify';
|
import notify from '../notify';
|
||||||
|
|
||||||
import {_extractDefault} from './init';
|
import {_extractDefault} from './init';
|
||||||
import {cfgDir, cfgPath, defaultCfg, legacyCfgPath, plugs, schemaFile, schemaPath} from './paths';
|
import {cfgDir, cfgPath, defaultCfg, legacyCfgPath, plugs, schemaFile, schemaPath} from './paths';
|
||||||
|
|
||||||
|
|
@ -66,7 +63,7 @@ export function configToPlugin(code: string): string {
|
||||||
});
|
});
|
||||||
const statements = ast.program.body;
|
const statements = ast.program.body;
|
||||||
let moduleExportsNode: namedTypes.AssignmentExpression | null = null;
|
let moduleExportsNode: namedTypes.AssignmentExpression | null = null;
|
||||||
let configNode: ExpressionKind | null = null;
|
let configNode: any = null;
|
||||||
|
|
||||||
for (const statement of statements) {
|
for (const statement of statements) {
|
||||||
if (namedTypes.ExpressionStatement.check(statement)) {
|
if (namedTypes.ExpressionStatement.check(statement)) {
|
||||||
|
|
@ -89,7 +86,7 @@ export function configToPlugin(code: string): string {
|
||||||
namedTypes.Identifier.check(property.key) &&
|
namedTypes.Identifier.check(property.key) &&
|
||||||
property.key.name === 'config'
|
property.key.name === 'config'
|
||||||
) {
|
) {
|
||||||
configNode = property.value as ExpressionKind;
|
configNode = property.value;
|
||||||
if (namedTypes.ObjectExpression.check(property.value)) {
|
if (namedTypes.ObjectExpression.check(property.value)) {
|
||||||
configNode = removeProperties(property.value);
|
configNode = removeProperties(property.value);
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +165,7 @@ export const migrateHyper3Config = () => {
|
||||||
try {
|
try {
|
||||||
const legacyCfgRaw = readFileSync(legacyCfgPath, 'utf8');
|
const legacyCfgRaw = readFileSync(legacyCfgPath, 'utf8');
|
||||||
const legacyCfgData = _extractDefault(legacyCfgRaw);
|
const legacyCfgData = _extractDefault(legacyCfgRaw);
|
||||||
newCfgData = merge({}, defaultCfgData, legacyCfgData);
|
newCfgData = _.merge({}, defaultCfgData, legacyCfgData);
|
||||||
|
|
||||||
const pluginCode = configToPlugin(legacyCfgRaw);
|
const pluginCode = configToPlugin(legacyCfgRaw);
|
||||||
if (pluginCode) {
|
if (pluginCode) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
import {exec} from 'child_process';
|
|
||||||
|
|
||||||
import {shell} from 'electron';
|
import {shell} from 'electron';
|
||||||
|
|
||||||
import * as Registry from 'native-reg';
|
|
||||||
|
|
||||||
import {cfgPath} from './paths';
|
import {cfgPath} from './paths';
|
||||||
|
import * as Registry from 'native-reg';
|
||||||
|
import {exec} from 'child_process';
|
||||||
|
|
||||||
const getUserChoiceKey = () => {
|
const getUserChoiceKey = () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -60,7 +57,7 @@ const openNotepad = (file: string) =>
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const openConfig = () => {
|
export default () => {
|
||||||
// Windows opens .js files with WScript.exe by default
|
// Windows opens .js files with WScript.exe by default
|
||||||
// If the user hasn't set up an editor for .js files, we fallback to notepad.
|
// If the user hasn't set up an editor for .js files, we fallback to notepad.
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
|
|
@ -76,5 +73,3 @@ const openConfig = () => {
|
||||||
}
|
}
|
||||||
return shell.openPath(cfgPath).then((error) => error === '');
|
return shell.openPath(cfgPath).then((error) => error === '');
|
||||||
};
|
};
|
||||||
|
|
||||||
export default openConfig;
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
// This module exports paths, names, and other metadata that is referenced
|
// This module exports paths, names, and other metadata that is referenced
|
||||||
import {statSync} from 'fs';
|
|
||||||
import {homedir} from 'os';
|
import {homedir} from 'os';
|
||||||
import {resolve, join} from 'path';
|
|
||||||
|
|
||||||
import {app} from 'electron';
|
import {app} from 'electron';
|
||||||
|
import {statSync} from 'fs';
|
||||||
|
import {resolve, join} from 'path';
|
||||||
import isDev from 'electron-is-dev';
|
import isDev from 'electron-is-dev';
|
||||||
|
|
||||||
const cfgFile = 'hyper.json';
|
const cfgFile = 'hyper.json';
|
||||||
|
|
@ -17,15 +15,15 @@ const homeDirectory = homedir();
|
||||||
let cfgDir = process.env.XDG_CONFIG_HOME
|
let cfgDir = process.env.XDG_CONFIG_HOME
|
||||||
? join(process.env.XDG_CONFIG_HOME, 'Hyper')
|
? join(process.env.XDG_CONFIG_HOME, 'Hyper')
|
||||||
: process.platform === 'win32'
|
: process.platform === 'win32'
|
||||||
? app.getPath('userData')
|
? app.getPath('userData')
|
||||||
: join(homeDirectory, '.config', 'Hyper');
|
: join(homeDirectory, '.config', 'Hyper');
|
||||||
|
|
||||||
const legacyCfgPath = join(
|
const legacyCfgPath = join(
|
||||||
process.env.XDG_CONFIG_HOME !== undefined
|
process.env.XDG_CONFIG_HOME !== undefined
|
||||||
? join(process.env.XDG_CONFIG_HOME, 'hyper')
|
? join(process.env.XDG_CONFIG_HOME, 'hyper')
|
||||||
: process.platform == 'win32'
|
: process.platform == 'win32'
|
||||||
? app.getPath('userData')
|
? app.getPath('userData')
|
||||||
: homedir(),
|
: homedir(),
|
||||||
'.hyper.js'
|
'.hyper.js'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,19 +24,25 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "A string or number representing text font weight."
|
"description": "A string or number representing text font weight."
|
||||||
},
|
}
|
||||||
"Partial<profileConfigOptions>": {
|
},
|
||||||
|
"properties": {
|
||||||
|
"config": {
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"autoUpdatePlugins": {
|
||||||
|
"description": "if `true` (default), Hyper will update plugins every 5 hours\nyou can also set it to a custom time e.g. `1d` or `2h`",
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"boolean"
|
||||||
|
]
|
||||||
|
},
|
||||||
"backgroundColor": {
|
"backgroundColor": {
|
||||||
"description": "terminal background color\n\nopacity is only supported on macOS",
|
"description": "terminal background color\n\nopacity is only supported on macOS",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"bell": {
|
"bell": {
|
||||||
"description": "Supported Options:\n1. 'SOUND' -> Enables the bell as a sound\n2. false: turns off the bell",
|
"description": "Supported Options:\n1. 'SOUND' -> Enables the bell as a sound\n2. false: turns off the bell",
|
||||||
"enum": [
|
"type": "string"
|
||||||
"SOUND",
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"bellSound": {
|
"bellSound": {
|
||||||
"description": "base64 encoded string of the sound file to use for the bell\nif null, the default bell will be used",
|
"description": "base64 encoded string of the sound file to use for the bell\nif null, the default bell will be used",
|
||||||
|
|
@ -157,6 +163,14 @@
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"defaultSSHApp": {
|
||||||
|
"description": "if `true` hyper will be set as the default protocol client for SSH",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"disableAutoUpdates": {
|
||||||
|
"description": "if `true` hyper will not check for updates",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"disableLigatures": {
|
"disableLigatures": {
|
||||||
"description": "if `false` Hyper will use ligatures provided by some fonts",
|
"description": "if `false` Hyper will use ligatures provided by some fonts",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
|
@ -188,10 +202,6 @@
|
||||||
"description": "color of the text",
|
"description": "color of the text",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"imageSupport": {
|
|
||||||
"description": "Whether to enable Sixel and iTerm2 inline image protocol support or not.",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"letterSpacing": {
|
"letterSpacing": {
|
||||||
"description": "letter spacing as a relative unit",
|
"description": "letter spacing as a relative unit",
|
||||||
"type": "number"
|
"type": "number"
|
||||||
|
|
@ -243,11 +253,11 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"shell": {
|
"shell": {
|
||||||
"description": "the shell to run when spawning a new session (e.g. /usr/local/bin/fish)\nif left empty, your system's login shell will be used by default\n\nWindows\n- Make sure to use a full path if the binary name doesn't work\n- Remove `--login` in shellArgs\n\nWindows Subsystem for Linux (WSL) - previously Bash on Windows\n- Example: `C:\\\\Windows\\\\System32\\\\wsl.exe`\n\nGit-bash on Windows\n- Example: `C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe`\n\nPowerShell on Windows\n- Example: `C:\\\\WINDOWS\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe`\n\nCygwin\n- Example: `C:\\\\cygwin64\\\\bin\\\\bash.exe`\n\nGit Bash\n- Example: `C:\\\\Program Files\\\\Git\\\\git-cmd.exe`\nThen Add `--command=usr/bin/bash.exe` to shellArgs",
|
"description": "the shell to run when spawning a new session (i.e. /usr/local/bin/fish)\nif left empty, your system's login shell will be used by default\n\nWindows\n- Make sure to use a full path if the binary name doesn't work\n- Remove `--login` in shellArgs\n\nWindows Subsystem for Linux (WSL) - previously Bash on Windows\n- Example: `C:\\\\Windows\\\\System32\\\\wsl.exe`\n\nGit-bash on Windows\n- Example: `C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe`\n\nPowerShell on Windows\n- Example: `C:\\\\WINDOWS\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe`\n\nCygwin\n- Example: `C:\\\\cygwin64\\\\bin\\\\bash.exe`\n\nGit Bash\n- Example: `C:\\\\Program Files\\\\Git\\\\git-cmd.exe`\nThen Add `--command=usr/bin/bash.exe` to shellArgs",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"shellArgs": {
|
"shellArgs": {
|
||||||
"description": "for setting shell arguments (e.g. for using interactive shellArgs: `['-i']`)\nby default `['--login']` will be used",
|
"description": "for setting shell arguments (i.e. for using interactive shellArgs: `['-i']`)\nby default `['--login']` will be used",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
@ -277,6 +287,17 @@
|
||||||
"uiFontFamily": {
|
"uiFontFamily": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"updateChannel": {
|
||||||
|
"description": "choose either `'stable'` for receiving highly polished, or `'canary'` for less polished but more frequent updates",
|
||||||
|
"enum": [
|
||||||
|
"canary",
|
||||||
|
"stable"
|
||||||
|
],
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"useConpty": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"webGLRenderer": {
|
"webGLRenderer": {
|
||||||
"description": "Whether to use the WebGL renderer. Set it to false to use canvas-based\nrendering (slower, but supports transparent backgrounds)",
|
"description": "Whether to use the WebGL renderer. Set it to false to use canvas-based\nrendering (slower, but supports transparent backgrounds)",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
|
@ -311,414 +332,50 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"required": [
|
||||||
|
"autoUpdatePlugins",
|
||||||
|
"backgroundColor",
|
||||||
|
"bell",
|
||||||
|
"bellSound",
|
||||||
|
"bellSoundURL",
|
||||||
|
"borderColor",
|
||||||
|
"colors",
|
||||||
|
"copyOnSelect",
|
||||||
|
"css",
|
||||||
|
"cursorAccentColor",
|
||||||
|
"cursorBlink",
|
||||||
|
"cursorColor",
|
||||||
|
"cursorShape",
|
||||||
|
"defaultSSHApp",
|
||||||
|
"disableAutoUpdates",
|
||||||
|
"disableLigatures",
|
||||||
|
"env",
|
||||||
|
"fontFamily",
|
||||||
|
"fontSize",
|
||||||
|
"fontWeight",
|
||||||
|
"fontWeightBold",
|
||||||
|
"foregroundColor",
|
||||||
|
"letterSpacing",
|
||||||
|
"lineHeight",
|
||||||
|
"macOptionSelectionMode",
|
||||||
|
"padding",
|
||||||
|
"preserveCWD",
|
||||||
|
"quickEdit",
|
||||||
|
"screenReaderMode",
|
||||||
|
"scrollback",
|
||||||
|
"selectionColor",
|
||||||
|
"shell",
|
||||||
|
"shellArgs",
|
||||||
|
"showHamburgerMenu",
|
||||||
|
"showWindowControls",
|
||||||
|
"termCSS",
|
||||||
|
"updateChannel",
|
||||||
|
"webGLRenderer",
|
||||||
|
"webLinksActivationKey",
|
||||||
|
"workingDirectory"
|
||||||
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"configOptions": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"properties": {
|
|
||||||
"autoUpdatePlugins": {
|
|
||||||
"description": "if `true` (default), Hyper will update plugins every 5 hours\nyou can also set it to a custom time e.g. `1d` or `2h`",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"boolean"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"defaultSSHApp": {
|
|
||||||
"description": "if `true` hyper will be set as the default protocol client for SSH",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"disableAutoUpdates": {
|
|
||||||
"description": "if `true` hyper will not check for updates",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"updateChannel": {
|
|
||||||
"description": "choose either `'stable'` for receiving highly polished, or `'canary'` for less polished but more frequent updates",
|
|
||||||
"enum": [
|
|
||||||
"canary",
|
|
||||||
"stable"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"useConpty": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"autoUpdatePlugins",
|
|
||||||
"defaultSSHApp",
|
|
||||||
"disableAutoUpdates",
|
|
||||||
"updateChannel"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"properties": {
|
|
||||||
"backgroundColor": {
|
|
||||||
"description": "terminal background color\n\nopacity is only supported on macOS",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"bell": {
|
|
||||||
"description": "Supported Options:\n1. 'SOUND' -> Enables the bell as a sound\n2. false: turns off the bell",
|
|
||||||
"enum": [
|
|
||||||
"SOUND",
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bellSound": {
|
|
||||||
"description": "base64 encoded string of the sound file to use for the bell\nif null, the default bell will be used",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bellSoundURL": {
|
|
||||||
"description": "An absolute file path to a sound file on the machine.",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"borderColor": {
|
|
||||||
"description": "border color (window, tabs)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"colors": {
|
|
||||||
"description": "the full list. if you're going to provide the full color palette,\nincluding the 6 x 6 color cubes and the grayscale map, just provide\nan array here instead of a color map object",
|
|
||||||
"properties": {
|
|
||||||
"black": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"blue": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"cyan": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"green": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightBlack": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightBlue": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightCyan": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightGreen": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightMagenta": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightRed": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightWhite": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightYellow": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"magenta": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"red": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"white": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"yellow": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"black",
|
|
||||||
"blue",
|
|
||||||
"cyan",
|
|
||||||
"green",
|
|
||||||
"lightBlack",
|
|
||||||
"lightBlue",
|
|
||||||
"lightCyan",
|
|
||||||
"lightGreen",
|
|
||||||
"lightMagenta",
|
|
||||||
"lightRed",
|
|
||||||
"lightWhite",
|
|
||||||
"lightYellow",
|
|
||||||
"magenta",
|
|
||||||
"red",
|
|
||||||
"white",
|
|
||||||
"yellow"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"copyOnSelect": {
|
|
||||||
"description": "if `true` selected text will automatically be copied to the clipboard",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"css": {
|
|
||||||
"description": "custom CSS to embed in the main window",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"cursorAccentColor": {
|
|
||||||
"description": "terminal text color under BLOCK cursor",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"cursorBlink": {
|
|
||||||
"description": "set to `true` for blinking cursor",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"cursorColor": {
|
|
||||||
"description": "terminal cursor background color and opacity (hex, rgb, hsl, hsv, hwb or cmyk)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"cursorShape": {
|
|
||||||
"description": "`'BEAM'` for |, `'UNDERLINE'` for _, `'BLOCK'` for █",
|
|
||||||
"enum": [
|
|
||||||
"BEAM",
|
|
||||||
"BLOCK",
|
|
||||||
"UNDERLINE"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"disableLigatures": {
|
|
||||||
"description": "if `false` Hyper will use ligatures provided by some fonts",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": "for environment variables",
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"fontFamily": {
|
|
||||||
"description": "font family with optional fallbacks",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"fontSize": {
|
|
||||||
"description": "default font size in pixels for all tabs",
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"fontWeight": {
|
|
||||||
"$ref": "#/definitions/FontWeight",
|
|
||||||
"description": "default font weight eg:'normal', '400', 'bold'"
|
|
||||||
},
|
|
||||||
"fontWeightBold": {
|
|
||||||
"$ref": "#/definitions/FontWeight",
|
|
||||||
"description": "font weight for bold characters eg:'normal', '600', 'bold'"
|
|
||||||
},
|
|
||||||
"foregroundColor": {
|
|
||||||
"description": "color of the text",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"imageSupport": {
|
|
||||||
"description": "Whether to enable Sixel and iTerm2 inline image protocol support or not.",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"letterSpacing": {
|
|
||||||
"description": "letter spacing as a relative unit",
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"lineHeight": {
|
|
||||||
"description": "line height as a relative unit",
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"macOptionSelectionMode": {
|
|
||||||
"description": "choose either `'vertical'`, if you want the column mode when Option key is hold during selection (Default)\nor `'force'`, if you want to force selection regardless of whether the terminal is in mouse events mode\n(inside tmux or vim with mouse mode enabled for example).",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"modifierKeys": {
|
|
||||||
"properties": {
|
|
||||||
"altIsMeta": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"cmdIsMeta": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"altIsMeta",
|
|
||||||
"cmdIsMeta"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"padding": {
|
|
||||||
"description": "custom padding (CSS format, i.e.: `top right bottom left` or `top horizontal bottom` or `vertical horizontal` or `all`)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"preserveCWD": {
|
|
||||||
"description": "set to true to preserve working directory when creating splits or tabs",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"quickEdit": {
|
|
||||||
"description": "if `true` on right click selected text will be copied or pasted if no\nselection is present (`true` by default on Windows and disables the context menu feature)",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"screenReaderMode": {
|
|
||||||
"description": "set to true to enable screen reading apps (like NVDA) to read the contents of the terminal",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"scrollback": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"selectionColor": {
|
|
||||||
"description": "terminal selection color",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"shell": {
|
|
||||||
"description": "the shell to run when spawning a new session (e.g. /usr/local/bin/fish)\nif left empty, your system's login shell will be used by default\n\nWindows\n- Make sure to use a full path if the binary name doesn't work\n- Remove `--login` in shellArgs\n\nWindows Subsystem for Linux (WSL) - previously Bash on Windows\n- Example: `C:\\\\Windows\\\\System32\\\\wsl.exe`\n\nGit-bash on Windows\n- Example: `C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe`\n\nPowerShell on Windows\n- Example: `C:\\\\WINDOWS\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe`\n\nCygwin\n- Example: `C:\\\\cygwin64\\\\bin\\\\bash.exe`\n\nGit Bash\n- Example: `C:\\\\Program Files\\\\Git\\\\git-cmd.exe`\nThen Add `--command=usr/bin/bash.exe` to shellArgs",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"shellArgs": {
|
|
||||||
"description": "for setting shell arguments (e.g. for using interactive shellArgs: `['-i']`)\nby default `['--login']` will be used",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
},
|
|
||||||
"showHamburgerMenu": {
|
|
||||||
"description": "if you're using a Linux setup which show native menus, set to false\n\ndefault: `true` on Linux, `true` on Windows, ignored on macOS",
|
|
||||||
"enum": [
|
|
||||||
"",
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"showWindowControls": {
|
|
||||||
"description": "set to `false` if you want to hide the minimize, maximize and close buttons\n\nadditionally, set to `'left'` if you want them on the left, like in Ubuntu\n\ndefault: `true` on Windows and Linux, ignored on macOS",
|
|
||||||
"enum": [
|
|
||||||
"",
|
|
||||||
false,
|
|
||||||
"left",
|
|
||||||
true
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"termCSS": {
|
|
||||||
"description": "custom CSS to embed in the terminal window",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"uiFontFamily": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"webGLRenderer": {
|
|
||||||
"description": "Whether to use the WebGL renderer. Set it to false to use canvas-based\nrendering (slower, but supports transparent backgrounds)",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"webLinksActivationKey": {
|
|
||||||
"description": "keypress required for weblink activation: [ctrl | alt | meta | shift]",
|
|
||||||
"enum": [
|
|
||||||
"",
|
|
||||||
"alt",
|
|
||||||
"ctrl",
|
|
||||||
"meta",
|
|
||||||
"shift"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"windowSize": {
|
|
||||||
"description": "Initial window size in pixels",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2,
|
|
||||||
"type": "array"
|
|
||||||
},
|
|
||||||
"workingDirectory": {
|
|
||||||
"description": "set custom startup directory (must be an absolute path)",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"backgroundColor",
|
|
||||||
"bell",
|
|
||||||
"bellSound",
|
|
||||||
"bellSoundURL",
|
|
||||||
"borderColor",
|
|
||||||
"colors",
|
|
||||||
"copyOnSelect",
|
|
||||||
"css",
|
|
||||||
"cursorAccentColor",
|
|
||||||
"cursorBlink",
|
|
||||||
"cursorColor",
|
|
||||||
"cursorShape",
|
|
||||||
"disableLigatures",
|
|
||||||
"env",
|
|
||||||
"fontFamily",
|
|
||||||
"fontSize",
|
|
||||||
"fontWeight",
|
|
||||||
"fontWeightBold",
|
|
||||||
"foregroundColor",
|
|
||||||
"imageSupport",
|
|
||||||
"letterSpacing",
|
|
||||||
"lineHeight",
|
|
||||||
"macOptionSelectionMode",
|
|
||||||
"padding",
|
|
||||||
"preserveCWD",
|
|
||||||
"quickEdit",
|
|
||||||
"screenReaderMode",
|
|
||||||
"scrollback",
|
|
||||||
"selectionColor",
|
|
||||||
"shell",
|
|
||||||
"shellArgs",
|
|
||||||
"showHamburgerMenu",
|
|
||||||
"showWindowControls",
|
|
||||||
"termCSS",
|
|
||||||
"webGLRenderer",
|
|
||||||
"webLinksActivationKey",
|
|
||||||
"workingDirectory"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"properties": {
|
|
||||||
"defaultProfile": {
|
|
||||||
"description": "The default profile name to use when launching a new session",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"profiles": {
|
|
||||||
"description": "A list of profiles to use",
|
|
||||||
"items": {
|
|
||||||
"properties": {
|
|
||||||
"config": {
|
|
||||||
"$ref": "#/definitions/Partial<profileConfigOptions>",
|
|
||||||
"description": "Specify all the options you want to override for each profile.\nOptions set here override the defaults set in the root."
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"config",
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"defaultProfile",
|
|
||||||
"profiles"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"config": {
|
|
||||||
"$ref": "#/definitions/configOptions"
|
|
||||||
},
|
|
||||||
"keymaps": {
|
"keymaps": {
|
||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import type {BrowserWindow} from 'electron';
|
|
||||||
|
|
||||||
import Config from 'electron-store';
|
import Config from 'electron-store';
|
||||||
|
import {BrowserWindow} from 'electron';
|
||||||
|
|
||||||
export const defaults = {
|
export const defaults = {
|
||||||
windowPosition: [50, 50] as [number, number],
|
windowPosition: [50, 50] as [number, number],
|
||||||
|
|
|
||||||
8
app/ext-modules.d.ts
vendored
Normal file
8
app/ext-modules.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
declare module 'git-describe' {
|
||||||
|
export function gitDescribe(...args: any[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'default-shell' {
|
||||||
|
const val: string;
|
||||||
|
export default val;
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
import type {Server} from '../app/rpc';
|
import type {Server} from './rpc';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace Electron {
|
namespace Electron {
|
||||||
interface App {
|
interface App {
|
||||||
config: typeof import('../app/config');
|
config: typeof import('./config');
|
||||||
plugins: typeof import('../app/plugins');
|
plugins: typeof import('./plugins');
|
||||||
getWindows: () => Set<BrowserWindow>;
|
getWindows: () => Set<BrowserWindow>;
|
||||||
getLastFocusedWindow: () => BrowserWindow | null;
|
getLastFocusedWindow: () => BrowserWindow | null;
|
||||||
windowCallback?: (win: BrowserWindow) => void;
|
windowCallback?: (win: BrowserWindow) => void;
|
||||||
createWindow: (
|
createWindow: (
|
||||||
fn?: (win: BrowserWindow) => void,
|
fn?: (win: BrowserWindow) => void,
|
||||||
options?: {size?: [number, number]; position?: [number, number]},
|
options?: {size?: [number, number]; position?: [number, number]}
|
||||||
profileName?: string
|
|
||||||
) => BrowserWindow;
|
) => BrowserWindow;
|
||||||
setVersion: (version: string) => void;
|
setVersion: (version: string) => void;
|
||||||
}
|
}
|
||||||
|
|
@ -23,7 +22,6 @@ declare global {
|
||||||
focusTime: number;
|
focusTime: number;
|
||||||
clean: () => void;
|
clean: () => void;
|
||||||
rpc: Server;
|
rpc: Server;
|
||||||
profileName: string;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1
app/index.d.ts
vendored
Normal file
1
app/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
// Dummy file, required by tsc
|
||||||
44
app/index.ts
44
app/index.ts
|
|
@ -1,4 +1,3 @@
|
||||||
// eslint-disable-next-line import/order
|
|
||||||
import {cfgPath} from './config/paths';
|
import {cfgPath} from './config/paths';
|
||||||
|
|
||||||
// Print diagnostic information for a few arguments instead of running Hyper.
|
// Print diagnostic information for a few arguments instead of running Hyper.
|
||||||
|
|
@ -12,29 +11,25 @@ if (['--help', '-v', '--version'].includes(process.argv[1])) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable remote module
|
// Enable remote module
|
||||||
// eslint-disable-next-line import/order
|
|
||||||
import {initialize as remoteInitialize} from '@electron/remote/main';
|
import {initialize as remoteInitialize} from '@electron/remote/main';
|
||||||
remoteInitialize();
|
remoteInitialize();
|
||||||
|
|
||||||
// set up config
|
|
||||||
// eslint-disable-next-line import/order
|
|
||||||
import * as config from './config';
|
|
||||||
config.setup();
|
|
||||||
|
|
||||||
// Native
|
// Native
|
||||||
import {resolve} from 'path';
|
import {resolve} from 'path';
|
||||||
|
|
||||||
// Packages
|
// Packages
|
||||||
import {app, BrowserWindow, Menu, screen} from 'electron';
|
import {app, BrowserWindow, Menu, screen} from 'electron';
|
||||||
|
|
||||||
import isDev from 'electron-is-dev';
|
|
||||||
import {gitDescribe} from 'git-describe';
|
import {gitDescribe} from 'git-describe';
|
||||||
import parseUrl from 'parse-url';
|
import isDev from 'electron-is-dev';
|
||||||
|
import * as config from './config';
|
||||||
|
|
||||||
|
// set up config
|
||||||
|
config.setup();
|
||||||
|
|
||||||
import * as AppMenu from './menus/menu';
|
|
||||||
import * as plugins from './plugins';
|
import * as plugins from './plugins';
|
||||||
import {newWindow} from './ui/window';
|
|
||||||
import {installCLI} from './utils/cli-install';
|
import {installCLI} from './utils/cli-install';
|
||||||
|
import * as AppMenu from './menus/menu';
|
||||||
|
import {newWindow} from './ui/window';
|
||||||
import * as windowUtils from './utils/window-utils';
|
import * as windowUtils from './utils/window-utils';
|
||||||
|
|
||||||
const windowSet = new Set<BrowserWindow>([]);
|
const windowSet = new Set<BrowserWindow>([]);
|
||||||
|
|
@ -62,7 +57,7 @@ if (isDev) {
|
||||||
console.log('running in dev mode');
|
console.log('running in dev mode');
|
||||||
|
|
||||||
// Override default appVersion which is set from package.json
|
// Override default appVersion which is set from package.json
|
||||||
gitDescribe({customArguments: ['--tags']}, (error: any, gitInfo: {raw: string}) => {
|
gitDescribe({customArguments: ['--tags']}, (error: any, gitInfo: any) => {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
app.setVersion(gitInfo.raw);
|
app.setVersion(gitInfo.raw);
|
||||||
}
|
}
|
||||||
|
|
@ -78,13 +73,15 @@ async function installDevExtensions(isDev_: boolean) {
|
||||||
if (!isDev_) {
|
if (!isDev_) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const {default: installer, REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS} = await import('electron-devtools-installer');
|
const installer = await import('electron-devtools-installer');
|
||||||
|
|
||||||
const extensions = [REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS];
|
const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'] as const;
|
||||||
const forceDownload = Boolean(process.env.UPGRADE_EXTENSIONS);
|
const forceDownload = Boolean(process.env.UPGRADE_EXTENSIONS);
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
extensions.map((extension) => installer(extension, {forceDownload, loadExtensionOptions: {allowFileAccess: true}}))
|
extensions.map((name) =>
|
||||||
|
installer.default(installer[name], {forceDownload, loadExtensionOptions: {allowFileAccess: true}})
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,10 +91,9 @@ app.on('ready', () =>
|
||||||
.then(() => {
|
.then(() => {
|
||||||
function createWindow(
|
function createWindow(
|
||||||
fn?: (win: BrowserWindow) => void,
|
fn?: (win: BrowserWindow) => void,
|
||||||
options: {size?: [number, number]; position?: [number, number]} = {},
|
options: {size?: [number, number]; position?: [number, number]} = {}
|
||||||
profileName: string = config.getDefaultProfile()
|
|
||||||
) {
|
) {
|
||||||
const cfg = plugins.getDecoratedConfig(profileName);
|
const cfg = plugins.getDecoratedConfig();
|
||||||
|
|
||||||
const winSet = config.getWin();
|
const winSet = config.getWin();
|
||||||
let [startX, startY] = winSet.position;
|
let [startX, startY] = winSet.position;
|
||||||
|
|
@ -139,14 +135,10 @@ app.on('ready', () =>
|
||||||
[startX, startY] = config.windowDefaults.windowPosition;
|
[startX, startY] = config.windowDefaults.windowPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hwin = newWindow({width, height, x: startX, y: startY}, cfg, fn, profileName);
|
const hwin = newWindow({width, height, x: startX, y: startY}, cfg, fn);
|
||||||
windowSet.add(hwin);
|
windowSet.add(hwin);
|
||||||
void hwin.loadURL(url);
|
void hwin.loadURL(url);
|
||||||
|
|
||||||
hwin.once('ready-to-show', () => {
|
|
||||||
hwin.show();
|
|
||||||
});
|
|
||||||
|
|
||||||
// the window can be closed by the browser process itself
|
// the window can be closed by the browser process itself
|
||||||
hwin.on('close', () => {
|
hwin.on('close', () => {
|
||||||
hwin.clean();
|
hwin.clean();
|
||||||
|
|
@ -190,7 +182,7 @@ app.on('ready', () =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
app.dock?.setMenu(dockMenu);
|
app.dock.setMenu(dockMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu.setApplicationMenu(AppMenu.buildMenu(menu));
|
Menu.setApplicationMenu(AppMenu.buildMenu(menu));
|
||||||
|
|
@ -242,6 +234,6 @@ app.on('open-file', (_event, path) => {
|
||||||
|
|
||||||
app.on('open-url', (_event, sshUrl) => {
|
app.on('open-url', (_event, sshUrl) => {
|
||||||
GetWindow((win: BrowserWindow) => {
|
GetWindow((win: BrowserWindow) => {
|
||||||
win.rpc.emit('open ssh', parseUrl(sshUrl));
|
win.rpc.emit('open ssh', sshUrl);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,19 @@
|
||||||
// Packages
|
// Packages
|
||||||
import {app, dialog, Menu} from 'electron';
|
import {app, dialog, Menu, BrowserWindow} from 'electron';
|
||||||
import type {BrowserWindow} from 'electron';
|
|
||||||
|
|
||||||
// Utilities
|
// Utilities
|
||||||
import {execCommand} from '../commands';
|
|
||||||
import {getConfig} from '../config';
|
import {getConfig} from '../config';
|
||||||
import {icon} from '../config/paths';
|
import {icon} from '../config/paths';
|
||||||
import {getDecoratedKeymaps} from '../plugins';
|
|
||||||
import {getRendererTypes} from '../utils/renderer-utils';
|
|
||||||
|
|
||||||
import darwinMenu from './menus/darwin';
|
|
||||||
import editMenu from './menus/edit';
|
|
||||||
import helpMenu from './menus/help';
|
|
||||||
import shellMenu from './menus/shell';
|
|
||||||
import toolsMenu from './menus/tools';
|
|
||||||
import viewMenu from './menus/view';
|
import viewMenu from './menus/view';
|
||||||
|
import shellMenu from './menus/shell';
|
||||||
|
import editMenu from './menus/edit';
|
||||||
|
import toolsMenu from './menus/tools';
|
||||||
import windowMenu from './menus/window';
|
import windowMenu from './menus/window';
|
||||||
|
import helpMenu from './menus/help';
|
||||||
|
import darwinMenu from './menus/darwin';
|
||||||
|
import {getDecoratedKeymaps} from '../plugins';
|
||||||
|
import {execCommand} from '../commands';
|
||||||
|
import {getRendererTypes} from '../utils/renderer-utils';
|
||||||
|
|
||||||
const appName = app.name;
|
const appName = app.name;
|
||||||
const appVersion = app.getVersion();
|
const appVersion = app.getVersion();
|
||||||
|
|
@ -56,30 +54,14 @@ export const createMenu = (
|
||||||
void dialog.showMessageBox({
|
void dialog.showMessageBox({
|
||||||
title: `About ${appName}`,
|
title: `About ${appName}`,
|
||||||
message: `${appName} ${appVersion} (${updateChannel})`,
|
message: `${appName} ${appVersion} (${updateChannel})`,
|
||||||
detail: `
|
detail: `Renderers: ${renderers}\nPlugins: ${pluginList}\n\nCreated by Guillermo Rauch\nCopyright © 2022 Vercel, Inc.`,
|
||||||
Renderers: ${renderers}
|
|
||||||
Plugins: ${pluginList}
|
|
||||||
|
|
||||||
Maintained by QUINE Global
|
|
||||||
Copyright © 2025
|
|
||||||
|
|
||||||
Created by Guillermo Rauch
|
|
||||||
Copyright © 2022 Vercel, Inc.
|
|
||||||
`
|
|
||||||
.split('\n')
|
|
||||||
.map((z) => z.trim())
|
|
||||||
.join('\n'),
|
|
||||||
buttons: [],
|
buttons: [],
|
||||||
icon: icon as any
|
icon: icon as any
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const menu = [
|
const menu = [
|
||||||
...(process.platform === 'darwin' ? [darwinMenu(commandKeys, execCommand, showAbout)] : []),
|
...(process.platform === 'darwin' ? [darwinMenu(commandKeys, execCommand, showAbout)] : []),
|
||||||
shellMenu(
|
shellMenu(commandKeys, execCommand),
|
||||||
commandKeys,
|
|
||||||
(command, focusedWindow) => execCommand(command, focusedWindow as BrowserWindow | undefined),
|
|
||||||
getConfig().profiles.map((p) => p.name)
|
|
||||||
),
|
|
||||||
editMenu(commandKeys, execCommand),
|
editMenu(commandKeys, execCommand),
|
||||||
viewMenu(commandKeys, execCommand),
|
viewMenu(commandKeys, execCommand),
|
||||||
toolsMenu(commandKeys, execCommand),
|
toolsMenu(commandKeys, execCommand),
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
// This menu label is overrided by OSX to be the appName
|
// This menu label is overrided by OSX to be the appName
|
||||||
// The label is set to appName here so it matches actual behavior
|
// The label is set to appName here so it matches actual behavior
|
||||||
import {app} from 'electron';
|
import {app, BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
||||||
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
|
||||||
|
|
||||||
const darwinMenu = (
|
export default (
|
||||||
commandKeys: Record<string, string>,
|
commandKeys: Record<string, string>,
|
||||||
execCommand: (command: string, focusedWindow?: BrowserWindow) => void,
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void,
|
||||||
showAbout: () => void
|
showAbout: () => void
|
||||||
|
|
@ -55,5 +54,3 @@ const darwinMenu = (
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default darwinMenu;
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
||||||
|
|
||||||
const editMenu = (
|
export default (
|
||||||
commandKeys: Record<string, string>,
|
commandKeys: Record<string, string>,
|
||||||
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
||||||
) => {
|
) => {
|
||||||
|
|
@ -38,7 +38,7 @@ const editMenu = (
|
||||||
label: 'Select All',
|
label: 'Select All',
|
||||||
accelerator: commandKeys['editor:selectAll'],
|
accelerator: commandKeys['editor:selectAll'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:selectAll', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:selectAll', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -51,28 +51,28 @@ const editMenu = (
|
||||||
label: 'Previous word',
|
label: 'Previous word',
|
||||||
accelerator: commandKeys['editor:movePreviousWord'],
|
accelerator: commandKeys['editor:movePreviousWord'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:movePreviousWord', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:movePreviousWord', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Next word',
|
label: 'Next word',
|
||||||
accelerator: commandKeys['editor:moveNextWord'],
|
accelerator: commandKeys['editor:moveNextWord'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:moveNextWord', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:moveNextWord', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Line beginning',
|
label: 'Line beginning',
|
||||||
accelerator: commandKeys['editor:moveBeginningLine'],
|
accelerator: commandKeys['editor:moveBeginningLine'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:moveBeginningLine', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:moveBeginningLine', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Line end',
|
label: 'Line end',
|
||||||
accelerator: commandKeys['editor:moveEndLine'],
|
accelerator: commandKeys['editor:moveEndLine'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:moveEndLine', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:moveEndLine', focusedWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -84,28 +84,28 @@ const editMenu = (
|
||||||
label: 'Previous word',
|
label: 'Previous word',
|
||||||
accelerator: commandKeys['editor:deletePreviousWord'],
|
accelerator: commandKeys['editor:deletePreviousWord'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:deletePreviousWord', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:deletePreviousWord', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Next word',
|
label: 'Next word',
|
||||||
accelerator: commandKeys['editor:deleteNextWord'],
|
accelerator: commandKeys['editor:deleteNextWord'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:deleteNextWord', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:deleteNextWord', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Line beginning',
|
label: 'Line beginning',
|
||||||
accelerator: commandKeys['editor:deleteBeginningLine'],
|
accelerator: commandKeys['editor:deleteBeginningLine'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:deleteBeginningLine', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:deleteBeginningLine', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Line end',
|
label: 'Line end',
|
||||||
accelerator: commandKeys['editor:deleteEndLine'],
|
accelerator: commandKeys['editor:deleteEndLine'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:deleteEndLine', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:deleteEndLine', focusedWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -117,14 +117,14 @@ const editMenu = (
|
||||||
label: 'Clear Buffer',
|
label: 'Clear Buffer',
|
||||||
accelerator: commandKeys['editor:clearBuffer'],
|
accelerator: commandKeys['editor:clearBuffer'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:clearBuffer', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:clearBuffer', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Search',
|
label: 'Search',
|
||||||
accelerator: commandKeys['editor:search'],
|
accelerator: commandKeys['editor:search'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:search', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:search', focusedWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
@ -147,5 +147,3 @@ const editMenu = (
|
||||||
submenu
|
submenu
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default editMenu;
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
import {release} from 'os';
|
import {release} from 'os';
|
||||||
|
import {app, shell, MenuItemConstructorOptions, dialog, clipboard} from 'electron';
|
||||||
import {app, shell, dialog, clipboard} from 'electron';
|
|
||||||
import type {MenuItemConstructorOptions} from 'electron';
|
|
||||||
|
|
||||||
import {getConfig, getPlugins} from '../../config';
|
import {getConfig, getPlugins} from '../../config';
|
||||||
|
const {arch, env, platform, versions} = process;
|
||||||
import {version} from '../../package.json';
|
import {version} from '../../package.json';
|
||||||
|
|
||||||
const {arch, env, platform, versions} = process;
|
export default (commands: Record<string, string>, showAbout: () => void): MenuItemConstructorOptions => {
|
||||||
|
|
||||||
const helpMenu = (commands: Record<string, string>, showAbout: () => void): MenuItemConstructorOptions => {
|
|
||||||
const submenu: MenuItemConstructorOptions[] = [
|
const submenu: MenuItemConstructorOptions[] = [
|
||||||
{
|
{
|
||||||
label: `${app.name} Website`,
|
label: `${app.name} Website`,
|
||||||
|
|
@ -61,11 +57,11 @@ ${JSON.stringify(getPlugins(), null, 2)}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
</details>`;
|
</details>`;
|
||||||
|
|
||||||
const issueURL = `https://github.com/quine-global/hyper/issues/new?body=${encodeURIComponent(body)}`;
|
const issueURL = `https://github.com/vercel/hyper/issues/new?body=${encodeURIComponent(body)}`;
|
||||||
const copyAndSend = () => {
|
const copyAndSend = () => {
|
||||||
clipboard.writeText(body);
|
clipboard.writeText(body);
|
||||||
void shell.openExternal(
|
void shell.openExternal(
|
||||||
`https://github.com/quine-global/hyper/issues/new?body=${encodeURIComponent(
|
`https://github.com/vercel/hyper/issues/new?body=${encodeURIComponent(
|
||||||
'<!-- We have written the needed data into your clipboard because it was too large to send. ' +
|
'<!-- We have written the needed data into your clipboard because it was too large to send. ' +
|
||||||
'Please paste. -->\n'
|
'Please paste. -->\n'
|
||||||
)}`
|
)}`
|
||||||
|
|
@ -110,5 +106,3 @@ ${JSON.stringify(getPlugins(), null, 2)}
|
||||||
submenu
|
submenu
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default helpMenu;
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import type {BaseWindow, MenuItemConstructorOptions} from 'electron';
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
||||||
|
|
||||||
const shellMenu = (
|
export default (
|
||||||
commandKeys: Record<string, string>,
|
commandKeys: Record<string, string>,
|
||||||
execCommand: (command: string, focusedWindow?: BaseWindow) => void,
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
||||||
profiles: string[]
|
|
||||||
): MenuItemConstructorOptions => {
|
): MenuItemConstructorOptions => {
|
||||||
const isMac = process.platform === 'darwin';
|
const isMac = process.platform === 'darwin';
|
||||||
|
|
||||||
|
|
@ -44,47 +43,6 @@ const shellMenu = (
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
},
|
||||||
...profiles.map(
|
|
||||||
(profile): MenuItemConstructorOptions => ({
|
|
||||||
label: profile,
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'New Tab',
|
|
||||||
accelerator: commandKeys[`tab:new:${profile}`],
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
execCommand(`tab:new:${profile}`, focusedWindow);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'New Window',
|
|
||||||
accelerator: commandKeys[`window:new:${profile}`],
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
execCommand(`window:new:${profile}`, focusedWindow);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Split Down',
|
|
||||||
accelerator: commandKeys[`pane:splitDown:${profile}`],
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
execCommand(`pane:splitDown:${profile}`, focusedWindow);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Split Right',
|
|
||||||
accelerator: commandKeys[`pane:splitRight:${profile}`],
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
execCommand(`pane:splitRight:${profile}`, focusedWindow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
),
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Close',
|
label: 'Close',
|
||||||
accelerator: commandKeys['pane:close'],
|
accelerator: commandKeys['pane:close'],
|
||||||
|
|
@ -100,5 +58,3 @@ const shellMenu = (
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default shellMenu;
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
||||||
|
|
||||||
const toolsMenu = (
|
export default (
|
||||||
commands: Record<string, string>,
|
commands: Record<string, string>,
|
||||||
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
||||||
): MenuItemConstructorOptions => {
|
): MenuItemConstructorOptions => {
|
||||||
|
|
@ -45,5 +45,3 @@ const toolsMenu = (
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default toolsMenu;
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
||||||
|
|
||||||
const viewMenu = (
|
export default (
|
||||||
commandKeys: Record<string, string>,
|
commandKeys: Record<string, string>,
|
||||||
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
||||||
): MenuItemConstructorOptions => {
|
): MenuItemConstructorOptions => {
|
||||||
|
|
@ -11,21 +11,21 @@ const viewMenu = (
|
||||||
label: 'Reload',
|
label: 'Reload',
|
||||||
accelerator: commandKeys['window:reload'],
|
accelerator: commandKeys['window:reload'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('window:reload', focusedWindow as BrowserWindow);
|
execCommand('window:reload', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Full Reload',
|
label: 'Full Reload',
|
||||||
accelerator: commandKeys['window:reloadFull'],
|
accelerator: commandKeys['window:reloadFull'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('window:reloadFull', focusedWindow as BrowserWindow);
|
execCommand('window:reloadFull', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Developer Tools',
|
label: 'Developer Tools',
|
||||||
accelerator: commandKeys['window:devtools'],
|
accelerator: commandKeys['window:devtools'],
|
||||||
click: (item, focusedWindow) => {
|
click: (item, focusedWindow) => {
|
||||||
execCommand('window:devtools', focusedWindow as BrowserWindow);
|
execCommand('window:devtools', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -35,25 +35,23 @@ const viewMenu = (
|
||||||
label: 'Reset Zoom Level',
|
label: 'Reset Zoom Level',
|
||||||
accelerator: commandKeys['zoom:reset'],
|
accelerator: commandKeys['zoom:reset'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('zoom:reset', focusedWindow as BrowserWindow);
|
execCommand('zoom:reset', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Zoom In',
|
label: 'Zoom In',
|
||||||
accelerator: commandKeys['zoom:in'],
|
accelerator: commandKeys['zoom:in'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('zoom:in', focusedWindow as BrowserWindow);
|
execCommand('zoom:in', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Zoom Out',
|
label: 'Zoom Out',
|
||||||
accelerator: commandKeys['zoom:out'],
|
accelerator: commandKeys['zoom:out'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('zoom:out', focusedWindow as BrowserWindow);
|
execCommand('zoom:out', focusedWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default viewMenu;
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
||||||
|
|
||||||
const windowMenu = (
|
export default (
|
||||||
commandKeys: Record<string, string>,
|
commandKeys: Record<string, string>,
|
||||||
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
||||||
): MenuItemConstructorOptions => {
|
): MenuItemConstructorOptions => {
|
||||||
|
|
@ -37,14 +37,14 @@ const windowMenu = (
|
||||||
label: 'Previous',
|
label: 'Previous',
|
||||||
accelerator: commandKeys['tab:prev'],
|
accelerator: commandKeys['tab:prev'],
|
||||||
click: (item, focusedWindow) => {
|
click: (item, focusedWindow) => {
|
||||||
execCommand('tab:prev', focusedWindow as BrowserWindow);
|
execCommand('tab:prev', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Next',
|
label: 'Next',
|
||||||
accelerator: commandKeys['tab:next'],
|
accelerator: commandKeys['tab:next'],
|
||||||
click: (item, focusedWindow) => {
|
click: (item, focusedWindow) => {
|
||||||
execCommand('tab:next', focusedWindow as BrowserWindow);
|
execCommand('tab:next', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -63,14 +63,14 @@ const windowMenu = (
|
||||||
label: 'Previous',
|
label: 'Previous',
|
||||||
accelerator: commandKeys['pane:prev'],
|
accelerator: commandKeys['pane:prev'],
|
||||||
click: (item, focusedWindow) => {
|
click: (item, focusedWindow) => {
|
||||||
execCommand('pane:prev', focusedWindow as BrowserWindow);
|
execCommand('pane:prev', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Next',
|
label: 'Next',
|
||||||
accelerator: commandKeys['pane:next'],
|
accelerator: commandKeys['pane:next'],
|
||||||
click: (item, focusedWindow) => {
|
click: (item, focusedWindow) => {
|
||||||
execCommand('pane:next', focusedWindow as BrowserWindow);
|
execCommand('pane:next', focusedWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -84,7 +84,7 @@ const windowMenu = (
|
||||||
{
|
{
|
||||||
label: 'Toggle Always on Top',
|
label: 'Toggle Always on Top',
|
||||||
click: (item, focusedWindow) => {
|
click: (item, focusedWindow) => {
|
||||||
execCommand('window:toggleKeepOnTop', focusedWindow as BrowserWindow);
|
execCommand('window:toggleKeepOnTop', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -94,5 +94,3 @@ const windowMenu = (
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default windowMenu;
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import type {BrowserWindow} from 'electron';
|
|
||||||
|
|
||||||
import fetch from 'electron-fetch';
|
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
import fetch from 'electron-fetch';
|
||||||
import {version} from './package.json';
|
import {version} from './package.json';
|
||||||
|
import {BrowserWindow} from 'electron';
|
||||||
|
|
||||||
const NEWS_URL = 'https://hyper-news.now.sh';
|
const NEWS_URL = 'https://hyper-news.now.sh';
|
||||||
|
|
||||||
|
|
@ -24,7 +22,7 @@ export default function fetchNotifications(win: BrowserWindow) {
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
const message: {text: string; url: string; dismissable: boolean} | '' = data.message || '';
|
const {message} = data || {};
|
||||||
if (typeof message !== 'object' && message !== '') {
|
if (typeof message !== 'object' && message !== '') {
|
||||||
throw new Error('Bad response');
|
throw new Error('Bad response');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import {app, Notification} from 'electron';
|
import {app, Notification} from 'electron';
|
||||||
|
|
||||||
import {icon} from './config/paths';
|
import {icon} from './config/paths';
|
||||||
|
|
||||||
export default function notify(title: string, body = '', details: {error?: any} = {}) {
|
export default function notify(title: string, body = '', details: {error?: any} = {}) {
|
||||||
|
|
|
||||||
|
|
@ -2,50 +2,43 @@
|
||||||
"name": "hyper",
|
"name": "hyper",
|
||||||
"productName": "Hyper",
|
"productName": "Hyper",
|
||||||
"description": "A terminal built on web technologies",
|
"description": "A terminal built on web technologies",
|
||||||
"version": "4.0.0-q-canary.8",
|
"version": "4.0.0-canary.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "ZEIT, Inc.",
|
"name": "ZEIT, Inc.",
|
||||||
"email": "team@zeit.co"
|
"email": "team@zeit.co"
|
||||||
},
|
},
|
||||||
"repository": "quine-global/hyper",
|
"repository": "zeit/hyper",
|
||||||
"scripts": {
|
|
||||||
"postinstall": "npx patch-package"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "7.27.0",
|
"@babel/parser": "7.20.7",
|
||||||
"@electron/remote": "2.1.2",
|
"@electron/remote": "2.0.9",
|
||||||
"ast-types": "^0.16.1",
|
|
||||||
"async-retry": "1.3.3",
|
"async-retry": "1.3.3",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^3.5.3",
|
||||||
"color": "4.2.3",
|
"color": "4.2.3",
|
||||||
"default-shell": "1.0.1",
|
"default-shell": "1.0.1",
|
||||||
"electron-devtools-installer": "3.2.1",
|
|
||||||
"electron-fetch": "1.9.1",
|
"electron-fetch": "1.9.1",
|
||||||
"electron-is-dev": "2.0.0",
|
"electron-is-dev": "2.0.0",
|
||||||
"electron-store": "8.2.0",
|
"electron-store": "8.1.0",
|
||||||
"fs-extra": "11.3.0",
|
"fs-extra": "11.1.0",
|
||||||
"git-describe": "4.1.1",
|
"git-describe": "4.1.1",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
|
"mkdirp": "1.0.4",
|
||||||
"ms": "2.1.3",
|
"ms": "2.1.3",
|
||||||
"native-process-working-directory": "^1.0.2",
|
"native-process-working-directory": "^1.0.2",
|
||||||
"node-pty": "1.1.0-beta33",
|
"node-pty": "0.11.0-beta27",
|
||||||
"os-locale": "5.0.0",
|
"os-locale": "5.0.0",
|
||||||
"parse-url": "9.2.0",
|
"parse-url": "8.1.0",
|
||||||
|
"pify": "5.0.0",
|
||||||
"queue": "6.0.2",
|
"queue": "6.0.2",
|
||||||
"quine-electron-drag-click": "2.0.0",
|
"react": "17.0.2",
|
||||||
"react": "18.3.1",
|
"react-dom": "17.0.2",
|
||||||
"react-dom": "18.3.1",
|
"recast": "0.22.0",
|
||||||
"recast": "0.23.11",
|
"semver": "7.3.8",
|
||||||
"semver": "7.7.1",
|
|
||||||
"shell-env": "3.0.1",
|
"shell-env": "3.0.1",
|
||||||
"sudo-prompt": "^9.2.1",
|
"sudo-prompt": "^9.2.1",
|
||||||
"uuid": "10.0.0"
|
"uuid": "9.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"native-reg": "1.1.1"
|
"native-reg": "1.1.1"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"node-gyp": "^10.2.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,22 @@
|
||||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
/* eslint-disable eslint-comments/disable-enable-pair */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
import {exec, execFile} from 'child_process';
|
import {app, dialog, BrowserWindow, App, ipcMain} from 'electron';
|
||||||
import {writeFileSync} from 'fs';
|
|
||||||
import {resolve, basename} from 'path';
|
import {resolve, basename} from 'path';
|
||||||
import {promisify} from 'util';
|
import {writeFileSync} from 'fs';
|
||||||
|
|
||||||
import {app, dialog, ipcMain as _ipcMain} from 'electron';
|
|
||||||
import type {BrowserWindow, App, MenuItemConstructorOptions} from 'electron';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import Config from 'electron-store';
|
import Config from 'electron-store';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
import React from 'react';
|
||||||
import ReactDom from 'react-dom';
|
import ReactDom from 'react-dom';
|
||||||
|
|
||||||
import type {IpcMainWithCommands} from '../typings/common';
|
|
||||||
import type {configOptions} from '../typings/config';
|
|
||||||
|
|
||||||
import * as config from './config';
|
import * as config from './config';
|
||||||
import {plugs} from './config/paths';
|
|
||||||
import notify from './notify';
|
import notify from './notify';
|
||||||
import {availableExtensions} from './plugins/extensions';
|
import {availableExtensions} from './plugins/extensions';
|
||||||
import {install} from './plugins/install';
|
import {install} from './plugins/install';
|
||||||
|
import {plugs} from './config/paths';
|
||||||
import mapKeys from './utils/map-keys';
|
import mapKeys from './utils/map-keys';
|
||||||
|
import {configOptions} from '../lib/config';
|
||||||
|
import {promisify} from 'util';
|
||||||
|
import {exec, execFile} from 'child_process';
|
||||||
|
|
||||||
// local storage
|
// local storage
|
||||||
const cache = new Config();
|
const cache = new Config();
|
||||||
|
|
@ -211,7 +205,7 @@ function syncPackageJSON() {
|
||||||
description: 'Auto-generated from `hyper.json`!',
|
description: 'Auto-generated from `hyper.json`!',
|
||||||
private: true,
|
private: true,
|
||||||
version: '0.0.1',
|
version: '0.0.1',
|
||||||
repository: 'quine-global/hyper',
|
repository: 'vercel/hyper',
|
||||||
license: 'MIT',
|
license: 'MIT',
|
||||||
homepage: 'https://hyper.is',
|
homepage: 'https://hyper.is',
|
||||||
dependencies
|
dependencies
|
||||||
|
|
@ -282,7 +276,7 @@ function requirePlugins(): any[] {
|
||||||
const {plugins: plugins_, localPlugins} = paths;
|
const {plugins: plugins_, localPlugins} = paths;
|
||||||
|
|
||||||
const load = (path_: string) => {
|
const load = (path_: string) => {
|
||||||
let mod: Record<string, any>;
|
let mod: any;
|
||||||
try {
|
try {
|
||||||
mod = require(path_);
|
mod = require(path_);
|
||||||
const exposed = mod && Object.keys(mod).some((key) => availableExtensions.has(key));
|
const exposed = mod && Object.keys(mod).some((key) => availableExtensions.has(key));
|
||||||
|
|
@ -318,7 +312,7 @@ function requirePlugins(): any[] {
|
||||||
...localPlugins.filter((p) => basename(p) !== 'migrated-hyper3-config')
|
...localPlugins.filter((p) => basename(p) !== 'migrated-hyper3-config')
|
||||||
]
|
]
|
||||||
.map(load)
|
.map(load)
|
||||||
.filter((v): v is Record<string, any> => Boolean(v));
|
.filter((v) => Boolean(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const onApp = (app_: App) => {
|
export const onApp = (app_: App) => {
|
||||||
|
|
@ -421,7 +415,7 @@ export const getDeprecatedConfig = () => {
|
||||||
return deprecated;
|
return deprecated;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const decorateMenu = (tpl: MenuItemConstructorOptions[]) => {
|
export const decorateMenu = (tpl: any) => {
|
||||||
return decorateObject(tpl, 'decorateMenu');
|
return decorateObject(tpl, 'decorateMenu');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -429,8 +423,8 @@ export const getDecoratedEnv = (baseEnv: Record<string, string>) => {
|
||||||
return decorateObject(baseEnv, 'decorateEnv');
|
return decorateObject(baseEnv, 'decorateEnv');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDecoratedConfig = (profile: string) => {
|
export const getDecoratedConfig = () => {
|
||||||
const baseConfig = config.getProfileConfig(profile);
|
const baseConfig = config.getConfig();
|
||||||
const decoratedConfig = decorateObject(baseConfig, 'decorateConfig');
|
const decoratedConfig = decorateObject(baseConfig, 'decorateConfig');
|
||||||
const fixedConfig = config.fixConfigDefaults(decoratedConfig);
|
const fixedConfig = config.fixConfigDefaults(decoratedConfig);
|
||||||
const translatedConfig = config.htermConfigTranslate(fixedConfig);
|
const translatedConfig = config.htermConfigTranslate(fixedConfig);
|
||||||
|
|
@ -462,19 +456,12 @@ export const decorateSessionClass = <T>(Session: T): T => {
|
||||||
|
|
||||||
export {toDependencies as _toDependencies};
|
export {toDependencies as _toDependencies};
|
||||||
|
|
||||||
const ipcMain = _ipcMain as IpcMainWithCommands;
|
ipcMain.handle('child_process.exec', (event, args) => {
|
||||||
|
const {command, options} = args;
|
||||||
ipcMain.handle('child_process.exec', (event, command, options) => {
|
|
||||||
return promisify(exec)(command, options);
|
return promisify(exec)(command, options);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('child_process.execFile', (event, file, args, options) => {
|
ipcMain.handle('child_process.execFile', (event, _args) => {
|
||||||
|
const {file, args, options} = _args;
|
||||||
return promisify(execFile)(file, args, options);
|
return promisify(execFile)(file, args, options);
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('getLoadedPluginVersions', () => getLoadedPluginVersions());
|
|
||||||
ipcMain.handle('getPaths', () => getPaths());
|
|
||||||
ipcMain.handle('getBasePaths', () => getBasePaths());
|
|
||||||
ipcMain.handle('getDeprecatedConfig', () => getDeprecatedConfig());
|
|
||||||
ipcMain.handle('getDecoratedConfig', (e, profile) => getDecoratedConfig(profile));
|
|
||||||
ipcMain.handle('getDecoratedKeymaps', () => getDecoratedKeymaps());
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import cp from 'child_process';
|
import cp from 'child_process';
|
||||||
|
|
||||||
import ms from 'ms';
|
|
||||||
import queue from 'queue';
|
import queue from 'queue';
|
||||||
|
import ms from 'ms';
|
||||||
import {yarn, plugs} from '../config/paths';
|
import {yarn, plugs} from '../config/paths';
|
||||||
|
|
||||||
export const install = (fn: (err: string | null) => void) => {
|
export const install = (fn: (err: string | null) => void) => {
|
||||||
|
|
|
||||||
46
app/rpc.ts
46
app/rpc.ts
|
|
@ -1,22 +1,15 @@
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
|
import {ipcMain, BrowserWindow} from 'electron';
|
||||||
import {ipcMain} from 'electron';
|
|
||||||
import type {BrowserWindow, IpcMainEvent} from 'electron';
|
|
||||||
|
|
||||||
import {v4 as uuidv4} from 'uuid';
|
import {v4 as uuidv4} from 'uuid';
|
||||||
|
|
||||||
import type {TypedEmitter, MainEvents, RendererEvents, FilterNever} from '../typings/common';
|
export class Server extends EventEmitter {
|
||||||
|
|
||||||
export class Server {
|
|
||||||
emitter: TypedEmitter<MainEvents>;
|
|
||||||
destroyed = false;
|
destroyed = false;
|
||||||
win: BrowserWindow;
|
win: BrowserWindow;
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
constructor(win: BrowserWindow) {
|
constructor(win: BrowserWindow) {
|
||||||
this.emitter = new EventEmitter();
|
super();
|
||||||
this.win = win;
|
this.win = win;
|
||||||
this.emit = this.emit.bind(this);
|
this.ipcListener = this.ipcListener.bind(this);
|
||||||
|
|
||||||
if (this.destroyed) {
|
if (this.destroyed) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -25,13 +18,14 @@ export class Server {
|
||||||
const uid = uuidv4();
|
const uid = uuidv4();
|
||||||
this.id = uid;
|
this.id = uid;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
ipcMain.on(uid, this.ipcListener);
|
ipcMain.on(uid, this.ipcListener);
|
||||||
|
|
||||||
// we intentionally subscribe to `on` instead of `once`
|
// we intentionally subscribe to `on` instead of `once`
|
||||||
// to support reloading the window and re-initializing
|
// to support reloading the window and re-initializing
|
||||||
// the channel
|
// the channel
|
||||||
this.wc.on('did-finish-load', () => {
|
this.wc.on('did-finish-load', () => {
|
||||||
this.wc.send('init', uid, win.profileName);
|
this.wc.send('init', uid);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,35 +33,23 @@ export class Server {
|
||||||
return this.win.webContents;
|
return this.win.webContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcListener = <U extends keyof MainEvents>(event: IpcMainEvent, {ev, data}: {ev: U; data: MainEvents[U]}) =>
|
ipcListener(event: any, {ev, data}: {ev: string; data: any}) {
|
||||||
this.emitter.emit(ev, data);
|
super.emit(ev, data);
|
||||||
|
}
|
||||||
|
|
||||||
on = <U extends keyof MainEvents>(ev: U, fn: (arg0: MainEvents[U]) => void) => {
|
emit(ch: string, data: any = {}): any {
|
||||||
this.emitter.on(ev, fn);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
once = <U extends keyof MainEvents>(ev: U, fn: (arg0: MainEvents[U]) => void) => {
|
|
||||||
this.emitter.once(ev, fn);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
emit<U extends Exclude<keyof RendererEvents, FilterNever<RendererEvents>>>(ch: U): boolean;
|
|
||||||
emit<U extends FilterNever<RendererEvents>>(ch: U, data: RendererEvents[U]): boolean;
|
|
||||||
emit<U extends keyof RendererEvents>(ch: U, data?: RendererEvents[U]) {
|
|
||||||
// This check is needed because data-batching can cause extra data to be
|
// This check is needed because data-batching can cause extra data to be
|
||||||
// emitted after the window has already closed
|
// emitted after the window has already closed
|
||||||
if (!this.win.isDestroyed()) {
|
if (!this.win.isDestroyed()) {
|
||||||
this.wc.send(this.id, {ch, data});
|
this.wc.send(this.id, {ch, data});
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.emitter.removeAllListeners();
|
this.removeAllListeners();
|
||||||
this.wc.removeAllListeners();
|
this.wc.removeAllListeners();
|
||||||
if (this.id) {
|
if (this.id) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
ipcMain.removeListener(this.id, this.ipcListener);
|
ipcMain.removeListener(this.id, this.ipcListener);
|
||||||
} else {
|
} else {
|
||||||
// mark for `genUid` in constructor
|
// mark for `genUid` in constructor
|
||||||
|
|
@ -76,8 +58,6 @@ export class Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createRPC = (win: BrowserWindow) => {
|
export default (win: BrowserWindow) => {
|
||||||
return new Server(win);
|
return new Server(win);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createRPC;
|
|
||||||
|
|
|
||||||
103
app/session.ts
103
app/session.ts
|
|
@ -1,17 +1,14 @@
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
import {dirname} from 'path';
|
|
||||||
import {StringDecoder} from 'string_decoder';
|
import {StringDecoder} from 'string_decoder';
|
||||||
|
|
||||||
import defaultShell from 'default-shell';
|
import defaultShell from 'default-shell';
|
||||||
import type {IPty, IWindowsPtyForkOptions, spawn as npSpawn} from 'node-pty';
|
|
||||||
import osLocale from 'os-locale';
|
|
||||||
import shellEnv from 'shell-env';
|
|
||||||
|
|
||||||
import * as config from './config';
|
|
||||||
import {cliScriptPath} from './config/paths';
|
|
||||||
import {productName, version} from './package.json';
|
|
||||||
import {getDecoratedEnv} from './plugins';
|
import {getDecoratedEnv} from './plugins';
|
||||||
import {getFallBackShellConfig} from './utils/shell-fallback';
|
import {productName, version} from './package.json';
|
||||||
|
import * as config from './config';
|
||||||
|
import {IPty, IWindowsPtyForkOptions, spawn as npSpawn} from 'node-pty';
|
||||||
|
import {cliScriptPath} from './config/paths';
|
||||||
|
import {dirname} from 'path';
|
||||||
|
import shellEnv from 'shell-env';
|
||||||
|
import osLocale from 'os-locale';
|
||||||
|
|
||||||
const createNodePtyError = () =>
|
const createNodePtyError = () =>
|
||||||
new Error(
|
new Error(
|
||||||
|
|
@ -26,6 +23,7 @@ try {
|
||||||
throw createNodePtyError();
|
throw createNodePtyError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const envFromConfig = config.getConfig().env || {};
|
||||||
const useConpty = config.getConfig().useConpty;
|
const useConpty = config.getConfig().useConpty;
|
||||||
|
|
||||||
// Max duration to batch session data before sending it to the renderer process.
|
// Max duration to batch session data before sending it to the renderer process.
|
||||||
|
|
@ -58,7 +56,7 @@ class DataBatcher extends EventEmitter {
|
||||||
this.timeout = null;
|
this.timeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
write(chunk: Buffer | string) {
|
write(chunk: Buffer) {
|
||||||
if (this.data.length + chunk.length >= BATCH_MAX_SIZE) {
|
if (this.data.length + chunk.length >= BATCH_MAX_SIZE) {
|
||||||
// We've reached the max batch size. Flush it and start another one
|
// We've reached the max batch size. Flush it and start another one
|
||||||
if (this.timeout) {
|
if (this.timeout) {
|
||||||
|
|
@ -68,7 +66,7 @@ class DataBatcher extends EventEmitter {
|
||||||
this.flush();
|
this.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.data += typeof chunk === 'string' ? chunk : this.decoder.write(chunk);
|
this.data += this.decoder.write(chunk);
|
||||||
|
|
||||||
if (!this.timeout) {
|
if (!this.timeout) {
|
||||||
this.timeout = setTimeout(() => this.flush(), BATCH_DURATION_MS);
|
this.timeout = setTimeout(() => this.flush(), BATCH_DURATION_MS);
|
||||||
|
|
@ -86,12 +84,11 @@ class DataBatcher extends EventEmitter {
|
||||||
|
|
||||||
interface SessionOptions {
|
interface SessionOptions {
|
||||||
uid: string;
|
uid: string;
|
||||||
rows?: number;
|
rows: number;
|
||||||
cols?: number;
|
cols: number;
|
||||||
cwd?: string;
|
cwd: string;
|
||||||
shell?: string;
|
shell: string;
|
||||||
shellArgs?: string[];
|
shellArgs: string[];
|
||||||
profile: string;
|
|
||||||
}
|
}
|
||||||
export default class Session extends EventEmitter {
|
export default class Session extends EventEmitter {
|
||||||
pty: IPty | null;
|
pty: IPty | null;
|
||||||
|
|
@ -99,7 +96,6 @@ export default class Session extends EventEmitter {
|
||||||
shell: string | null;
|
shell: string | null;
|
||||||
ended: boolean;
|
ended: boolean;
|
||||||
initTimestamp: number;
|
initTimestamp: number;
|
||||||
profile!: string;
|
|
||||||
constructor(options: SessionOptions) {
|
constructor(options: SessionOptions) {
|
||||||
super();
|
super();
|
||||||
this.pty = null;
|
this.pty = null;
|
||||||
|
|
@ -110,25 +106,22 @@ export default class Session extends EventEmitter {
|
||||||
this.init(options);
|
this.init(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
init({uid, rows, cols, cwd, shell: _shell, shellArgs: _shellArgs, profile}: SessionOptions) {
|
init({uid, rows, cols: columns, cwd, shell: _shell, shellArgs: _shellArgs}: SessionOptions) {
|
||||||
this.profile = profile;
|
|
||||||
const envFromConfig = config.getProfileConfig(profile).env || {};
|
|
||||||
const defaultShellArgs = ['--login'];
|
|
||||||
|
|
||||||
const shell = _shell || defaultShell;
|
|
||||||
const shellArgs = _shellArgs || defaultShellArgs;
|
|
||||||
|
|
||||||
const cleanEnv =
|
const cleanEnv =
|
||||||
process.env['APPIMAGE'] && process.env['APPDIR'] ? shellEnv.sync(_shell || defaultShell) : process.env;
|
process.env['APPIMAGE'] && process.env['APPDIR'] ? shellEnv.sync(_shell || defaultShell) : process.env;
|
||||||
const baseEnv: Record<string, string> = {
|
const baseEnv = Object.assign(
|
||||||
...cleanEnv,
|
{},
|
||||||
LANG: `${osLocale.sync().replace(/-/, '_')}.UTF-8`,
|
cleanEnv,
|
||||||
TERM: 'xterm-256color',
|
{
|
||||||
COLORTERM: 'truecolor',
|
LANG: `${osLocale.sync().replace(/-/, '_')}.UTF-8`,
|
||||||
TERM_PROGRAM: productName,
|
TERM: 'xterm-256color',
|
||||||
TERM_PROGRAM_VERSION: version,
|
COLORTERM: 'truecolor',
|
||||||
...envFromConfig
|
TERM_PROGRAM: productName,
|
||||||
};
|
TERM_PROGRAM_VERSION: version
|
||||||
|
},
|
||||||
|
envFromConfig
|
||||||
|
);
|
||||||
|
|
||||||
// path to AppImage mount point is added to PATH environment variable automatically
|
// path to AppImage mount point is added to PATH environment variable automatically
|
||||||
// which conflicts with the cli
|
// which conflicts with the cli
|
||||||
if (baseEnv['APPIMAGE'] && baseEnv['APPDIR']) {
|
if (baseEnv['APPIMAGE'] && baseEnv['APPDIR']) {
|
||||||
|
|
@ -144,8 +137,10 @@ export default class Session extends EventEmitter {
|
||||||
delete baseEnv.GOOGLE_API_KEY;
|
delete baseEnv.GOOGLE_API_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultShellArgs = ['--login'];
|
||||||
|
|
||||||
const options: IWindowsPtyForkOptions = {
|
const options: IWindowsPtyForkOptions = {
|
||||||
cols,
|
cols: columns,
|
||||||
rows,
|
rows,
|
||||||
cwd,
|
cwd,
|
||||||
env: getDecoratedEnv(baseEnv)
|
env: getDecoratedEnv(baseEnv)
|
||||||
|
|
@ -156,6 +151,9 @@ export default class Session extends EventEmitter {
|
||||||
options.useConpty = useConpty;
|
options.useConpty = useConpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shell = _shell || defaultShell;
|
||||||
|
const shellArgs = _shellArgs || defaultShellArgs;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.pty = spawn(shell, shellArgs, options);
|
this.pty = spawn(shell, shellArgs, options);
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
|
|
@ -172,7 +170,7 @@ export default class Session extends EventEmitter {
|
||||||
if (this.ended) {
|
if (this.ended) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.batcher?.write(chunk);
|
this.batcher?.write(chunk as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.batcher.on('flush', (data: string) => {
|
this.batcher.on('flush', (data: string) => {
|
||||||
|
|
@ -185,32 +183,15 @@ export default class Session extends EventEmitter {
|
||||||
// this will inform users in case there are errors in the config instead of instant exit
|
// this will inform users in case there are errors in the config instead of instant exit
|
||||||
const runDuration = new Date().getTime() - this.initTimestamp;
|
const runDuration = new Date().getTime() - this.initTimestamp;
|
||||||
if (e.exitCode > 0 && runDuration < 1000) {
|
if (e.exitCode > 0 && runDuration < 1000) {
|
||||||
const fallBackShellConfig = getFallBackShellConfig(shell, shellArgs, defaultShell, defaultShellArgs);
|
const defaultShellConfig = {shell: defaultShell, shellArgs: defaultShellArgs};
|
||||||
if (fallBackShellConfig) {
|
const msg = `
|
||||||
const msg = `
|
|
||||||
shell exited in ${runDuration} ms with exit code ${e.exitCode}
|
shell exited in ${runDuration} ms with exit code ${e.exitCode}
|
||||||
please check the shell config: ${JSON.stringify({shell, shellArgs}, undefined, 2)}
|
please check the shell config: ${JSON.stringify({shell, shellArgs}, undefined, 2)}
|
||||||
using fallback shell config: ${JSON.stringify(fallBackShellConfig, undefined, 2)}
|
fallback to default shell config: ${JSON.stringify(defaultShellConfig, undefined, 2)}
|
||||||
`;
|
`;
|
||||||
console.warn(msg);
|
console.warn(msg);
|
||||||
this.batcher?.write(msg.replace(/\n/g, '\r\n'));
|
this.batcher?.write(msg.replace(/\n/g, '\r\n') as any);
|
||||||
this.init({
|
this.init({uid, rows, cols: columns, cwd, ...defaultShellConfig});
|
||||||
uid,
|
|
||||||
rows,
|
|
||||||
cols,
|
|
||||||
cwd,
|
|
||||||
shell: fallBackShellConfig.shell,
|
|
||||||
shellArgs: fallBackShellConfig.shellArgs,
|
|
||||||
profile
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const msg = `
|
|
||||||
shell exited in ${runDuration} ms with exit code ${e.exitCode}
|
|
||||||
No fallback available, please check the shell config.
|
|
||||||
`;
|
|
||||||
console.warn(msg);
|
|
||||||
this.batcher?.write(msg.replace(/\n/g, '\r\n'));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.ended = true;
|
this.ended = true;
|
||||||
this.emit('exit');
|
this.emit('exit');
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,12 @@
|
||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
"extends": "../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"declaration": true,
|
|
||||||
"declarationDir": "../dist/tmp/appdts/",
|
"declarationDir": "../dist/tmp/appdts/",
|
||||||
"outDir": "../target/",
|
"outDir": "../target/",
|
||||||
"composite": true,
|
|
||||||
"noImplicitAny": false
|
"noImplicitAny": false
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./**/*",
|
"./**/*",
|
||||||
"./package.json",
|
"./package.json"
|
||||||
"../typings/extend-electron.d.ts",
|
|
||||||
"../typings/ext-modules.d.ts"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"../dist/**/*",
|
|
||||||
"../target/**/*"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
import type {MenuItemConstructorOptions, BrowserWindow} from 'electron';
|
|
||||||
|
|
||||||
import {execCommand} from '../commands';
|
|
||||||
import {getProfiles} from '../config';
|
|
||||||
import editMenu from '../menus/menus/edit';
|
import editMenu from '../menus/menus/edit';
|
||||||
import shellMenu from '../menus/menus/shell';
|
import shellMenu from '../menus/menus/shell';
|
||||||
|
import {execCommand} from '../commands';
|
||||||
import {getDecoratedKeymaps} from '../plugins';
|
import {getDecoratedKeymaps} from '../plugins';
|
||||||
|
import {MenuItemConstructorOptions, BrowserWindow} from 'electron';
|
||||||
const separator: MenuItemConstructorOptions = {type: 'separator'};
|
const separator: MenuItemConstructorOptions = {type: 'separator'};
|
||||||
|
|
||||||
const getCommandKeys = (keymaps: Record<string, string[]>): Record<string, string> =>
|
const getCommandKeys = (keymaps: Record<string, string[]>): Record<string, string> =>
|
||||||
|
|
@ -23,20 +20,14 @@ const filterCutCopy = (selection: string, menuItem: MenuItemConstructorOptions)
|
||||||
return menuItem;
|
return menuItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
const contextMenuTemplate = (
|
export default (
|
||||||
createWindow: (fn?: (win: BrowserWindow) => void, options?: Record<string, any>) => BrowserWindow,
|
createWindow: (fn?: (win: BrowserWindow) => void, options?: Record<string, any>) => BrowserWindow,
|
||||||
selection: string
|
selection: string
|
||||||
) => {
|
) => {
|
||||||
const commandKeys = getCommandKeys(getDecoratedKeymaps());
|
const commandKeys = getCommandKeys(getDecoratedKeymaps());
|
||||||
const _shell = shellMenu(
|
const _shell = shellMenu(commandKeys, execCommand).submenu as MenuItemConstructorOptions[];
|
||||||
commandKeys,
|
|
||||||
(command, focusedWindow) => execCommand(command, focusedWindow as BrowserWindow | undefined),
|
|
||||||
getProfiles().map((p) => p.name)
|
|
||||||
).submenu as MenuItemConstructorOptions[];
|
|
||||||
const _edit = editMenu(commandKeys, execCommand).submenu.filter(filterCutCopy.bind(null, selection));
|
const _edit = editMenu(commandKeys, execCommand).submenu.filter(filterCutCopy.bind(null, selection));
|
||||||
return _edit
|
return _edit
|
||||||
.concat(separator, _shell)
|
.concat(separator, _shell)
|
||||||
.filter((menuItem) => !Object.prototype.hasOwnProperty.call(menuItem, 'enabled') || menuItem.enabled);
|
.filter((menuItem) => !Object.prototype.hasOwnProperty.call(menuItem, 'enabled') || menuItem.enabled);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default contextMenuTemplate;
|
|
||||||
|
|
|
||||||
159
app/ui/window.ts
159
app/ui/window.ts
|
|
@ -1,41 +1,28 @@
|
||||||
import {existsSync} from 'fs';
|
import {app, BrowserWindow, shell, Menu, BrowserWindowConstructorOptions, Event} from 'electron';
|
||||||
import {isAbsolute, normalize, sep} from 'path';
|
import {isAbsolute, normalize, sep} from 'path';
|
||||||
import {URL, fileURLToPath} from 'url';
|
import {URL, fileURLToPath} from 'url';
|
||||||
|
|
||||||
import {app, BrowserWindow, shell, Menu} from 'electron';
|
|
||||||
import type {BrowserWindowConstructorOptions} from 'electron';
|
|
||||||
|
|
||||||
import {enable as remoteEnable} from '@electron/remote/main';
|
|
||||||
import isDev from 'electron-is-dev';
|
|
||||||
import {getWorkingDirectoryFromPID} from 'native-process-working-directory';
|
|
||||||
import electronDragClick from 'quine-electron-drag-click';
|
|
||||||
import {v4 as uuidv4} from 'uuid';
|
import {v4 as uuidv4} from 'uuid';
|
||||||
|
import isDev from 'electron-is-dev';
|
||||||
import type {sessionExtraOptions} from '../../typings/common';
|
|
||||||
import type {configOptions} from '../../typings/config';
|
|
||||||
import {execCommand} from '../commands';
|
|
||||||
import {getDefaultProfile} from '../config';
|
|
||||||
import {icon, homeDirectory} from '../config/paths';
|
|
||||||
import fetchNotifications from '../notifications';
|
|
||||||
import notify from '../notify';
|
|
||||||
import {decorateSessionOptions, decorateSessionClass} from '../plugins';
|
|
||||||
import createRPC from '../rpc';
|
|
||||||
import Session from '../session';
|
|
||||||
import updater from '../updater';
|
import updater from '../updater';
|
||||||
import {setRendererType, unsetRendererType} from '../utils/renderer-utils';
|
|
||||||
import toElectronBackgroundColor from '../utils/to-electron-background-color';
|
import toElectronBackgroundColor from '../utils/to-electron-background-color';
|
||||||
|
import {icon, homeDirectory} from '../config/paths';
|
||||||
|
import createRPC from '../rpc';
|
||||||
|
import notify from '../notify';
|
||||||
|
import fetchNotifications from '../notifications';
|
||||||
|
import Session from '../session';
|
||||||
import contextMenuTemplate from './contextmenu';
|
import contextMenuTemplate from './contextmenu';
|
||||||
|
import {execCommand} from '../commands';
|
||||||
if (process.platform === 'darwin') {
|
import {setRendererType, unsetRendererType} from '../utils/renderer-utils';
|
||||||
electronDragClick();
|
import {decorateSessionOptions, decorateSessionClass} from '../plugins';
|
||||||
}
|
import {enable as remoteEnable} from '@electron/remote/main';
|
||||||
|
import {configOptions} from '../../lib/config';
|
||||||
|
import {getWorkingDirectoryFromPID} from 'native-process-working-directory';
|
||||||
|
import {existsSync} from 'fs';
|
||||||
|
|
||||||
export function newWindow(
|
export function newWindow(
|
||||||
options_: BrowserWindowConstructorOptions,
|
options_: BrowserWindowConstructorOptions,
|
||||||
cfg: configOptions,
|
cfg: configOptions,
|
||||||
fn?: (win: BrowserWindow) => void,
|
fn?: (win: BrowserWindow) => void
|
||||||
profileName: string = getDefaultProfile()
|
|
||||||
): BrowserWindow {
|
): BrowserWindow {
|
||||||
const classOpts = Object.assign({uid: uuidv4()});
|
const classOpts = Object.assign({uid: uuidv4()});
|
||||||
app.plugins.decorateWindowClass(classOpts);
|
app.plugins.decorateWindowClass(classOpts);
|
||||||
|
|
@ -61,8 +48,6 @@ export function newWindow(
|
||||||
};
|
};
|
||||||
const window = new BrowserWindow(app.plugins.getDecoratedBrowserOptions(winOpts));
|
const window = new BrowserWindow(app.plugins.getDecoratedBrowserOptions(winOpts));
|
||||||
|
|
||||||
window.profileName = profileName;
|
|
||||||
|
|
||||||
// Enable remote module on this window
|
// Enable remote module on this window
|
||||||
remoteEnable(window.webContents);
|
remoteEnable(window.webContents);
|
||||||
|
|
||||||
|
|
@ -75,13 +60,28 @@ export function newWindow(
|
||||||
const sessions = new Map<string, Session>();
|
const sessions = new Map<string, Session>();
|
||||||
|
|
||||||
const updateBackgroundColor = () => {
|
const updateBackgroundColor = () => {
|
||||||
const cfg_ = app.plugins.getDecoratedConfig(profileName);
|
const cfg_ = app.plugins.getDecoratedConfig();
|
||||||
window.setBackgroundColor(toElectronBackgroundColor(cfg_.backgroundColor || '#000'));
|
window.setBackgroundColor(toElectronBackgroundColor(cfg_.backgroundColor || '#000'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// set working directory
|
||||||
|
let argPath = process.argv[1];
|
||||||
|
if (argPath && process.platform === 'win32') {
|
||||||
|
if (/[a-zA-Z]:"/.test(argPath)) {
|
||||||
|
argPath = argPath.replace('"', sep);
|
||||||
|
}
|
||||||
|
argPath = normalize(argPath + sep);
|
||||||
|
}
|
||||||
|
let workingDirectory = homeDirectory;
|
||||||
|
if (argPath && isAbsolute(argPath)) {
|
||||||
|
workingDirectory = argPath;
|
||||||
|
} else if (cfg.workingDirectory && isAbsolute(cfg.workingDirectory)) {
|
||||||
|
workingDirectory = cfg.workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
// config changes
|
// config changes
|
||||||
const cfgUnsubscribe = app.config.subscribe(() => {
|
const cfgUnsubscribe = app.config.subscribe(() => {
|
||||||
const cfg_ = app.plugins.getDecoratedConfig(profileName);
|
const cfg_ = app.plugins.getDecoratedConfig();
|
||||||
|
|
||||||
// notify renderer
|
// notify renderer
|
||||||
window.webContents.send('config change');
|
window.webContents.send('config change');
|
||||||
|
|
@ -124,58 +124,34 @@ export function newWindow(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function createSession(extraOptions: sessionExtraOptions = {}) {
|
function createSession(extraOptions: any = {}) {
|
||||||
const uid = uuidv4();
|
const uid = uuidv4();
|
||||||
const extraOptionsFiltered: sessionExtraOptions = {};
|
const extraOptionsFiltered: any = {};
|
||||||
Object.keys(extraOptions).forEach((key) => {
|
Object.keys(extraOptions).forEach((key) => {
|
||||||
if (extraOptions[key] !== undefined) extraOptionsFiltered[key] = extraOptions[key];
|
if (extraOptions[key] !== undefined) extraOptionsFiltered[key] = extraOptions[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
const profile = extraOptionsFiltered.profile || profileName;
|
|
||||||
const activeSession = extraOptionsFiltered.activeUid ? sessions.get(extraOptionsFiltered.activeUid) : undefined;
|
|
||||||
let cwd = '';
|
let cwd = '';
|
||||||
if (cfg.preserveCWD !== false && activeSession && activeSession.profile === profile) {
|
if (cfg.preserveCWD === undefined || cfg.preserveCWD) {
|
||||||
const activePID = activeSession.pty?.pid;
|
const activePID = extraOptionsFiltered.activeUid && sessions.get(extraOptionsFiltered.activeUid)?.pty?.pid;
|
||||||
if (activePID !== undefined) {
|
try {
|
||||||
try {
|
cwd = activePID && getWorkingDirectoryFromPID(activePID);
|
||||||
cwd = getWorkingDirectoryFromPID(activePID) || '';
|
} catch (error) {
|
||||||
} catch (error) {
|
console.error(error);
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
cwd = cwd && isAbsolute(cwd) && existsSync(cwd) ? cwd : '';
|
cwd = cwd && isAbsolute(cwd) && existsSync(cwd) ? cwd : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileCfg = app.plugins.getDecoratedConfig(profile);
|
|
||||||
|
|
||||||
// set working directory
|
|
||||||
let argPath = process.argv[1];
|
|
||||||
if (argPath && process.platform === 'win32') {
|
|
||||||
if (/[a-zA-Z]:"/.test(argPath)) {
|
|
||||||
argPath = argPath.replace('"', sep);
|
|
||||||
}
|
|
||||||
argPath = normalize(argPath + sep);
|
|
||||||
}
|
|
||||||
let workingDirectory = homeDirectory;
|
|
||||||
if (argPath && isAbsolute(argPath)) {
|
|
||||||
workingDirectory = argPath;
|
|
||||||
} else if (profileCfg.workingDirectory && isAbsolute(profileCfg.workingDirectory)) {
|
|
||||||
workingDirectory = profileCfg.workingDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the rows and cols, the wrong value of them will break layout when init create
|
// remove the rows and cols, the wrong value of them will break layout when init create
|
||||||
const defaultOptions = Object.assign(
|
const defaultOptions = Object.assign(
|
||||||
{
|
{
|
||||||
cwd: cwd || workingDirectory,
|
cwd: cwd || workingDirectory,
|
||||||
splitDirection: undefined,
|
splitDirection: undefined,
|
||||||
shell: profileCfg.shell,
|
shell: cfg.shell,
|
||||||
shellArgs: profileCfg.shellArgs && Array.from(profileCfg.shellArgs)
|
shellArgs: cfg.shellArgs && Array.from(cfg.shellArgs)
|
||||||
},
|
},
|
||||||
extraOptionsFiltered,
|
extraOptionsFiltered,
|
||||||
{
|
{uid}
|
||||||
profile: extraOptionsFiltered.profile || profileName,
|
|
||||||
uid
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
const options = decorateSessionOptions(defaultOptions);
|
const options = decorateSessionOptions(defaultOptions);
|
||||||
const DecoratedSession = decorateSessionClass(Session);
|
const DecoratedSession = decorateSessionClass(Session);
|
||||||
|
|
@ -195,8 +171,7 @@ export function newWindow(
|
||||||
splitDirection: options.splitDirection,
|
splitDirection: options.splitDirection,
|
||||||
shell: session.shell,
|
shell: session.shell,
|
||||||
pid: session.pty ? session.pty.pid : null,
|
pid: session.pty ? session.pty.pid : null,
|
||||||
activeUid: options.activeUid ?? undefined,
|
activeUid: options.activeUid
|
||||||
profile: options.profile
|
|
||||||
});
|
});
|
||||||
|
|
||||||
session.on('data', (data: string) => {
|
session.on('data', (data: string) => {
|
||||||
|
|
@ -231,8 +206,8 @@ export function newWindow(
|
||||||
session.resize({cols, rows});
|
session.resize({cols, rows});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
rpc.on('data', ({uid, data, escaped}) => {
|
rpc.on('data', ({uid, data, escaped}: {uid: string; data: string; escaped: boolean}) => {
|
||||||
const session = uid && sessions.get(uid);
|
const session = sessions.get(uid);
|
||||||
if (session) {
|
if (session) {
|
||||||
if (escaped) {
|
if (escaped) {
|
||||||
const escapedData = session.shell?.endsWith('cmd.exe')
|
const escapedData = session.shell?.endsWith('cmd.exe')
|
||||||
|
|
@ -262,12 +237,11 @@ export function newWindow(
|
||||||
// Same deal as above, grabbing the window titlebar when the window
|
// Same deal as above, grabbing the window titlebar when the window
|
||||||
// is maximized on Windows results in unmaximize, without hitting any
|
// is maximized on Windows results in unmaximize, without hitting any
|
||||||
// app buttons
|
// app buttons
|
||||||
const onGeometryChange = () => rpc.emit('windowGeometry change', {isMaximized: window.isMaximized()});
|
for (const ev of ['maximize', 'unmaximize', 'minimize', 'restore'] as any) {
|
||||||
window.on('maximize', onGeometryChange);
|
window.on(ev, () => {
|
||||||
window.on('unmaximize', onGeometryChange);
|
rpc.emit('windowGeometry change', {isMaximized: window.isMaximized()});
|
||||||
window.on('minimize', onGeometryChange);
|
});
|
||||||
window.on('restore', onGeometryChange);
|
}
|
||||||
|
|
||||||
window.on('move', () => {
|
window.on('move', () => {
|
||||||
const position = window.getPosition();
|
const position = window.getPosition();
|
||||||
rpc.emit('move', {bounds: {x: position[0], y: position[1]}});
|
rpc.emit('move', {bounds: {x: position[0], y: position[1]}});
|
||||||
|
|
@ -281,10 +255,10 @@ export function newWindow(
|
||||||
});
|
});
|
||||||
// pass on the full screen events from the window to react
|
// pass on the full screen events from the window to react
|
||||||
rpc.win.on('enter-full-screen', () => {
|
rpc.win.on('enter-full-screen', () => {
|
||||||
rpc.emit('enter full screen');
|
rpc.emit('enter full screen', {});
|
||||||
});
|
});
|
||||||
rpc.win.on('leave-full-screen', () => {
|
rpc.win.on('leave-full-screen', () => {
|
||||||
rpc.emit('leave full screen');
|
rpc.emit('leave full screen', {});
|
||||||
});
|
});
|
||||||
const deleteSessions = () => {
|
const deleteSessions = () => {
|
||||||
sessions.forEach((session, key) => {
|
sessions.forEach((session, key) => {
|
||||||
|
|
@ -302,33 +276,22 @@ export function newWindow(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDroppedURL = (url: string) => {
|
const handleDrop = (event: Event, url: string) => {
|
||||||
const protocol = typeof url === 'string' && new URL(url).protocol;
|
const protocol = typeof url === 'string' && new URL(url).protocol;
|
||||||
if (protocol === 'file:') {
|
if (protocol === 'file:') {
|
||||||
|
event.preventDefault();
|
||||||
const path = fileURLToPath(url);
|
const path = fileURLToPath(url);
|
||||||
return {uid: null, data: path, escaped: true};
|
rpc.emit('session data send', {data: path, escaped: true});
|
||||||
} else if (protocol === 'http:' || protocol === 'https:') {
|
} else if (protocol === 'http:' || protocol === 'https:') {
|
||||||
return {uid: null, data: url};
|
event.preventDefault();
|
||||||
|
rpc.emit('session data send', {data: url});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// If file is dropped onto the terminal window, navigate and new-window events are prevented
|
// If file is dropped onto the terminal window, navigate and new-window events are prevented
|
||||||
// and it's path is added to active session.
|
// and his path is added to active session.
|
||||||
window.webContents.on('will-navigate', (event, url) => {
|
window.webContents.on('will-navigate', handleDrop);
|
||||||
const data = handleDroppedURL(url);
|
window.webContents.on('new-window', handleDrop);
|
||||||
if (data) {
|
|
||||||
event.preventDefault();
|
|
||||||
rpc.emit('session data send', data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
window.webContents.setWindowOpenHandler(({url}) => {
|
|
||||||
const data = handleDroppedURL(url);
|
|
||||||
if (data) {
|
|
||||||
rpc.emit('session data send', data);
|
|
||||||
return {action: 'deny'};
|
|
||||||
}
|
|
||||||
return {action: 'allow'};
|
|
||||||
});
|
|
||||||
|
|
||||||
// expose internals to extension authors
|
// expose internals to extension authors
|
||||||
window.rpc = rpc;
|
window.rpc = rpc;
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,12 @@
|
||||||
// Packages
|
// Packages
|
||||||
import electron, {app} from 'electron';
|
import electron, {app, BrowserWindow, AutoUpdater} from 'electron';
|
||||||
import type {BrowserWindow, AutoUpdater as OriginalAutoUpdater} from 'electron';
|
|
||||||
|
|
||||||
import retry from 'async-retry';
|
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
import retry from 'async-retry';
|
||||||
|
|
||||||
// Utilities
|
// Utilities
|
||||||
import autoUpdaterLinux from './auto-updater-linux';
|
|
||||||
import {getDefaultProfile} from './config';
|
|
||||||
import {version} from './package.json';
|
import {version} from './package.json';
|
||||||
import {getDecoratedConfig} from './plugins';
|
import {getDecoratedConfig} from './plugins';
|
||||||
|
import autoUpdaterLinux from './auto-updater-linux';
|
||||||
// Necessary due to typescript not handling overloads well
|
|
||||||
type AutoUpdaterEvent =
|
|
||||||
| 'error'
|
|
||||||
| 'checking-for-update'
|
|
||||||
| 'before-quit-for-update'
|
|
||||||
| 'update-downloaded'
|
|
||||||
| 'update-available'
|
|
||||||
| 'update-not-available';
|
|
||||||
|
|
||||||
interface AutoUpdater extends OriginalAutoUpdater {
|
|
||||||
on(event: AutoUpdaterEvent, listener: Function): this;
|
|
||||||
removeListener(event: AutoUpdaterEvent, listener: Function): this;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {platform} = process;
|
const {platform} = process;
|
||||||
const isLinux = platform === 'linux';
|
const isLinux = platform === 'linux';
|
||||||
|
|
@ -32,7 +15,7 @@ const autoUpdater: AutoUpdater = isLinux ? autoUpdaterLinux : electron.autoUpdat
|
||||||
|
|
||||||
const getDecoratedConfigWithRetry = async () => {
|
const getDecoratedConfigWithRetry = async () => {
|
||||||
return await retry(() => {
|
return await retry(() => {
|
||||||
const content = getDecoratedConfig(getDefaultProfile());
|
const content = getDecoratedConfig();
|
||||||
if (!content) {
|
if (!content) {
|
||||||
throw new Error('No config content loaded');
|
throw new Error('No config content loaded');
|
||||||
}
|
}
|
||||||
|
|
@ -86,23 +69,28 @@ async function init() {
|
||||||
isInit = true;
|
isInit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updater = (win: BrowserWindow) => {
|
export default (win: BrowserWindow) => {
|
||||||
if (!isInit) {
|
if (!isInit) {
|
||||||
void init();
|
void init();
|
||||||
}
|
}
|
||||||
|
|
||||||
const {rpc} = win;
|
const {rpc} = win;
|
||||||
|
|
||||||
const onupdate = (ev: Event, releaseNotes: string, releaseName: string, date: Date, updateUrl: string) => {
|
const onupdate = (
|
||||||
const releaseUrl = updateUrl || `https://github.com/quine-global/hyper/releases/tag/${releaseName}`;
|
ev: Event,
|
||||||
rpc.emit('update available', {releaseNotes, releaseName, releaseUrl, canInstall: !isLinux});
|
releaseNotes: string,
|
||||||
|
releaseName: string,
|
||||||
|
date: Date,
|
||||||
|
updateUrl: string,
|
||||||
|
onQuitAndInstall: any
|
||||||
|
) => {
|
||||||
|
const releaseUrl = updateUrl || `https://github.com/vercel/hyper/releases/tag/${releaseName}`;
|
||||||
|
rpc.emit('update available', {releaseNotes, releaseName, releaseUrl, canInstall: !!onQuitAndInstall});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLinux) {
|
const eventName: any = isLinux ? 'update-available' : 'update-downloaded';
|
||||||
autoUpdater.on('update-available', onupdate);
|
|
||||||
} else {
|
autoUpdater.on(eventName, onupdate);
|
||||||
autoUpdater.on('update-downloaded', onupdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
rpc.once('quit and install', () => {
|
rpc.once('quit and install', () => {
|
||||||
autoUpdater.quitAndInstall();
|
autoUpdater.quitAndInstall();
|
||||||
|
|
@ -123,12 +111,6 @@ const updater = (win: BrowserWindow) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
win.on('close', () => {
|
win.on('close', () => {
|
||||||
if (isLinux) {
|
autoUpdater.removeListener(eventName, onupdate);
|
||||||
autoUpdater.removeListener('update-available', onupdate);
|
|
||||||
} else {
|
|
||||||
autoUpdater.removeListener('update-downloaded', onupdate);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default updater;
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,20 @@
|
||||||
import {existsSync, readlink, symlink} from 'fs';
|
import pify from 'pify';
|
||||||
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {promisify} from 'util';
|
import notify from '../notify';
|
||||||
|
import {cliScriptPath, cliLinkPath} from '../config/paths';
|
||||||
import {clipboard, dialog} from 'electron';
|
|
||||||
|
|
||||||
import {mkdirpSync} from 'fs-extra';
|
|
||||||
import * as Registry from 'native-reg';
|
import * as Registry from 'native-reg';
|
||||||
import type {ValueType} from 'native-reg';
|
import type {ValueType} from 'native-reg';
|
||||||
import sudoPrompt from 'sudo-prompt';
|
import sudoPrompt from 'sudo-prompt';
|
||||||
|
import {clipboard, dialog} from 'electron';
|
||||||
|
import {mkdirpSync} from 'fs-extra';
|
||||||
|
|
||||||
import {cliScriptPath, cliLinkPath} from '../config/paths';
|
const readlink = pify(fs.readlink);
|
||||||
import notify from '../notify';
|
const symlink = pify(fs.symlink);
|
||||||
|
const sudoExec = pify(sudoPrompt.exec, {multiArgs: true});
|
||||||
const readLink = promisify(readlink);
|
|
||||||
const symLink = promisify(symlink);
|
|
||||||
const sudoExec = promisify(sudoPrompt.exec);
|
|
||||||
|
|
||||||
const checkInstall = () => {
|
const checkInstall = () => {
|
||||||
return readLink(cliLinkPath)
|
return readlink(cliLinkPath)
|
||||||
.then((link) => link === cliScriptPath)
|
.then((link) => link === cliScriptPath)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
|
|
@ -35,14 +32,14 @@ const addSymlink = async (silent: boolean) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('Linking HyperCLI');
|
console.log('Linking HyperCLI');
|
||||||
if (!existsSync(path.dirname(cliLinkPath))) {
|
if (!fs.existsSync(path.dirname(cliLinkPath))) {
|
||||||
try {
|
try {
|
||||||
mkdirpSync(path.dirname(cliLinkPath));
|
mkdirpSync(path.dirname(cliLinkPath));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw `Failed to create directory ${path.dirname(cliLinkPath)} - ${err}`;
|
throw `Failed to create directory ${path.dirname(cliLinkPath)} - ${err}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await symLink(cliScriptPath, cliLinkPath);
|
await symlink(cliScriptPath, cliLinkPath);
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
const err = _err as {code: string};
|
const err = _err as {code: string};
|
||||||
// 'EINVAL' is returned by readlink,
|
// 'EINVAL' is returned by readlink,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ const generatePrefixedCommand = (command: string, shortcuts: string[]) => {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapKeys = (config: Record<string, string[] | string>) => {
|
export default (config: Record<string, string[] | string>) => {
|
||||||
return Object.keys(config).reduce((keymap: Record<string, string[]>, command: string) => {
|
return Object.keys(config).reduce((keymap: Record<string, string[]>, command: string) => {
|
||||||
if (!command) {
|
if (!command) {
|
||||||
return keymap;
|
return keymap;
|
||||||
|
|
@ -39,5 +39,3 @@ const mapKeys = (config: Record<string, string[] | string>) => {
|
||||||
return keymap;
|
return keymap;
|
||||||
}, {});
|
}, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default mapKeys;
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
export const getFallBackShellConfig = (
|
|
||||||
shell: string,
|
|
||||||
shellArgs: string[],
|
|
||||||
defaultShell: string,
|
|
||||||
defaultShellArgs: string[]
|
|
||||||
): {
|
|
||||||
shell: string;
|
|
||||||
shellArgs: string[];
|
|
||||||
} | null => {
|
|
||||||
if (shellArgs.length > 0) {
|
|
||||||
return {
|
|
||||||
shell,
|
|
||||||
shellArgs: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shell != defaultShell) {
|
|
||||||
return {
|
|
||||||
shell: defaultShell,
|
|
||||||
shellArgs: defaultShellArgs
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
@ -9,7 +9,7 @@ const regKeys = [
|
||||||
];
|
];
|
||||||
const regParts = [
|
const regParts = [
|
||||||
{key: 'command', name: '', value: `${appPath} "%V"`},
|
{key: 'command', name: '', value: `${appPath} "%V"`},
|
||||||
{name: '', value: 'Open &Hyper here'},
|
{name: '', value: 'Open Hyper here'},
|
||||||
{name: 'Icon', value: `${appPath}`}
|
{name: 'Icon', value: `${appPath}`}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import Color from 'color';
|
||||||
// returns a background color that's in hex
|
// returns a background color that's in hex
|
||||||
// format including the alpha channel (e.g.: `#00000050`)
|
// format including the alpha channel (e.g.: `#00000050`)
|
||||||
// input can be any css value (rgb, hsl, string…)
|
// input can be any css value (rgb, hsl, string…)
|
||||||
const toElectronBackgroundColor = (bgColor: string) => {
|
export default (bgColor: string) => {
|
||||||
const color = Color(bgColor);
|
const color = Color(bgColor);
|
||||||
|
|
||||||
if (color.alpha() === 1) {
|
if (color.alpha() === 1) {
|
||||||
|
|
@ -15,5 +15,3 @@ const toElectronBackgroundColor = (bgColor: string) => {
|
||||||
const alphaHex = Math.round(color.alpha() * 255).toString(16);
|
const alphaHex = Math.round(color.alpha() * 255).toString(16);
|
||||||
return `#${alphaHex}${color.hex().toString().slice(1)}`;
|
return `#${alphaHex}${color.hex().toString().slice(1)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default toElectronBackgroundColor;
|
|
||||||
|
|
|
||||||
1255
app/yarn.lock
1255
app/yarn.lock
File diff suppressed because it is too large
Load diff
|
|
@ -2,8 +2,5 @@ module.exports = {
|
||||||
files: ['test/*'],
|
files: ['test/*'],
|
||||||
extensions: ['ts'],
|
extensions: ['ts'],
|
||||||
require: ['ts-node/register/transpile-only'],
|
require: ['ts-node/register/transpile-only'],
|
||||||
timeout: '2m',
|
timeout: '30s'
|
||||||
verbose: true,
|
|
||||||
// Due to permissions issues, Windows needs cache turned off
|
|
||||||
cache: false
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
files: ['test/unit/*'],
|
files: ['test/unit/*'],
|
||||||
extensions: ['ts'],
|
extensions: ['ts'],
|
||||||
require: ['ts-node/register/transpile-only'],
|
require: ['ts-node/register/transpile-only']
|
||||||
verbose: true,
|
|
||||||
// Due to permissions issues, Windows needs cache turned off
|
|
||||||
cache: false
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
51
bin/cp-snapshot.js
vendored
51
bin/cp-snapshot.js
vendored
|
|
@ -1,7 +1,5 @@
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const fsPromises = require('fs/promises');
|
|
||||||
const {Arch} = require('electron-builder');
|
const {Arch} = require('electron-builder');
|
||||||
|
|
||||||
function copySnapshot(pathToElectron, archToCopy) {
|
function copySnapshot(pathToElectron, archToCopy) {
|
||||||
|
|
@ -11,32 +9,30 @@ function copySnapshot(pathToElectron, archToCopy) {
|
||||||
const pathToBlobV8 = path.resolve(__dirname, '..', 'cache', archToCopy, v8ContextFileName);
|
const pathToBlobV8 = path.resolve(__dirname, '..', 'cache', archToCopy, v8ContextFileName);
|
||||||
|
|
||||||
console.log('Copying v8 snapshots from', pathToBlob, 'to', pathToElectron);
|
console.log('Copying v8 snapshots from', pathToBlob, 'to', pathToElectron);
|
||||||
fs.mkdirSync(pathToElectron, { recursive: true });
|
|
||||||
fs.copyFileSync(pathToBlob, path.join(pathToElectron, snapshotFileName));
|
fs.copyFileSync(pathToBlob, path.join(pathToElectron, snapshotFileName));
|
||||||
fs.copyFileSync(pathToBlobV8, path.join(pathToElectron, v8ContextFileName));
|
fs.copyFileSync(pathToBlobV8, path.join(pathToElectron, v8ContextFileName));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPathToElectron() {
|
function getPathToElectron() {
|
||||||
const electronPath = require.resolve('electron');
|
|
||||||
|
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
case 'darwin':
|
case 'darwin':
|
||||||
return path.resolve(
|
return path.resolve(
|
||||||
electronPath,
|
__dirname,
|
||||||
'..',
|
'..',
|
||||||
'..',
|
'node_modules/electron/dist/Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources'
|
||||||
'..',
|
|
||||||
'dist/Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources'
|
|
||||||
);
|
);
|
||||||
case 'win32':
|
case 'win32':
|
||||||
case 'linux':
|
case 'linux':
|
||||||
return path.resolve(electronPath, '..', '..', '..', 'dist');
|
return path.resolve(__dirname, '..', 'node_modules', 'electron', 'dist');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getV8ContextFileName(archToCopy) {
|
function getV8ContextFileName(archToCopy) {
|
||||||
return `snapshot_blob.bin`;
|
if (process.platform === 'darwin') {
|
||||||
|
return `v8_context_snapshot${archToCopy === 'arm64' ? '.arm64' : '.x86_64'}.bin`;
|
||||||
|
} else {
|
||||||
|
return `v8_context_snapshot.bin`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.default = async (context) => {
|
exports.default = async (context) => {
|
||||||
|
|
@ -46,7 +42,6 @@ exports.default = async (context) => {
|
||||||
? `${context.appOutDir}/Hyper.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources`
|
? `${context.appOutDir}/Hyper.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources`
|
||||||
: context.appOutDir;
|
: context.appOutDir;
|
||||||
copySnapshot(pathToElectron, archToCopy);
|
copySnapshot(pathToElectron, archToCopy);
|
||||||
useLoaderScriptFix(context);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
|
|
@ -56,33 +51,3 @@ if (require.main === module) {
|
||||||
copySnapshot(pathToElectron, archToCopy);
|
copySnapshot(pathToElectron, archToCopy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// copied and modified from https://github.com/gergof/electron-builder-sandbox-fix/blob/master/lib/index.js
|
|
||||||
// copied and modified from https://github.com/Adamant-im/adamant-im/blob/7b20272a717833ffb0b49b034ab9974118fc59ec/scripts/electron/sandboxFix.js
|
|
||||||
|
|
||||||
const useLoaderScriptFix = async (params) => {
|
|
||||||
if (params.electronPlatformName !== 'linux') {
|
|
||||||
// this fix is only required on linux
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const executable = path.join(params.appOutDir, params.packager.executableName)
|
|
||||||
|
|
||||||
const loaderScript = `#!/usr/bin/env bash
|
|
||||||
set -u
|
|
||||||
SCRIPT_DIR="$( cd "$( dirname "\${BASH_SOURCE[0]}" )" && pwd )"
|
|
||||||
exec "$SCRIPT_DIR/${params.packager.executableName}.bin" "--no-sandbox" "$@"
|
|
||||||
`
|
|
||||||
|
|
||||||
try {
|
|
||||||
await fsPromises.rename(executable, executable + '.bin')
|
|
||||||
await fsPromises.writeFile(executable, loaderScript)
|
|
||||||
await fsPromises.chmod(executable, 0o755)
|
|
||||||
} catch (e) {
|
|
||||||
console.error('failed to create loader for sandbox fix: ' + e.message)
|
|
||||||
throw new Error('Failed to create loader for sandbox fix')
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('sandbox fix successfully applied')
|
|
||||||
}
|
|
||||||
43
bin/mk-snapshot.js
vendored
43
bin/mk-snapshot.js
vendored
|
|
@ -9,18 +9,7 @@ const excludedModules = {};
|
||||||
|
|
||||||
const crossArchDirs = ['clang_x86_v8_arm', 'clang_x64_v8_arm64', 'win_clang_x64'];
|
const crossArchDirs = ['clang_x86_v8_arm', 'clang_x64_v8_arm64', 'win_clang_x64'];
|
||||||
|
|
||||||
const archMap = {
|
|
||||||
x64: 'x86_64',
|
|
||||||
arm64: 'arm64'
|
|
||||||
};
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const npmConfigArch = process.env.npm_config_arch;
|
|
||||||
if (!npmConfigArch) {
|
|
||||||
throw new Error('env var npm_config_arch is not specified')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const baseDirPath = path.resolve(__dirname, '..');
|
const baseDirPath = path.resolve(__dirname, '..');
|
||||||
|
|
||||||
console.log('Creating a linked script..');
|
console.log('Creating a linked script..');
|
||||||
|
|
@ -38,25 +27,11 @@ async function main() {
|
||||||
// Verify if we will be able to use this in `mksnapshot`
|
// Verify if we will be able to use this in `mksnapshot`
|
||||||
vm.runInNewContext(result.snapshotScript, undefined, {filename: snapshotScriptPath, displayErrors: true});
|
vm.runInNewContext(result.snapshotScript, undefined, {filename: snapshotScriptPath, displayErrors: true});
|
||||||
|
|
||||||
const outputBlobPath = `${baseDirPath}/cache/${npmConfigArch}`;
|
const outputBlobPath = `${baseDirPath}/cache/${process.env.npm_config_arch}`;
|
||||||
await mkdirp(outputBlobPath);
|
await mkdirp(outputBlobPath);
|
||||||
|
|
||||||
let mksnapshotBinPath
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
mksnapshotBinPath =
|
|
||||||
require.resolve(
|
|
||||||
path.join("electron-mksnapshot", "bin", "mksnapshot.exe")
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
mksnapshotBinPath =
|
|
||||||
require.resolve(
|
|
||||||
path.join("electron-mksnapshot", "bin", "mksnapshot")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
mksnapshotBinPath = path.dirname(mksnapshotBinPath);
|
|
||||||
|
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
|
const mksnapshotBinPath = `${baseDirPath}/node_modules/electron-mksnapshot/bin`;
|
||||||
const matchingDirs = crossArchDirs.map((dir) => `${mksnapshotBinPath}/${dir}`).filter((dir) => fs.existsSync(dir));
|
const matchingDirs = crossArchDirs.map((dir) => `${mksnapshotBinPath}/${dir}`).filter((dir) => fs.existsSync(dir));
|
||||||
for (const dir of matchingDirs) {
|
for (const dir of matchingDirs) {
|
||||||
if (fs.existsSync(`${mksnapshotBinPath}/gen/v8/embedded.S`)) {
|
if (fs.existsSync(`${mksnapshotBinPath}/gen/v8/embedded.S`)) {
|
||||||
|
|
@ -65,20 +40,12 @@ async function main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const startupBlobPath = path.join(outputBlobPath, 'snapshot_blob.bin');
|
|
||||||
|
|
||||||
console.log(`Generating startup blob in "${outputBlobPath}"`);
|
console.log(`Generating startup blob in "${outputBlobPath}"`);
|
||||||
const res = childProcess.execFileSync(
|
childProcess.execFileSync(
|
||||||
require.resolve(`electron-mksnapshot/bin/mksnapshot${process.platform === 'win32' ? '.exe' : ''}`),
|
path.resolve(__dirname, '..', 'node_modules', '.bin', 'mksnapshot' + (process.platform === 'win32' ? '.cmd' : '')),
|
||||||
[
|
[snapshotScriptPath, '--output_dir', outputBlobPath]
|
||||||
'--startup-src=' + snapshotScriptPath,
|
|
||||||
'--startup-blob=' + startupBlobPath,
|
|
||||||
`--target-arch=${archMap[process.env.npm_config_arch]}`,
|
|
||||||
//'--v8-context-snapshot=' + v8SnapshotPath
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
console.log('result:', res.toString())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((err) => console.error(err));
|
main().catch((err) => console.error(err));
|
||||||
|
|
|
||||||
7
bin/notarize.js
vendored
7
bin/notarize.js
vendored
|
|
@ -1,15 +1,14 @@
|
||||||
exports.default = async function notarizing(context) {
|
const { notarize } = require("electron-notarize");
|
||||||
|
|
||||||
|
exports.default = async function notarizing(context) {
|
||||||
const { electronPlatformName, appOutDir } = context;
|
const { electronPlatformName, appOutDir } = context;
|
||||||
if (electronPlatformName !== "darwin" || !process.env.APPLE_ID || !process.env.APPLE_PASSWORD) {
|
if (electronPlatformName !== "darwin" || !process.env.APPLE_ID || !process.env.APPLE_PASSWORD) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { notarize } = await import('@electron/notarize');
|
|
||||||
|
|
||||||
const appName = context.packager.appInfo.productFilename;
|
const appName = context.packager.appInfo.productFilename;
|
||||||
return await notarize({
|
return await notarize({
|
||||||
appBundleId: "com.quineglobal.hyper",
|
appBundleId: "co.zeit.hyper",
|
||||||
appPath: `${appOutDir}/${appName}.app`,
|
appPath: `${appOutDir}/${appName}.app`,
|
||||||
appleId: process.env.APPLE_ID,
|
appleId: process.env.APPLE_ID,
|
||||||
appleIdPassword: process.env.APPLE_PASSWORD
|
appleIdPassword: process.env.APPLE_PASSWORD
|
||||||
|
|
|
||||||
22
bin/snapshot-libs.js
vendored
22
bin/snapshot-libs.js
vendored
|
|
@ -7,25 +7,25 @@ require('normalize-url');
|
||||||
require('parse-url');
|
require('parse-url');
|
||||||
require('php-escape-shell');
|
require('php-escape-shell');
|
||||||
require('plist');
|
require('plist');
|
||||||
|
require('react-deep-force-update');
|
||||||
|
require('react-dom');
|
||||||
|
require('react-redux');
|
||||||
|
require('react');
|
||||||
require('redux-thunk');
|
require('redux-thunk');
|
||||||
require('redux');
|
require('redux');
|
||||||
require('reselect');
|
require('reselect');
|
||||||
require('seamless-immutable');
|
require('seamless-immutable');
|
||||||
require('stylis');
|
require('stylis');
|
||||||
require('@xterm/addon-unicode11');
|
require('xterm-addon-unicode11');
|
||||||
// eslint-disable-next-line no-constant-condition
|
// eslint-disable-next-line no-constant-condition
|
||||||
if (false) {
|
if (false) {
|
||||||
require('args');
|
require('args');
|
||||||
require('mousetrap');
|
require('mousetrap');
|
||||||
require('open');
|
require('open');
|
||||||
require('react-dom');
|
require('xterm-addon-fit');
|
||||||
require('react-redux');
|
require('xterm-addon-ligatures');
|
||||||
require('react');
|
require('xterm-addon-search');
|
||||||
require('@xterm/addon-fit');
|
require('xterm-addon-web-links');
|
||||||
require('@xterm/addon-image');
|
require('xterm-addon-webgl');
|
||||||
require('@xterm/addon-search');
|
require('xterm');
|
||||||
require('@xterm/addon-web-links');
|
|
||||||
require('@xterm/addon-webgl');
|
|
||||||
require('@xterm/addon-canvas');
|
|
||||||
require('@xterm/xterm');
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
!macro customInstall
|
!macro customInstall
|
||||||
WriteRegStr HKCU "Software\Classes\Directory\Background\shell\Hyper" "" "Open &Hyper here"
|
WriteRegStr HKCU "Software\Classes\Directory\Background\shell\Hyper" "" "Open Hyper here"
|
||||||
WriteRegStr HKCU "Software\Classes\Directory\Background\shell\Hyper" "Icon" `"$appExe"`
|
WriteRegStr HKCU "Software\Classes\Directory\Background\shell\Hyper" "Icon" "$appExe"
|
||||||
WriteRegStr HKCU "Software\Classes\Directory\Background\shell\Hyper\command" "" `"$appExe" "%V"`
|
WriteRegStr HKCU "Software\Classes\Directory\Background\shell\Hyper\command" "" `$appExe "%V"`
|
||||||
|
|
||||||
WriteRegStr HKCU "Software\Classes\Directory\shell\Hyper" "" "Open &Hyper here"
|
WriteRegStr HKCU "Software\Classes\Directory\shell\Hyper" "" "Open Hyper here"
|
||||||
WriteRegStr HKCU "Software\Classes\Directory\shell\Hyper" "Icon" `"$appExe"`
|
WriteRegStr HKCU "Software\Classes\Directory\shell\Hyper" "Icon" "$appExe"
|
||||||
WriteRegStr HKCU "Software\Classes\Directory\shell\Hyper\command" "" `"$appExe" "%V"`
|
WriteRegStr HKCU "Software\Classes\Directory\shell\Hyper\command" "" `$appExe "%V"`
|
||||||
|
|
||||||
WriteRegStr HKCU "Software\Classes\Drive\shell\Hyper" "" "Open &Hyper here"
|
WriteRegStr HKCU "Software\Classes\Drive\shell\Hyper" "" "Open Hyper here"
|
||||||
WriteRegStr HKCU "Software\Classes\Drive\shell\Hyper" "Icon" `"$appExe"`
|
WriteRegStr HKCU "Software\Classes\Drive\shell\Hyper" "Icon" "$appExe"
|
||||||
WriteRegStr HKCU "Software\Classes\Drive\shell\Hyper\command" "" `"$appExe" "%V"`
|
WriteRegStr HKCU "Software\Classes\Drive\shell\Hyper\command" "" `$appExe "%V"`
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
!macro customUnInstall
|
!macro customUnInstall
|
||||||
|
|
@ -25,4 +25,4 @@
|
||||||
!macro customInit
|
!macro customInit
|
||||||
IfFileExists $LOCALAPPDATA\Hyper\Update.exe 0 +2
|
IfFileExists $LOCALAPPDATA\Hyper\Update.exe 0 +2
|
||||||
nsExec::Exec '"$LOCALAPPDATA\Hyper\Update.exe" --uninstall -s'
|
nsExec::Exec '"$LOCALAPPDATA\Hyper\Update.exe" --uninstall -s'
|
||||||
!macroend
|
!macroend
|
||||||
10
cli/api.ts
10
cli/api.ts
|
|
@ -2,20 +2,18 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
import registryUrlModule from 'registry-url';
|
import registryUrlModule from 'registry-url';
|
||||||
|
|
||||||
const registryUrl = registryUrlModule();
|
const registryUrl = registryUrlModule();
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
// If the user defines XDG_CONFIG_HOME they definitely want their config there,
|
// If the user defines XDG_CONFIG_HOME they definitely want their config there,
|
||||||
// otherwise use the home directory in linux/mac and userdata in windows
|
// otherwise use the home directory in linux/mac and userdata in windows
|
||||||
const applicationDirectory = process.env.XDG_CONFIG_HOME
|
const applicationDirectory = process.env.XDG_CONFIG_HOME
|
||||||
? path.join(process.env.XDG_CONFIG_HOME, 'Hyper')
|
? path.join(process.env.XDG_CONFIG_HOME, 'Hyper')
|
||||||
: process.platform === 'win32'
|
: process.platform === 'win32'
|
||||||
? path.join(process.env.APPDATA!, 'Hyper')
|
? path.join(process.env.APPDATA!, 'Hyper')
|
||||||
: path.join(os.homedir(), '.config', 'Hyper');
|
: path.join(os.homedir(), '.config', 'Hyper');
|
||||||
|
|
||||||
const devConfigFileName = path.join(__dirname, `../hyper.json`);
|
const devConfigFileName = path.join(__dirname, `../hyper.json`);
|
||||||
|
|
||||||
|
|
@ -32,7 +30,7 @@ const fileName =
|
||||||
function memoize<T extends (...args: any[]) => any>(fn: T): T {
|
function memoize<T extends (...args: any[]) => any>(fn: T): T {
|
||||||
let hasResult = false;
|
let hasResult = false;
|
||||||
let result: any;
|
let result: any;
|
||||||
return ((...args: Parameters<T>) => {
|
return ((...args: any[]) => {
|
||||||
if (!hasResult) {
|
if (!hasResult) {
|
||||||
result = fn(...args);
|
result = fn(...args);
|
||||||
hasResult = true;
|
hasResult = true;
|
||||||
|
|
|
||||||
24
cli/index.ts
24
cli/index.ts
|
|
@ -1,20 +1,16 @@
|
||||||
// This is a CLI tool, using console is OK
|
// This is a CLI tool, using console is OK
|
||||||
/* eslint no-console: 0 */
|
/* eslint no-console: 0 */
|
||||||
import {spawn, exec} from 'child_process';
|
import {spawn, exec, SpawnOptions} from 'child_process';
|
||||||
import type {SpawnOptions} from 'child_process';
|
|
||||||
import {existsSync} from 'fs';
|
|
||||||
import {isAbsolute, resolve} from 'path';
|
import {isAbsolute, resolve} from 'path';
|
||||||
import {promisify} from 'util';
|
import {existsSync} from 'fs';
|
||||||
|
import {version} from '../app/package.json';
|
||||||
|
import pify from 'pify';
|
||||||
import args from 'args';
|
import args from 'args';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
import open from 'open';
|
||||||
import _columnify from 'columnify';
|
import _columnify from 'columnify';
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
import open from 'open';
|
|
||||||
import ora from 'ora';
|
import ora from 'ora';
|
||||||
|
|
||||||
import {version} from '../app/package.json';
|
|
||||||
|
|
||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
|
|
||||||
let commandPromise: Promise<void> | undefined;
|
let commandPromise: Promise<void> | undefined;
|
||||||
|
|
@ -195,10 +191,8 @@ const main = (argv: string[]) => {
|
||||||
version: false,
|
version: false,
|
||||||
mri: {
|
mri: {
|
||||||
boolean: ['v', 'verbose']
|
boolean: ['v', 'verbose']
|
||||||
},
|
}
|
||||||
mainColor: 'yellow',
|
} as any);
|
||||||
subColor: 'dim'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (commandPromise) {
|
if (commandPromise) {
|
||||||
return commandPromise;
|
return commandPromise;
|
||||||
|
|
@ -234,11 +228,11 @@ const main = (argv: string[]) => {
|
||||||
options['stdio'] = 'ignore';
|
options['stdio'] = 'ignore';
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
//Use `open` to prevent multiple Hyper process
|
//Use `open` to prevent multiple Hyper process
|
||||||
const cmd = `open -b com.quineglobal.hyper ${args_}`;
|
const cmd = `open -b co.zeit.hyper ${args_}`;
|
||||||
const opts = {
|
const opts = {
|
||||||
env
|
env
|
||||||
};
|
};
|
||||||
return promisify(exec)(cmd, opts);
|
return pify(exec)(cmd, opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "http://json.schemastore.org/electron-builder",
|
|
||||||
"extends": "electron-builder.json",
|
|
||||||
"afterSign": null,
|
|
||||||
"npmRebuild": false
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"appId": "com.quineglobal.hyper",
|
"$schema": "http://json.schemastore.org/electron-builder",
|
||||||
|
"appId": "co.zeit.hyper",
|
||||||
"afterSign": "./bin/notarize.js",
|
"afterSign": "./bin/notarize.js",
|
||||||
"afterPack": "./bin/cp-snapshot.js",
|
"afterPack": "./bin/cp-snapshot.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
|
|
@ -16,27 +17,43 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
|
||||||
"linux": {
|
"linux": {
|
||||||
"category": "TerminalEmulator",
|
"category": "TerminalEmulator",
|
||||||
"target": [
|
"target": [
|
||||||
"deb",
|
{
|
||||||
"AppImage",
|
"target": "deb",
|
||||||
"snap",
|
"arch": [
|
||||||
"pacman"
|
"x64",
|
||||||
|
"arm64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": "AppImage",
|
||||||
|
"arch": [
|
||||||
|
"x64",
|
||||||
|
"arm64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": "rpm",
|
||||||
|
"arch": [
|
||||||
|
"x64",
|
||||||
|
"arm64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": "snap",
|
||||||
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"target": {
|
"target": [
|
||||||
"target": "nsis",
|
"nsis"
|
||||||
"arch": [
|
],
|
||||||
"x64",
|
"rfc3161TimeStampServer": "http://timestamp.comodoca.com"
|
||||||
"arm64"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"signtoolOptions": {
|
|
||||||
"timeStampServer": "http://timestamp.comodoca.com"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"include": "build/win/installer.nsh",
|
"include": "build/win/installer.nsh",
|
||||||
|
|
@ -103,6 +120,13 @@
|
||||||
"compression": "bzip2",
|
"compression": "bzip2",
|
||||||
"afterInstall": "./build/linux/after-install.tpl"
|
"afterInstall": "./build/linux/after-install.tpl"
|
||||||
},
|
},
|
||||||
|
"rpm": {
|
||||||
|
"afterInstall": "./build/linux/after-install.tpl",
|
||||||
|
"fpm": [
|
||||||
|
"--rpm-rpmbuild-define",
|
||||||
|
"_build_id_links none"
|
||||||
|
]
|
||||||
|
},
|
||||||
"snap": {
|
"snap": {
|
||||||
"confinement": "classic",
|
"confinement": "classic",
|
||||||
"publish": "github"
|
"publish": "github"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type {configOptions} from '../../typings/config';
|
import {CONFIG_LOAD, CONFIG_RELOAD} from '../constants/config';
|
||||||
import {CONFIG_LOAD, CONFIG_RELOAD} from '../../typings/constants/config';
|
import {HyperActions} from '../hyper';
|
||||||
import type {HyperActions} from '../../typings/hyper';
|
import {configOptions} from '../config';
|
||||||
|
|
||||||
export function loadConfig(config: configOptions): HyperActions {
|
export function loadConfig(config: configOptions): HyperActions {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import {CLOSE_TAB, CHANGE_TAB} from '../../typings/constants/tabs';
|
import {CLOSE_TAB, CHANGE_TAB} from '../constants/tabs';
|
||||||
import {
|
import {
|
||||||
UI_WINDOW_MAXIMIZE,
|
UI_WINDOW_MAXIMIZE,
|
||||||
UI_WINDOW_UNMAXIMIZE,
|
UI_WINDOW_UNMAXIMIZE,
|
||||||
UI_OPEN_HAMBURGER_MENU,
|
UI_OPEN_HAMBURGER_MENU,
|
||||||
UI_WINDOW_MINIMIZE,
|
UI_WINDOW_MINIMIZE,
|
||||||
UI_WINDOW_CLOSE
|
UI_WINDOW_CLOSE
|
||||||
} from '../../typings/constants/ui';
|
} from '../constants/ui';
|
||||||
import type {HyperDispatch} from '../../typings/hyper';
|
|
||||||
import rpc from '../rpc';
|
import rpc from '../rpc';
|
||||||
|
|
||||||
import {userExitTermGroup, setActiveGroup} from './term-groups';
|
import {userExitTermGroup, setActiveGroup} from './term-groups';
|
||||||
|
import {HyperDispatch} from '../hyper';
|
||||||
|
|
||||||
export function closeTab(uid: string) {
|
export function closeTab(uid: string) {
|
||||||
return (dispatch: HyperDispatch) => {
|
return (dispatch: HyperDispatch) => {
|
||||||
|
|
@ -40,7 +39,7 @@ export function maximize() {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UI_WINDOW_MAXIMIZE,
|
type: UI_WINDOW_MAXIMIZE,
|
||||||
effect() {
|
effect() {
|
||||||
rpc.emit('maximize');
|
rpc.emit('maximize', null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -51,7 +50,7 @@ export function unmaximize() {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UI_WINDOW_UNMAXIMIZE,
|
type: UI_WINDOW_UNMAXIMIZE,
|
||||||
effect() {
|
effect() {
|
||||||
rpc.emit('unmaximize');
|
rpc.emit('unmaximize', null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -73,7 +72,7 @@ export function minimize() {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UI_WINDOW_MINIMIZE,
|
type: UI_WINDOW_MINIMIZE,
|
||||||
effect() {
|
effect() {
|
||||||
rpc.emit('minimize');
|
rpc.emit('minimize', null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -84,7 +83,7 @@ export function close() {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UI_WINDOW_CLOSE,
|
type: UI_WINDOW_CLOSE,
|
||||||
effect() {
|
effect() {
|
||||||
rpc.emit('close');
|
rpc.emit('close', null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import {INIT} from '../../typings/constants';
|
|
||||||
import type {HyperDispatch} from '../../typings/hyper';
|
|
||||||
import rpc from '../rpc';
|
import rpc from '../rpc';
|
||||||
|
import {INIT} from '../constants';
|
||||||
|
import {HyperDispatch} from '../hyper';
|
||||||
|
|
||||||
export default function init() {
|
export default function init() {
|
||||||
return (dispatch: HyperDispatch) => {
|
return (dispatch: HyperDispatch) => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import {NOTIFICATION_MESSAGE, NOTIFICATION_DISMISS} from '../../typings/constants/notifications';
|
import {NOTIFICATION_MESSAGE, NOTIFICATION_DISMISS} from '../constants/notifications';
|
||||||
import type {HyperActions} from '../../typings/hyper';
|
import {HyperActions} from '../hyper';
|
||||||
|
|
||||||
export function dismissNotification(id: string): HyperActions {
|
export function dismissNotification(id: string): HyperActions {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import type {Session} from '../../typings/common';
|
import rpc from '../rpc';
|
||||||
|
import {keys} from '../utils/object';
|
||||||
|
import findBySession from '../utils/term-groups';
|
||||||
import {
|
import {
|
||||||
SESSION_ADD,
|
SESSION_ADD,
|
||||||
SESSION_RESIZE,
|
SESSION_RESIZE,
|
||||||
|
|
@ -12,13 +14,10 @@ import {
|
||||||
SESSION_USER_DATA,
|
SESSION_USER_DATA,
|
||||||
SESSION_SET_XTERM_TITLE,
|
SESSION_SET_XTERM_TITLE,
|
||||||
SESSION_SEARCH
|
SESSION_SEARCH
|
||||||
} from '../../typings/constants/sessions';
|
} from '../constants/sessions';
|
||||||
import type {HyperState, HyperDispatch, HyperActions} from '../../typings/hyper';
|
import {HyperState, session, HyperDispatch, HyperActions} from '../hyper';
|
||||||
import rpc from '../rpc';
|
|
||||||
import {keys} from '../utils/object';
|
|
||||||
import findBySession from '../utils/term-groups';
|
|
||||||
|
|
||||||
export function addSession({uid, shell, pid, cols = null, rows = null, splitDirection, activeUid, profile}: Session) {
|
export function addSession({uid, shell, pid, cols, rows, splitDirection, activeUid}: session) {
|
||||||
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
||||||
const {sessions} = getState();
|
const {sessions} = getState();
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
@ -31,20 +30,20 @@ export function addSession({uid, shell, pid, cols = null, rows = null, splitDire
|
||||||
rows,
|
rows,
|
||||||
splitDirection,
|
splitDirection,
|
||||||
activeUid: activeUid ? activeUid : sessions.activeUid,
|
activeUid: activeUid ? activeUid : sessions.activeUid,
|
||||||
now,
|
now
|
||||||
profile
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function requestSession(profile: string | undefined) {
|
export function requestSession() {
|
||||||
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SESSION_REQUEST,
|
type: SESSION_REQUEST,
|
||||||
effect: () => {
|
effect: () => {
|
||||||
const {ui} = getState();
|
const {ui} = getState();
|
||||||
const {cwd} = ui;
|
// the cols and rows from preview session maybe not accurate. so remove.
|
||||||
rpc.emit('new', {cwd, profile});
|
const {/*cols, rows,*/ cwd} = ui;
|
||||||
|
rpc.emit('new', {cwd});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -141,7 +140,7 @@ export function openSearch(uid?: string) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SESSION_SEARCH,
|
type: SESSION_SEARCH,
|
||||||
uid: targetUid,
|
uid: targetUid,
|
||||||
value: new Date()
|
value: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -153,7 +152,7 @@ export function closeSearch(uid?: string, keyEvent?: any) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SESSION_SEARCH,
|
type: SESSION_SEARCH,
|
||||||
uid: targetUid,
|
uid: targetUid,
|
||||||
value: null
|
value: false
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (keyEvent) {
|
if (keyEvent) {
|
||||||
|
|
@ -163,7 +162,7 @@ export function closeSearch(uid?: string, keyEvent?: any) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendSessionData(uid: string | null, data: string, escaped?: boolean) {
|
export function sendSessionData(uid: string | null, data: any, escaped?: boolean | null) {
|
||||||
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SESSION_USER_DATA,
|
type: SESSION_USER_DATA,
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,28 @@
|
||||||
import {SESSION_REQUEST} from '../../typings/constants/sessions';
|
import rpc from '../rpc';
|
||||||
import {
|
import {
|
||||||
DIRECTION,
|
DIRECTION,
|
||||||
TERM_GROUP_RESIZE,
|
TERM_GROUP_RESIZE,
|
||||||
TERM_GROUP_REQUEST,
|
TERM_GROUP_REQUEST,
|
||||||
TERM_GROUP_EXIT,
|
TERM_GROUP_EXIT,
|
||||||
TERM_GROUP_EXIT_ACTIVE
|
TERM_GROUP_EXIT_ACTIVE
|
||||||
} from '../../typings/constants/term-groups';
|
} from '../constants/term-groups';
|
||||||
import type {ITermState, ITermGroup, HyperState, HyperDispatch, HyperActions} from '../../typings/hyper';
|
import {SESSION_REQUEST} from '../constants/sessions';
|
||||||
import rpc from '../rpc';
|
|
||||||
import {getRootGroups} from '../selectors';
|
|
||||||
import findBySession from '../utils/term-groups';
|
import findBySession from '../utils/term-groups';
|
||||||
|
import {getRootGroups} from '../selectors';
|
||||||
import {setActiveSession, ptyExitSession, userExitSession} from './sessions';
|
import {setActiveSession, ptyExitSession, userExitSession} from './sessions';
|
||||||
|
import {ITermState, ITermGroup, HyperState, HyperDispatch, HyperActions} from '../hyper';
|
||||||
|
|
||||||
function requestSplit(direction: 'VERTICAL' | 'HORIZONTAL') {
|
function requestSplit(direction: 'VERTICAL' | 'HORIZONTAL') {
|
||||||
return (_activeUid: string | undefined, _profile: string | undefined) =>
|
return (activeUid: string) =>
|
||||||
(dispatch: HyperDispatch, getState: () => HyperState): void => {
|
(dispatch: HyperDispatch, getState: () => HyperState): void => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SESSION_REQUEST,
|
type: SESSION_REQUEST,
|
||||||
effect: () => {
|
effect: () => {
|
||||||
const {ui, sessions} = getState();
|
const {ui, sessions} = getState();
|
||||||
const activeUid = _activeUid ? _activeUid : sessions.activeUid;
|
|
||||||
const profile = _profile ? _profile : activeUid ? sessions.sessions[activeUid].profile : window.profileName;
|
|
||||||
rpc.emit('new', {
|
rpc.emit('new', {
|
||||||
splitDirection: direction,
|
splitDirection: direction,
|
||||||
cwd: ui.cwd,
|
cwd: ui.cwd,
|
||||||
activeUid,
|
activeUid: activeUid ? activeUid : sessions.activeUid
|
||||||
profile
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -44,20 +40,17 @@ export function resizeTermGroup(uid: string, sizes: number[]): HyperActions {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function requestTermGroup(_activeUid: string | undefined, _profile: string | undefined) {
|
export function requestTermGroup(activeUid: string) {
|
||||||
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: TERM_GROUP_REQUEST,
|
type: TERM_GROUP_REQUEST,
|
||||||
effect: () => {
|
effect: () => {
|
||||||
const {ui, sessions} = getState();
|
const {ui, sessions} = getState();
|
||||||
const {cwd} = ui;
|
const {cwd} = ui;
|
||||||
const activeUid = _activeUid ? _activeUid : sessions.activeUid;
|
|
||||||
const profile = _profile ? _profile : activeUid ? sessions.sessions[activeUid].profile : window.profileName;
|
|
||||||
rpc.emit('new', {
|
rpc.emit('new', {
|
||||||
isNewGroup: true,
|
isNewGroup: true,
|
||||||
cwd,
|
cwd,
|
||||||
activeUid,
|
activeUid: activeUid ? activeUid : sessions.activeUid
|
||||||
profile
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import {stat} from 'fs';
|
|
||||||
import type {Stats} from 'fs';
|
|
||||||
|
|
||||||
import type parseUrl from 'parse-url';
|
|
||||||
import {php_escapeshellcmd as escapeShellCmd} from 'php-escape-shell';
|
import {php_escapeshellcmd as escapeShellCmd} from 'php-escape-shell';
|
||||||
|
import {isExecutable} from '../utils/file';
|
||||||
|
import {getRootGroups} from '../selectors';
|
||||||
|
import findBySession from '../utils/term-groups';
|
||||||
|
import notify from '../utils/notify';
|
||||||
|
import rpc from '../rpc';
|
||||||
|
import {requestSession, sendSessionData, setActiveSession} from './sessions';
|
||||||
import {
|
import {
|
||||||
UI_FONT_SIZE_SET,
|
UI_FONT_SIZE_SET,
|
||||||
UI_FONT_SIZE_INCR,
|
UI_FONT_SIZE_INCR,
|
||||||
|
|
@ -23,18 +24,14 @@ import {
|
||||||
UI_OPEN_SSH_URL,
|
UI_OPEN_SSH_URL,
|
||||||
UI_CONTEXTMENU_OPEN,
|
UI_CONTEXTMENU_OPEN,
|
||||||
UI_COMMAND_EXEC
|
UI_COMMAND_EXEC
|
||||||
} from '../../typings/constants/ui';
|
} from '../constants/ui';
|
||||||
import type {HyperState, HyperDispatch, HyperActions, ITermGroups} from '../../typings/hyper';
|
|
||||||
import rpc from '../rpc';
|
|
||||||
import {getRootGroups} from '../selectors';
|
|
||||||
import {isExecutable} from '../utils/file';
|
|
||||||
import notify from '../utils/notify';
|
|
||||||
import findBySession from '../utils/term-groups';
|
|
||||||
|
|
||||||
import {requestSession, sendSessionData, setActiveSession} from './sessions';
|
|
||||||
import {setActiveGroup} from './term-groups';
|
import {setActiveGroup} from './term-groups';
|
||||||
|
import parseUrl from 'parse-url';
|
||||||
|
import {HyperState, HyperDispatch, HyperActions, ITermGroups} from '../hyper';
|
||||||
|
import {stat, Stats} from 'fs';
|
||||||
|
|
||||||
export function openContextMenu(uid: string, selection: string) {
|
export function openContextMenu(uid: string, selection: any) {
|
||||||
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UI_CONTEXTMENU_OPEN,
|
type: UI_CONTEXTMENU_OPEN,
|
||||||
|
|
@ -272,11 +269,11 @@ export function openFile(path: string) {
|
||||||
}
|
}
|
||||||
rpc.once('session add', ({uid}) => {
|
rpc.once('session add', ({uid}) => {
|
||||||
rpc.once('session data', () => {
|
rpc.once('session data', () => {
|
||||||
dispatch(sendSessionData(uid, command));
|
dispatch(sendSessionData(uid, command, null));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
dispatch(requestSession(undefined));
|
dispatch(requestSession());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -295,11 +292,12 @@ export function leaveFullScreen(): HyperActions {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openSSH(parsedUrl: ReturnType<typeof parseUrl>) {
|
export function openSSH(url: string) {
|
||||||
return (dispatch: HyperDispatch) => {
|
return (dispatch: HyperDispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UI_OPEN_SSH_URL,
|
type: UI_OPEN_SSH_URL,
|
||||||
effect() {
|
effect() {
|
||||||
|
const parsedUrl = parseUrl(url, true);
|
||||||
let command = `${parsedUrl.protocol} ${parsedUrl.user ? `${parsedUrl.user}@` : ''}${parsedUrl.resource}`;
|
let command = `${parsedUrl.protocol} ${parsedUrl.user ? `${parsedUrl.user}@` : ''}${parsedUrl.resource}`;
|
||||||
|
|
||||||
if (parsedUrl.port) command += ` -p ${parsedUrl.port}`;
|
if (parsedUrl.port) command += ` -p ${parsedUrl.port}`;
|
||||||
|
|
@ -308,11 +306,11 @@ export function openSSH(parsedUrl: ReturnType<typeof parseUrl>) {
|
||||||
|
|
||||||
rpc.once('session add', ({uid}) => {
|
rpc.once('session add', ({uid}) => {
|
||||||
rpc.once('session data', () => {
|
rpc.once('session data', () => {
|
||||||
dispatch(sendSessionData(uid, command));
|
dispatch(sendSessionData(uid, command, null));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(requestSession(undefined));
|
dispatch(requestSession());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import {UPDATE_INSTALL, UPDATE_AVAILABLE} from '../../typings/constants/updater';
|
import {UPDATE_INSTALL, UPDATE_AVAILABLE} from '../constants/updater';
|
||||||
import type {HyperActions} from '../../typings/hyper';
|
|
||||||
import rpc from '../rpc';
|
import rpc from '../rpc';
|
||||||
|
import {HyperActions} from '../hyper';
|
||||||
|
|
||||||
export function installUpdate(): HyperActions {
|
export function installUpdate(): HyperActions {
|
||||||
return {
|
return {
|
||||||
type: UPDATE_INSTALL,
|
type: UPDATE_INSTALL,
|
||||||
effect: () => {
|
effect: () => {
|
||||||
rpc.emit('quit and install');
|
rpc.emit('quit and install', null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import type {HyperDispatch} from '../typings/hyper';
|
import {require as remoteRequire} from '@electron/remote';
|
||||||
|
import {HyperDispatch} from './hyper';
|
||||||
import {closeSearch} from './actions/sessions';
|
import {closeSearch} from './actions/sessions';
|
||||||
import {ipcRenderer} from './utils/ipc';
|
// TODO: Should be updates to new async API https://medium.com/@nornagon/electrons-remote-module-considered-harmful-70d69500f31
|
||||||
|
|
||||||
|
const {getDecoratedKeymaps} = remoteRequire('./plugins') as typeof import('../app/plugins');
|
||||||
|
|
||||||
let commands: Record<string, (event: any, dispatch: HyperDispatch) => void> = {
|
let commands: Record<string, (event: any, dispatch: HyperDispatch) => void> = {
|
||||||
'editor:search-close': (e, dispatch) => {
|
'editor:search-close': (e, dispatch) => {
|
||||||
|
|
@ -10,8 +12,8 @@ let commands: Record<string, (event: any, dispatch: HyperDispatch) => void> = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRegisteredKeys = async () => {
|
export const getRegisteredKeys = () => {
|
||||||
const keymaps = await ipcRenderer.invoke('getDecoratedKeymaps');
|
const keymaps = getDecoratedKeymaps();
|
||||||
|
|
||||||
return Object.keys(keymaps).reduce((result: Record<string, string>, actionName) => {
|
return Object.keys(keymaps).reduce((result: Record<string, string>, actionName) => {
|
||||||
const commandKeys = keymaps[actionName];
|
const commandKeys = keymaps[actionName];
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,27 @@
|
||||||
import React, {forwardRef, useState} from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import type {HeaderProps} from '../../typings/hyper';
|
|
||||||
import {decorate, getTabsProps} from '../utils/plugins';
|
import {decorate, getTabsProps} from '../utils/plugins';
|
||||||
|
|
||||||
import Tabs_ from './tabs';
|
import Tabs_ from './tabs';
|
||||||
|
import {HeaderProps} from '../hyper';
|
||||||
|
|
||||||
const Tabs = decorate(Tabs_, 'Tabs');
|
const Tabs = decorate(Tabs_, 'Tabs');
|
||||||
|
|
||||||
const Header = forwardRef<HTMLElement, HeaderProps>((props, ref) => {
|
export default class Header extends React.PureComponent<HeaderProps> {
|
||||||
const [headerMouseDownWindowX, setHeaderMouseDownWindowX] = useState<number>(0);
|
headerMouseDownWindowX!: number;
|
||||||
const [headerMouseDownWindowY, setHeaderMouseDownWindowY] = useState<number>(0);
|
headerMouseDownWindowY!: number;
|
||||||
|
|
||||||
const onChangeIntent = (active: string) => {
|
onChangeIntent = (active: string) => {
|
||||||
// we ignore clicks if they're a byproduct of a drag
|
// we ignore clicks if they're a byproduct of a drag
|
||||||
// motion to move the window
|
// motion to move the window
|
||||||
if (window.screenX !== headerMouseDownWindowX || window.screenY !== headerMouseDownWindowY) {
|
if (window.screenX !== this.headerMouseDownWindowX || window.screenY !== this.headerMouseDownWindowY) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
props.onChangeTab(active);
|
this.props.onChangeTab(active);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHeaderMouseDown = () => {
|
handleHeaderMouseDown = () => {
|
||||||
// the hack of all hacks, this prevents the term
|
// the hack of all hacks, this prevents the term
|
||||||
// iframe from losing focus, for example, when
|
// iframe from losing focus, for example, when
|
||||||
// the user drags the nav around
|
// the user drags the nav around
|
||||||
|
|
@ -30,43 +30,43 @@ const Header = forwardRef<HTMLElement, HeaderProps>((props, ref) => {
|
||||||
|
|
||||||
// persist start positions of a potential drag motion
|
// persist start positions of a potential drag motion
|
||||||
// to differentiate dragging from clicking
|
// to differentiate dragging from clicking
|
||||||
setHeaderMouseDownWindowX(window.screenX);
|
this.headerMouseDownWindowX = window.screenX;
|
||||||
setHeaderMouseDownWindowY(window.screenY);
|
this.headerMouseDownWindowY = window.screenY;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHamburgerMenuClick = (event: React.MouseEvent) => {
|
handleHamburgerMenuClick = (event: React.MouseEvent) => {
|
||||||
let {right: x, bottom: y} = event.currentTarget.getBoundingClientRect();
|
let {right: x, bottom: y} = event.currentTarget.getBoundingClientRect();
|
||||||
x -= 15; // to compensate padding
|
x -= 15; // to compensate padding
|
||||||
y -= 12; // ^ same
|
y -= 12; // ^ same
|
||||||
props.openHamburgerMenu({x, y});
|
this.props.openHamburgerMenu({x, y});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMaximizeClick = () => {
|
handleMaximizeClick = () => {
|
||||||
if (props.maximized) {
|
if (this.props.maximized) {
|
||||||
props.unmaximize();
|
this.props.unmaximize();
|
||||||
} else {
|
} else {
|
||||||
props.maximize();
|
this.props.maximize();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMinimizeClick = () => {
|
handleMinimizeClick = () => {
|
||||||
props.minimize();
|
this.props.minimize();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseClick = () => {
|
handleCloseClick = () => {
|
||||||
props.close();
|
this.props.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getWindowHeaderConfig = () => {
|
getWindowHeaderConfig() {
|
||||||
const {showHamburgerMenu, showWindowControls} = props;
|
const {showHamburgerMenu, showWindowControls} = this.props;
|
||||||
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
hambMenu: !props.isMac, // show by default on windows and linux
|
hambMenu: !this.props.isMac, // show by default on windows and linux
|
||||||
winCtrls: !props.isMac // show by default on Windows and Linux
|
winCtrls: !this.props.isMac // show by default on Windows and Linux
|
||||||
};
|
};
|
||||||
|
|
||||||
// don't allow the user to change defaults on macOS
|
// don't allow the user to change defaults on macOS
|
||||||
if (props.isMac) {
|
if (this.props.isMac) {
|
||||||
return defaults;
|
return defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,187 +74,182 @@ const Header = forwardRef<HTMLElement, HeaderProps>((props, ref) => {
|
||||||
hambMenu: showHamburgerMenu === '' ? defaults.hambMenu : showHamburgerMenu,
|
hambMenu: showHamburgerMenu === '' ? defaults.hambMenu : showHamburgerMenu,
|
||||||
winCtrls: showWindowControls === '' ? defaults.winCtrls : showWindowControls
|
winCtrls: showWindowControls === '' ? defaults.winCtrls : showWindowControls
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const {isMac} = props;
|
|
||||||
const {borderColor} = props;
|
|
||||||
let title = 'Hyper';
|
|
||||||
if (props.tabs.length === 1 && props.tabs[0].title) {
|
|
||||||
// if there's only one tab we use its title as the window title
|
|
||||||
title = props.tabs[0].title;
|
|
||||||
}
|
}
|
||||||
const {hambMenu, winCtrls} = getWindowHeaderConfig();
|
|
||||||
const left = winCtrls === 'left';
|
|
||||||
const maxButtonHref = props.maximized
|
|
||||||
? './renderer/assets/icons.svg#restore-window'
|
|
||||||
: './renderer/assets/icons.svg#maximize-window';
|
|
||||||
|
|
||||||
return (
|
render() {
|
||||||
<header
|
const {isMac} = this.props;
|
||||||
className={`header_header ${isMac && 'header_headerRounded'}`}
|
const props = getTabsProps(this.props, {
|
||||||
onMouseDown={handleHeaderMouseDown}
|
tabs: this.props.tabs,
|
||||||
onMouseUp={() => window.focusActiveTerm()}
|
borderColor: this.props.borderColor,
|
||||||
onDoubleClick={handleMaximizeClick}
|
onClose: this.props.onCloseTab,
|
||||||
ref={ref}
|
onChange: this.onChangeIntent,
|
||||||
>
|
fullScreen: this.props.fullScreen
|
||||||
{!isMac && (
|
});
|
||||||
<div
|
const {borderColor} = props;
|
||||||
className={`header_windowHeader ${props.tabs.length > 1 ? 'header_windowHeaderWithBorder' : ''}`}
|
let title = 'Hyper';
|
||||||
style={{borderColor}}
|
if (props.tabs.length === 1 && props.tabs[0].title) {
|
||||||
>
|
// if there's only one tab we use its title as the window title
|
||||||
{hambMenu && (
|
title = props.tabs[0].title;
|
||||||
<svg
|
}
|
||||||
className={`header_shape ${left ? 'header_hamburgerMenuRight' : 'header_hamburgerMenuLeft'}`}
|
const {hambMenu, winCtrls} = this.getWindowHeaderConfig();
|
||||||
onClick={handleHamburgerMenuClick}
|
const left = winCtrls === 'left';
|
||||||
>
|
const maxButtonHref = this.props.maximized
|
||||||
<use xlinkHref="./renderer/assets/icons.svg#hamburger-menu" />
|
? './renderer/assets/icons.svg#restore-window'
|
||||||
</svg>
|
: './renderer/assets/icons.svg#maximize-window';
|
||||||
)}
|
|
||||||
<span className="header_appTitle">{title}</span>
|
return (
|
||||||
{winCtrls && (
|
<header
|
||||||
<div className={`header_windowControls ${left ? 'header_windowControlsLeft' : ''}`}>
|
className={`header_header ${isMac && 'header_headerRounded'}`}
|
||||||
<div className={`${left ? 'header_minimizeWindowLeft' : ''}`} onClick={handleMinimizeClick}>
|
onMouseDown={this.handleHeaderMouseDown}
|
||||||
<svg className="header_shape">
|
onMouseUp={() => window.focusActiveTerm()}
|
||||||
<use xlinkHref="./renderer/assets/icons.svg#minimize-window" />
|
onDoubleClick={this.handleMaximizeClick}
|
||||||
</svg>
|
>
|
||||||
|
{!isMac && (
|
||||||
|
<div
|
||||||
|
className={`header_windowHeader ${props.tabs.length > 1 ? 'header_windowHeaderWithBorder' : ''}`}
|
||||||
|
style={{borderColor}}
|
||||||
|
>
|
||||||
|
{hambMenu && (
|
||||||
|
<svg
|
||||||
|
className={`header_shape ${left ? 'header_hamburgerMenuRight' : 'header_hamburgerMenuLeft'}`}
|
||||||
|
onClick={this.handleHamburgerMenuClick}
|
||||||
|
>
|
||||||
|
<use xlinkHref="./renderer/assets/icons.svg#hamburger-menu" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
<span className="header_appTitle">{title}</span>
|
||||||
|
{winCtrls && (
|
||||||
|
<div className={`header_windowControls ${left ? 'header_windowControlsLeft' : ''}`}>
|
||||||
|
<div className={`${left ? 'header_minimizeWindowLeft' : ''}`} onClick={this.handleMinimizeClick}>
|
||||||
|
<svg className="header_shape">
|
||||||
|
<use xlinkHref="./renderer/assets/icons.svg#minimize-window" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className={`${left ? 'header_maximizeWindowLeft' : ''}`} onClick={this.handleMaximizeClick}>
|
||||||
|
<svg className="header_shape">
|
||||||
|
<use xlinkHref={maxButtonHref} />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`header_closeWindow ${left ? 'header_closeWindowLeft' : ''}`}
|
||||||
|
onClick={this.handleCloseClick}
|
||||||
|
>
|
||||||
|
<svg className="header_shape">
|
||||||
|
<use xlinkHref="./renderer/assets/icons.svg#close-window" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${left ? 'header_maximizeWindowLeft' : ''}`} onClick={handleMaximizeClick}>
|
)}
|
||||||
<svg className="header_shape">
|
</div>
|
||||||
<use xlinkHref={maxButtonHref} />
|
)}
|
||||||
</svg>
|
{this.props.customChildrenBefore}
|
||||||
</div>
|
<Tabs {...props} />
|
||||||
<div className={`header_closeWindow ${left ? 'header_closeWindowLeft' : ''}`} onClick={handleCloseClick}>
|
{this.props.customChildren}
|
||||||
<svg className="header_shape">
|
|
||||||
<use xlinkHref="./renderer/assets/icons.svg#close-window" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{props.customChildrenBefore}
|
|
||||||
<Tabs
|
|
||||||
{...getTabsProps(props, {
|
|
||||||
tabs: props.tabs,
|
|
||||||
borderColor: props.borderColor,
|
|
||||||
backgroundColor: props.backgroundColor,
|
|
||||||
onClose: props.onCloseTab,
|
|
||||||
onChange: onChangeIntent,
|
|
||||||
fullScreen: props.fullScreen,
|
|
||||||
defaultProfile: props.defaultProfile,
|
|
||||||
profiles: props.profiles.asMutable({deep: true}),
|
|
||||||
openNewTab: props.openNewTab
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
{props.customChildren}
|
|
||||||
|
|
||||||
<style jsx>{`
|
<style jsx>{`
|
||||||
.header_header {
|
.header_header {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
left: 1px;
|
left: 1px;
|
||||||
right: 1px;
|
right: 1px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_headerRounded {
|
.header_headerRounded {
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_windowHeader {
|
.header_windowHeader {
|
||||||
height: 34px;
|
height: 34px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
left: 1px;
|
left: 1px;
|
||||||
right: 1px;
|
right: 1px;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_windowHeaderWithBorder {
|
.header_windowHeaderWithBorder {
|
||||||
border-color: #ccc;
|
border-color: #ccc;
|
||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_appTitle {
|
.header_appTitle {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_shape,
|
.header_shape,
|
||||||
.header_shape > svg {
|
.header_shape > svg {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
padding: 12px 15px 12px 15px;
|
padding: 12px 15px 12px 15px;
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
shape-rendering: crispEdges;
|
shape-rendering: crispEdges;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_shape:hover {
|
.header_shape:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_shape:active {
|
.header_shape:active {
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_hamburgerMenuLeft {
|
.header_hamburgerMenuLeft {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_hamburgerMenuRight {
|
.header_hamburgerMenuRight {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_windowControls {
|
.header_windowControls {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_windowControlsLeft {
|
.header_windowControlsLeft {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_closeWindowLeft {
|
.header_closeWindowLeft {
|
||||||
order: 1;
|
order: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_minimizeWindowLeft {
|
.header_minimizeWindowLeft {
|
||||||
order: 2;
|
order: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_maximizeWindowLeft {
|
.header_maximizeWindowLeft {
|
||||||
order: 3;
|
order: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_closeWindow:hover {
|
.header_closeWindow:hover {
|
||||||
color: #fe354e;
|
color: #fe354e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_closeWindow:active {
|
.header_closeWindow:active {
|
||||||
color: #fe354e;
|
color: #fe354e;
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
Header.displayName = 'Header';
|
|
||||||
|
|
||||||
export default Header;
|
|
||||||
|
|
|
||||||
|
|
@ -1,149 +0,0 @@
|
||||||
import React, {useRef, useState} from 'react';
|
|
||||||
|
|
||||||
import {VscChevronDown} from '@react-icons/all-files/vsc/VscChevronDown';
|
|
||||||
import useClickAway from 'react-use/lib/useClickAway';
|
|
||||||
|
|
||||||
import type {configOptions} from '../../typings/config';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
defaultProfile: string;
|
|
||||||
profiles: configOptions['profiles'];
|
|
||||||
openNewTab: (name: string) => void;
|
|
||||||
backgroundColor: string;
|
|
||||||
borderColor: string;
|
|
||||||
tabsVisible: boolean;
|
|
||||||
}
|
|
||||||
const isMac = /Mac/.test(navigator.userAgent);
|
|
||||||
|
|
||||||
const DropdownButton = ({defaultProfile, profiles, openNewTab, backgroundColor, borderColor, tabsVisible}: Props) => {
|
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
||||||
const ref = useRef(null);
|
|
||||||
|
|
||||||
const toggleDropdown = () => {
|
|
||||||
setDropdownOpen(!dropdownOpen);
|
|
||||||
};
|
|
||||||
|
|
||||||
useClickAway(ref, () => {
|
|
||||||
setDropdownOpen(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={ref}
|
|
||||||
title="New Tab"
|
|
||||||
className={`new_tab ${tabsVisible ? 'tabs_visible' : 'tabs_hidden'}`}
|
|
||||||
onClick={toggleDropdown}
|
|
||||||
onDoubleClick={(e) => e.stopPropagation()}
|
|
||||||
onBlur={() => setDropdownOpen(false)}
|
|
||||||
>
|
|
||||||
<VscChevronDown style={{verticalAlign: 'middle'}} />
|
|
||||||
|
|
||||||
{dropdownOpen && (
|
|
||||||
<ul
|
|
||||||
key="dropdown"
|
|
||||||
className="profile_dropdown"
|
|
||||||
style={{
|
|
||||||
borderColor,
|
|
||||||
backgroundColor
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{profiles.map((profile) => (
|
|
||||||
<li
|
|
||||||
key={profile.name}
|
|
||||||
onClick={() => {
|
|
||||||
openNewTab(profile.name);
|
|
||||||
setDropdownOpen(false);
|
|
||||||
}}
|
|
||||||
className={`profile_dropdown_item ${
|
|
||||||
profile.name === defaultProfile && profiles.length > 1 ? 'profile_dropdown_item_default' : ''
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{profile.name}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<style jsx>{`
|
|
||||||
.profile_dropdown {
|
|
||||||
border-width: 1px;
|
|
||||||
border-style: solid;
|
|
||||||
border-bottom-width: 0px;
|
|
||||||
border-right-width: 0px;
|
|
||||||
position: absolute;
|
|
||||||
top: 33px;
|
|
||||||
right: 0px;
|
|
||||||
z-index: 1000;
|
|
||||||
padding: 0px;
|
|
||||||
margin: 0px;
|
|
||||||
list-style-type: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
min-width: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile_dropdown_item {
|
|
||||||
padding: 0px 20px;
|
|
||||||
height: 34px;
|
|
||||||
line-height: 34px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #fff;
|
|
||||||
background-color: transparent;
|
|
||||||
border-width: 0px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent;
|
|
||||||
border-bottom-width: 1px;
|
|
||||||
border-bottom-style: solid;
|
|
||||||
border-bottom-color: ${borderColor};
|
|
||||||
text-align: start;
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile_dropdown_item:hover {
|
|
||||||
background-color: ${borderColor};
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile_dropdown_item_default {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new_tab {
|
|
||||||
background: transparent;
|
|
||||||
color: #fff;
|
|
||||||
border-left: 1px;
|
|
||||||
border-bottom: 1px;
|
|
||||||
border-left-style: solid;
|
|
||||||
border-bottom-style: solid;
|
|
||||||
border-left-width: 1px;
|
|
||||||
border-bottom-width: 1px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
height: 34px;
|
|
||||||
line-height: 34px;
|
|
||||||
padding: 0 16px;
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
${isMac ? '-webkit-app-region: drag;' : ''}
|
|
||||||
top: '0px';
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs_visible {
|
|
||||||
border-color: ${borderColor};
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs_hidden {
|
|
||||||
border-color: transparent;
|
|
||||||
position: absolute;
|
|
||||||
right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs_hidden:hover {
|
|
||||||
border-color: ${borderColor};
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DropdownButton;
|
|
||||||
|
|
@ -1,110 +1,115 @@
|
||||||
import React, {forwardRef, useEffect, useRef, useState} from 'react';
|
import React from 'react';
|
||||||
|
import {NotificationProps, NotificationState} from '../hyper';
|
||||||
|
|
||||||
import type {NotificationProps} from '../../typings/hyper';
|
export default class Notification extends React.PureComponent<NotificationProps, NotificationState> {
|
||||||
|
dismissTimer!: NodeJS.Timeout;
|
||||||
|
constructor(props: NotificationProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
dismissing: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const Notification = forwardRef<HTMLDivElement, React.PropsWithChildren<NotificationProps>>((props, ref) => {
|
componentDidMount() {
|
||||||
const dismissTimer = useRef<NodeJS.Timeout | undefined>(undefined);
|
if (this.props.dismissAfter) {
|
||||||
const [dismissing, setDismissing] = useState(false);
|
this.setDismissTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
componentDidUpdate(prevProps: NotificationProps, prevState: NotificationState) {
|
||||||
setDismissTimer();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// if we have a timer going and the notification text
|
// if we have a timer going and the notification text
|
||||||
// changed we reset the timer
|
// changed we reset the timer
|
||||||
resetDismissTimer();
|
if (this.props.text !== prevProps.text) {
|
||||||
setDismissing(false);
|
if (prevProps.dismissAfter) {
|
||||||
}, [props.text]);
|
this.resetDismissTimer();
|
||||||
|
}
|
||||||
|
if (prevState.dismissing) {
|
||||||
|
this.setState({dismissing: false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleDismiss = () => {
|
handleDismiss = () => {
|
||||||
setDismissing(true);
|
this.setState({dismissing: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onElement = (el: HTMLDivElement | null) => {
|
onElement = (el: HTMLDivElement | null) => {
|
||||||
if (el) {
|
if (el) {
|
||||||
el.addEventListener('webkitTransitionEnd', () => {
|
el.addEventListener('webkitTransitionEnd', () => {
|
||||||
if (dismissing) {
|
if (this.state.dismissing) {
|
||||||
props.onDismiss();
|
this.props.onDismiss();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const {backgroundColor} = props;
|
const {backgroundColor} = this.props;
|
||||||
if (backgroundColor) {
|
if (backgroundColor) {
|
||||||
el.style.setProperty('background-color', backgroundColor, 'important');
|
el.style.setProperty('background-color', backgroundColor, 'important');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ref) {
|
|
||||||
if (typeof ref === 'function') ref(el);
|
|
||||||
else ref.current = el;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setDismissTimer = () => {
|
setDismissTimer() {
|
||||||
if (typeof props.dismissAfter === 'number') {
|
this.dismissTimer = setTimeout(() => {
|
||||||
dismissTimer.current = setTimeout(() => {
|
this.handleDismiss();
|
||||||
handleDismiss();
|
}, this.props.dismissAfter);
|
||||||
}, props.dismissAfter);
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetDismissTimer = () => {
|
resetDismissTimer() {
|
||||||
clearTimeout(dismissTimer.current);
|
clearTimeout(this.dismissTimer);
|
||||||
setDismissTimer();
|
this.setDismissTimer();
|
||||||
};
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
componentWillUnmount() {
|
||||||
return () => {
|
clearTimeout(this.dismissTimer);
|
||||||
clearTimeout(dismissTimer.current);
|
}
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const {backgroundColor, color} = props;
|
render() {
|
||||||
const opacity = dismissing ? 0 : 1;
|
const {backgroundColor, color} = this.props;
|
||||||
return (
|
const opacity = this.state.dismissing ? 0 : 1;
|
||||||
<div ref={onElement} style={{opacity, backgroundColor, color}} className="notification_indicator">
|
return (
|
||||||
{props.customChildrenBefore}
|
<div ref={this.onElement} style={{opacity, backgroundColor, color}} className="notification_indicator">
|
||||||
{props.children || props.text}
|
{this.props.customChildrenBefore}
|
||||||
{props.userDismissable ? (
|
{this.props.children || this.props.text}
|
||||||
<a className="notification_dismissLink" onClick={handleDismiss} style={{color: props.userDismissColor}}>
|
{this.props.userDismissable ? (
|
||||||
[x]
|
<a
|
||||||
</a>
|
className="notification_dismissLink"
|
||||||
) : null}
|
onClick={this.handleDismiss}
|
||||||
{props.customChildren}
|
style={{color: this.props.userDismissColor}}
|
||||||
|
>
|
||||||
|
[x]
|
||||||
|
</a>
|
||||||
|
) : null}
|
||||||
|
{this.props.customChildren}
|
||||||
|
|
||||||
<style jsx>{`
|
<style jsx>{`
|
||||||
.notification_indicator {
|
.notification_indicator {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
padding: 8px 14px 9px;
|
padding: 8px 14px 9px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
transition: 150ms opacity ease;
|
transition: 150ms opacity ease;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',
|
||||||
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification_dismissLink {
|
.notification_dismissLink {
|
||||||
position: relative;
|
position: relative;
|
||||||
left: 4px;
|
left: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: currentColor;
|
color: currentColor;
|
||||||
transition: font-weight 0.1s ease-in-out;
|
transition: font-weight 0.1s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification_dismissLink:hover,
|
.notification_dismissLink:hover,
|
||||||
.notification_dismissLink:focus {
|
.notification_dismissLink:focus {
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
Notification.displayName = 'Notification';
|
|
||||||
|
|
||||||
export default Notification;
|
|
||||||
|
|
|
||||||
|
|
@ -1,132 +1,132 @@
|
||||||
import React, {forwardRef} from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import type {NotificationsProps} from '../../typings/hyper';
|
|
||||||
import {decorate} from '../utils/plugins';
|
import {decorate} from '../utils/plugins';
|
||||||
|
|
||||||
import Notification_ from './notification';
|
import Notification_ from './notification';
|
||||||
|
import {NotificationsProps} from '../hyper';
|
||||||
|
|
||||||
const Notification = decorate(Notification_, 'Notification');
|
const Notification = decorate(Notification_, 'Notification');
|
||||||
|
|
||||||
const Notifications = forwardRef<HTMLDivElement, NotificationsProps>((props, ref) => {
|
export default class Notifications extends React.PureComponent<NotificationsProps> {
|
||||||
return (
|
render() {
|
||||||
<div className="notifications_view" ref={ref}>
|
return (
|
||||||
{props.customChildrenBefore}
|
<div className="notifications_view">
|
||||||
{props.fontShowing && (
|
{this.props.customChildrenBefore}
|
||||||
<Notification
|
{this.props.fontShowing && (
|
||||||
key="font"
|
<Notification
|
||||||
backgroundColor="rgba(255, 255, 255, .2)"
|
key="font"
|
||||||
text={`${props.fontSize}px`}
|
backgroundColor="rgba(255, 255, 255, .2)"
|
||||||
userDismissable={false}
|
text={`${this.props.fontSize}px`}
|
||||||
onDismiss={props.onDismissFont}
|
userDismissable={false}
|
||||||
dismissAfter={1000}
|
onDismiss={this.props.onDismissFont}
|
||||||
/>
|
dismissAfter={1000}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{props.resizeShowing && (
|
{this.props.resizeShowing && (
|
||||||
<Notification
|
<Notification
|
||||||
key="resize"
|
key="resize"
|
||||||
backgroundColor="rgba(255, 255, 255, .2)"
|
backgroundColor="rgba(255, 255, 255, .2)"
|
||||||
text={`${props.cols}x${props.rows}`}
|
text={`${this.props.cols}x${this.props.rows}`}
|
||||||
userDismissable={false}
|
userDismissable={false}
|
||||||
onDismiss={props.onDismissResize}
|
onDismiss={this.props.onDismissResize}
|
||||||
dismissAfter={1000}
|
dismissAfter={1000}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{props.messageShowing && (
|
{this.props.messageShowing && (
|
||||||
<Notification
|
<Notification
|
||||||
key="message"
|
key="message"
|
||||||
backgroundColor="#FE354E"
|
backgroundColor="#FE354E"
|
||||||
color="#fff"
|
color="#fff"
|
||||||
text={props.messageText}
|
text={this.props.messageText}
|
||||||
onDismiss={props.onDismissMessage}
|
onDismiss={this.props.onDismissMessage}
|
||||||
userDismissable={props.messageDismissable}
|
userDismissable={this.props.messageDismissable}
|
||||||
>
|
|
||||||
{props.messageURL ? (
|
|
||||||
<>
|
|
||||||
{props.messageText} (
|
|
||||||
<a
|
|
||||||
style={{color: '#fff'}}
|
|
||||||
onClick={(ev) => {
|
|
||||||
void window.require('electron').shell.openExternal(ev.currentTarget.href);
|
|
||||||
ev.preventDefault();
|
|
||||||
}}
|
|
||||||
href={props.messageURL}
|
|
||||||
>
|
|
||||||
more
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</Notification>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{props.updateShowing && (
|
|
||||||
<Notification
|
|
||||||
key="update"
|
|
||||||
backgroundColor="#18E179"
|
|
||||||
color="#000"
|
|
||||||
text={`Version ${props.updateVersion} ready`}
|
|
||||||
onDismiss={props.onDismissUpdate}
|
|
||||||
userDismissable
|
|
||||||
>
|
|
||||||
Version <b>{props.updateVersion}</b> ready.
|
|
||||||
{props.updateNote && ` ${props.updateNote.trim().replace(/\.$/, '')}`} (
|
|
||||||
<a
|
|
||||||
style={{color: '#000'}}
|
|
||||||
onClick={(ev) => {
|
|
||||||
void window.require('electron').shell.openExternal(ev.currentTarget.href);
|
|
||||||
ev.preventDefault();
|
|
||||||
}}
|
|
||||||
href={`https://github.com/quine-global/hyper/releases/tag/${props.updateVersion}`}
|
|
||||||
>
|
>
|
||||||
notes
|
{this.props.messageURL
|
||||||
</a>
|
? [
|
||||||
).{' '}
|
this.props.messageText,
|
||||||
{props.updateCanInstall ? (
|
' (',
|
||||||
|
<a
|
||||||
|
key="link"
|
||||||
|
style={{color: '#fff'}}
|
||||||
|
onClick={(ev) => {
|
||||||
|
void window.require('electron').shell.openExternal(ev.currentTarget.href);
|
||||||
|
ev.preventDefault();
|
||||||
|
}}
|
||||||
|
href={this.props.messageURL}
|
||||||
|
>
|
||||||
|
more
|
||||||
|
</a>,
|
||||||
|
') '
|
||||||
|
]
|
||||||
|
: null}
|
||||||
|
</Notification>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{this.props.updateShowing && (
|
||||||
|
<Notification
|
||||||
|
key="update"
|
||||||
|
backgroundColor="#18E179"
|
||||||
|
color="#000"
|
||||||
|
text={`Version ${this.props.updateVersion} ready`}
|
||||||
|
onDismiss={this.props.onDismissUpdate}
|
||||||
|
userDismissable
|
||||||
|
>
|
||||||
|
Version <b>{this.props.updateVersion}</b> ready.
|
||||||
|
{this.props.updateNote && ` ${this.props.updateNote.trim().replace(/\.$/, '')}`} (
|
||||||
<a
|
<a
|
||||||
style={{
|
style={{color: '#000'}}
|
||||||
cursor: 'pointer',
|
|
||||||
textDecoration: 'underline',
|
|
||||||
fontWeight: 'bold'
|
|
||||||
}}
|
|
||||||
onClick={props.onUpdateInstall}
|
|
||||||
>
|
|
||||||
Restart
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<a
|
|
||||||
style={{
|
|
||||||
color: '#000',
|
|
||||||
cursor: 'pointer',
|
|
||||||
textDecoration: 'underline',
|
|
||||||
fontWeight: 'bold'
|
|
||||||
}}
|
|
||||||
onClick={(ev) => {
|
onClick={(ev) => {
|
||||||
void window.require('electron').shell.openExternal(ev.currentTarget.href);
|
void window.require('electron').shell.openExternal(ev.currentTarget.href);
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}}
|
}}
|
||||||
href={props.updateReleaseUrl!}
|
href={`https://github.com/vercel/hyper/releases/tag/${this.props.updateVersion}`}
|
||||||
>
|
>
|
||||||
Download
|
notes
|
||||||
</a>
|
</a>
|
||||||
)}
|
).{' '}
|
||||||
.{' '}
|
{this.props.updateCanInstall ? (
|
||||||
</Notification>
|
<a
|
||||||
)}
|
style={{
|
||||||
{props.customChildren}
|
cursor: 'pointer',
|
||||||
|
textDecoration: 'underline',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}}
|
||||||
|
onClick={this.props.onUpdateInstall}
|
||||||
|
>
|
||||||
|
Restart
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<a
|
||||||
|
style={{
|
||||||
|
color: '#000',
|
||||||
|
cursor: 'pointer',
|
||||||
|
textDecoration: 'underline',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}}
|
||||||
|
onClick={(ev) => {
|
||||||
|
void window.require('electron').shell.openExternal(ev.currentTarget.href);
|
||||||
|
ev.preventDefault();
|
||||||
|
}}
|
||||||
|
href={this.props.updateReleaseUrl!}
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
.{' '}
|
||||||
|
</Notification>
|
||||||
|
)}
|
||||||
|
{this.props.customChildren}
|
||||||
|
|
||||||
<style jsx>{`
|
<style jsx>{`
|
||||||
.notifications_view {
|
.notifications_view {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
Notifications.displayName = 'Notifications';
|
|
||||||
|
|
||||||
export default Notifications;
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
import React, {useCallback, useRef, useEffect, forwardRef} from 'react';
|
import React, {useCallback} from 'react';
|
||||||
|
import {SearchBoxProps} from '../hyper';
|
||||||
import {VscArrowDown} from '@react-icons/all-files/vsc/VscArrowDown';
|
|
||||||
import {VscArrowUp} from '@react-icons/all-files/vsc/VscArrowUp';
|
import {VscArrowUp} from '@react-icons/all-files/vsc/VscArrowUp';
|
||||||
import {VscCaseSensitive} from '@react-icons/all-files/vsc/VscCaseSensitive';
|
import {VscArrowDown} from '@react-icons/all-files/vsc/VscArrowDown';
|
||||||
import {VscClose} from '@react-icons/all-files/vsc/VscClose';
|
import {VscClose} from '@react-icons/all-files/vsc/VscClose';
|
||||||
|
import {VscCaseSensitive} from '@react-icons/all-files/vsc/VscCaseSensitive';
|
||||||
import {VscRegex} from '@react-icons/all-files/vsc/VscRegex';
|
import {VscRegex} from '@react-icons/all-files/vsc/VscRegex';
|
||||||
import {VscWholeWord} from '@react-icons/all-files/vsc/VscWholeWord';
|
import {VscWholeWord} from '@react-icons/all-files/vsc/VscWholeWord';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
import type {SearchBoxProps} from '../../typings/hyper';
|
|
||||||
|
|
||||||
type SearchButtonColors = {
|
type SearchButtonColors = {
|
||||||
foregroundColor: string;
|
foregroundColor: string;
|
||||||
selectionColor: string;
|
selectionColor: string;
|
||||||
|
|
@ -84,163 +82,179 @@ const SearchButton = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SearchBox = forwardRef<HTMLDivElement, SearchBoxProps>((props, ref) => {
|
class SearchBox extends React.PureComponent<SearchBoxProps> {
|
||||||
const {
|
searchTerm: string;
|
||||||
caseSensitive,
|
input: HTMLInputElement | null = null;
|
||||||
dateFocused,
|
searchButtonColors: SearchButtonColors;
|
||||||
wholeWord,
|
|
||||||
regex,
|
|
||||||
results,
|
|
||||||
toggleCaseSensitive,
|
|
||||||
toggleWholeWord,
|
|
||||||
toggleRegex,
|
|
||||||
next,
|
|
||||||
prev,
|
|
||||||
close,
|
|
||||||
backgroundColor,
|
|
||||||
foregroundColor,
|
|
||||||
borderColor,
|
|
||||||
selectionColor,
|
|
||||||
font
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const searchTermRef = useRef<string>('');
|
constructor(props: SearchBoxProps) {
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
super(props);
|
||||||
|
this.searchTerm = '';
|
||||||
|
this.searchButtonColors = {
|
||||||
|
backgroundColor: this.props.borderColor,
|
||||||
|
selectionColor: this.props.selectionColor,
|
||||||
|
foregroundColor: this.props.foregroundColor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const handleChange = useCallback(
|
handleChange = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
this.searchTerm = event.currentTarget.value;
|
||||||
searchTermRef.current = event.currentTarget.value;
|
if (event.shiftKey && event.key === 'Enter') {
|
||||||
if (event.shiftKey && event.key === 'Enter') {
|
this.props.prev(this.searchTerm);
|
||||||
prev(searchTermRef.current);
|
} else if (event.key === 'Enter') {
|
||||||
} else if (event.key === 'Enter') {
|
this.props.next(this.searchTerm);
|
||||||
next(searchTermRef.current);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[prev, next]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
inputRef.current?.focus();
|
|
||||||
}, [inputRef.current]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!dateFocused) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
inputRef.current?.focus();
|
|
||||||
inputRef.current?.select();
|
|
||||||
}, [dateFocused]);
|
|
||||||
|
|
||||||
const searchButtonColors: SearchButtonColors = {
|
|
||||||
backgroundColor: borderColor,
|
|
||||||
selectionColor,
|
|
||||||
foregroundColor
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
componentDidMount(): void {
|
||||||
<div className="flex-row search-container" ref={ref}>
|
this.input?.focus();
|
||||||
<div className="flex-row search-box">
|
}
|
||||||
<input className="search-input" type="text" onKeyDown={handleChange} ref={inputRef} placeholder="Search" />
|
|
||||||
|
|
||||||
<SearchButton onClick={toggleCaseSensitive} active={caseSensitive} title="Match Case" {...searchButtonColors}>
|
render() {
|
||||||
<VscCaseSensitive size="14px" />
|
const {
|
||||||
</SearchButton>
|
caseSensitive,
|
||||||
|
wholeWord,
|
||||||
|
regex,
|
||||||
|
results,
|
||||||
|
toggleCaseSensitive,
|
||||||
|
toggleWholeWord,
|
||||||
|
toggleRegex,
|
||||||
|
next,
|
||||||
|
prev,
|
||||||
|
close,
|
||||||
|
backgroundColor,
|
||||||
|
foregroundColor,
|
||||||
|
borderColor,
|
||||||
|
selectionColor,
|
||||||
|
font
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
<SearchButton onClick={toggleWholeWord} active={wholeWord} title="Match Whole Word" {...searchButtonColors}>
|
return (
|
||||||
<VscWholeWord size="14px" />
|
<div className="flex-row search-container">
|
||||||
</SearchButton>
|
<div className="flex-row search-box">
|
||||||
|
<input
|
||||||
|
className="search-input"
|
||||||
|
type="text"
|
||||||
|
onKeyDown={this.handleChange}
|
||||||
|
ref={(input) => {
|
||||||
|
this.input = input;
|
||||||
|
}}
|
||||||
|
placeholder="Search"
|
||||||
|
></input>
|
||||||
|
|
||||||
<SearchButton onClick={toggleRegex} active={regex} title="Use Regular Expression" {...searchButtonColors}>
|
<SearchButton
|
||||||
<VscRegex size="14px" />
|
onClick={toggleCaseSensitive}
|
||||||
</SearchButton>
|
active={caseSensitive}
|
||||||
</div>
|
title="Match Case"
|
||||||
|
{...this.searchButtonColors}
|
||||||
|
>
|
||||||
|
<VscCaseSensitive size="14px" />
|
||||||
|
</SearchButton>
|
||||||
|
|
||||||
<span style={{minWidth: '60px', marginLeft: '4px'}}>
|
<SearchButton
|
||||||
{results === undefined
|
onClick={toggleWholeWord}
|
||||||
? ''
|
active={wholeWord}
|
||||||
: results.resultCount === 0
|
title="Match Whole Word"
|
||||||
|
{...this.searchButtonColors}
|
||||||
|
>
|
||||||
|
<VscWholeWord size="14px" />
|
||||||
|
</SearchButton>
|
||||||
|
|
||||||
|
<SearchButton
|
||||||
|
onClick={toggleRegex}
|
||||||
|
active={regex}
|
||||||
|
title="Use Regular Expression"
|
||||||
|
{...this.searchButtonColors}
|
||||||
|
>
|
||||||
|
<VscRegex size="14px" />
|
||||||
|
</SearchButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span style={{minWidth: '60px', marginLeft: '4px'}}>
|
||||||
|
{results === undefined
|
||||||
|
? ''
|
||||||
|
: results.resultCount === 0
|
||||||
? 'No results'
|
? 'No results'
|
||||||
: `${results.resultIndex + 1} of ${results.resultCount}`}
|
: `${results.resultIndex + 1} of ${results.resultCount}`}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className="flex-row">
|
<div className="flex-row">
|
||||||
<SearchButton
|
<SearchButton
|
||||||
onClick={() => prev(searchTermRef.current)}
|
onClick={() => prev(this.searchTerm)}
|
||||||
active={false}
|
active={false}
|
||||||
title="Previous Match"
|
title="Previous Match"
|
||||||
{...searchButtonColors}
|
{...this.searchButtonColors}
|
||||||
>
|
>
|
||||||
<VscArrowUp size="14px" />
|
<VscArrowUp size="14px" />
|
||||||
</SearchButton>
|
</SearchButton>
|
||||||
|
|
||||||
<SearchButton
|
<SearchButton
|
||||||
onClick={() => next(searchTermRef.current)}
|
onClick={() => next(this.searchTerm)}
|
||||||
active={false}
|
active={false}
|
||||||
title="Next Match"
|
title="Next Match"
|
||||||
{...searchButtonColors}
|
{...this.searchButtonColors}
|
||||||
>
|
>
|
||||||
<VscArrowDown size="14px" />
|
<VscArrowDown size="14px" />
|
||||||
</SearchButton>
|
</SearchButton>
|
||||||
|
|
||||||
<SearchButton onClick={close} active={false} title="Close" {...searchButtonColors}>
|
<SearchButton onClick={() => close()} active={false} title="Close" {...this.searchButtonColors}>
|
||||||
<VscClose size="14px" />
|
<VscClose size="14px" />
|
||||||
</SearchButton>
|
</SearchButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style jsx>
|
||||||
|
{`
|
||||||
|
.search-container {
|
||||||
|
background-color: ${backgroundColor};
|
||||||
|
border: 1px solid ${borderColor};
|
||||||
|
border-radius: 2px;
|
||||||
|
position: absolute;
|
||||||
|
right: 13px;
|
||||||
|
top: 4px;
|
||||||
|
z-index: 10;
|
||||||
|
padding: 4px;
|
||||||
|
font-family: ${font};
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
outline: none;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
color: ${foregroundColor};
|
||||||
|
align-self: stretch;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
border: none;
|
||||||
|
border-radius: 2px;
|
||||||
|
outline: ${borderColor} solid 1px;
|
||||||
|
background-color: ${backgroundColor};
|
||||||
|
color: ${foregroundColor};
|
||||||
|
padding: 0px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input::placeholder {
|
||||||
|
color: ${foregroundColor};
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box:focus-within {
|
||||||
|
outline: ${selectionColor} solid 2px;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
<style jsx>
|
}
|
||||||
{`
|
}
|
||||||
.search-container {
|
|
||||||
background-color: ${backgroundColor};
|
|
||||||
border: 1px solid ${borderColor};
|
|
||||||
border-radius: 2px;
|
|
||||||
position: absolute;
|
|
||||||
right: 13px;
|
|
||||||
top: 4px;
|
|
||||||
z-index: 10;
|
|
||||||
padding: 4px;
|
|
||||||
font-family: ${font};
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input {
|
|
||||||
outline: none;
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
color: ${foregroundColor};
|
|
||||||
align-self: stretch;
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
|
||||||
border: none;
|
|
||||||
border-radius: 2px;
|
|
||||||
outline: ${borderColor} solid 1px;
|
|
||||||
background-color: ${backgroundColor};
|
|
||||||
color: ${foregroundColor};
|
|
||||||
padding: 0px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input::placeholder {
|
|
||||||
color: ${foregroundColor};
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box:focus-within {
|
|
||||||
outline: ${selectionColor} solid 2px;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
SearchBox.displayName = 'SearchBox';
|
|
||||||
|
|
||||||
export default SearchBox;
|
export default SearchBox;
|
||||||
|
|
|
||||||
|
|
@ -1,191 +1,219 @@
|
||||||
import React, {useState, useEffect, useRef, forwardRef} from 'react';
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import {SplitPaneProps} from '../hyper';
|
||||||
|
|
||||||
import sum from 'lodash/sum';
|
export default class SplitPane extends React.PureComponent<SplitPaneProps, {dragging: boolean}> {
|
||||||
|
dragPanePosition!: number;
|
||||||
|
dragTarget!: Element;
|
||||||
|
panes!: Element[];
|
||||||
|
paneIndex!: number;
|
||||||
|
d1!: 'height' | 'width';
|
||||||
|
d2!: 'top' | 'left';
|
||||||
|
d3!: 'clientX' | 'clientY';
|
||||||
|
panesSize!: number;
|
||||||
|
dragging!: boolean;
|
||||||
|
constructor(props: SplitPaneProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {dragging: false};
|
||||||
|
}
|
||||||
|
|
||||||
import type {SplitPaneProps} from '../../typings/hyper';
|
componentDidUpdate(prevProps: SplitPaneProps) {
|
||||||
|
if (this.state.dragging && prevProps.sizes !== this.props.sizes) {
|
||||||
|
// recompute positions for ongoing dragging
|
||||||
|
this.dragPanePosition = this.dragTarget.getBoundingClientRect()[this.d2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const SplitPane = forwardRef<HTMLDivElement, SplitPaneProps>((props, ref) => {
|
setupPanes(ev: any) {
|
||||||
const dragPanePosition = useRef<number>(0);
|
this.panes = Array.from(ev.target.parentNode.childNodes);
|
||||||
const dragTarget = useRef<HTMLDivElement | null>(null);
|
this.paneIndex = this.panes.indexOf(ev.target);
|
||||||
const paneIndex = useRef<number>(0);
|
this.paneIndex -= Math.ceil(this.paneIndex / 2);
|
||||||
const d1 = props.direction === 'horizontal' ? 'height' : 'width';
|
}
|
||||||
const d2 = props.direction === 'horizontal' ? 'top' : 'left';
|
|
||||||
const d3 = props.direction === 'horizontal' ? 'clientY' : 'clientX';
|
|
||||||
const panesSize = useRef<number | null>(null);
|
|
||||||
const [dragging, setDragging] = useState(false);
|
|
||||||
|
|
||||||
const handleAutoResize = (ev: React.MouseEvent<HTMLDivElement>, index: number) => {
|
handleAutoResize = (ev: React.MouseEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
paneIndex.current = index;
|
this.setupPanes(ev);
|
||||||
|
|
||||||
const sizes_ = getSizes();
|
const sizes_ = this.getSizes();
|
||||||
sizes_[paneIndex.current] = 0;
|
sizes_[this.paneIndex] = 0;
|
||||||
sizes_[paneIndex.current + 1] = 0;
|
sizes_[this.paneIndex + 1] = 0;
|
||||||
|
|
||||||
const availableWidth = 1 - sum(sizes_);
|
const availableWidth = 1 - _.sum(sizes_);
|
||||||
sizes_[paneIndex.current] = availableWidth / 2;
|
sizes_[this.paneIndex] = availableWidth / 2;
|
||||||
sizes_[paneIndex.current + 1] = availableWidth / 2;
|
sizes_[this.paneIndex + 1] = availableWidth / 2;
|
||||||
|
|
||||||
props.onResize(sizes_);
|
this.props.onResize(sizes_);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragStart = (ev: React.MouseEvent<HTMLDivElement>, index: number) => {
|
handleDragStart = (ev: any) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
setDragging(true);
|
this.setState({dragging: true});
|
||||||
window.addEventListener('mousemove', onDrag);
|
window.addEventListener('mousemove', this.onDrag);
|
||||||
window.addEventListener('mouseup', onDragEnd);
|
window.addEventListener('mouseup', this.onDragEnd);
|
||||||
|
|
||||||
const target = ev.target as HTMLDivElement;
|
// dimensions to consider
|
||||||
dragTarget.current = target;
|
if (this.props.direction === 'horizontal') {
|
||||||
dragPanePosition.current = dragTarget.current.getBoundingClientRect()[d2];
|
this.d1 = 'height';
|
||||||
panesSize.current = target.parentElement!.getBoundingClientRect()[d1];
|
this.d2 = 'top';
|
||||||
paneIndex.current = index;
|
this.d3 = 'clientY';
|
||||||
|
} else {
|
||||||
|
this.d1 = 'width';
|
||||||
|
this.d2 = 'left';
|
||||||
|
this.d3 = 'clientX';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dragTarget = ev.target;
|
||||||
|
this.dragPanePosition = this.dragTarget.getBoundingClientRect()[this.d2];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
|
this.panesSize = ev.target.parentNode.getBoundingClientRect()[this.d1];
|
||||||
|
this.setupPanes(ev);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSizes = () => {
|
getSizes() {
|
||||||
const {sizes} = props;
|
const {sizes} = this.props;
|
||||||
let sizes_: number[];
|
let sizes_: number[];
|
||||||
|
|
||||||
if (sizes) {
|
if (sizes) {
|
||||||
sizes_ = [...sizes.asMutable()];
|
sizes_ = [...sizes.asMutable()];
|
||||||
} else {
|
} else {
|
||||||
const total = props.children.length;
|
const total = (this.props.children as React.ReactNodeArray).length;
|
||||||
const count = new Array<number>(total).fill(1 / total);
|
const count = new Array<number>(total).fill(1 / total);
|
||||||
|
|
||||||
sizes_ = count;
|
sizes_ = count;
|
||||||
}
|
}
|
||||||
return sizes_;
|
return sizes_;
|
||||||
};
|
}
|
||||||
|
|
||||||
const onDrag = (ev: MouseEvent) => {
|
onDrag = (ev: MouseEvent) => {
|
||||||
const sizes_ = getSizes();
|
const sizes_ = this.getSizes();
|
||||||
|
|
||||||
const i = paneIndex.current;
|
const i = this.paneIndex;
|
||||||
const pos = ev[d3];
|
const pos = ev[this.d3];
|
||||||
const d = Math.abs(dragPanePosition.current - pos) / panesSize.current!;
|
const d = Math.abs(this.dragPanePosition - pos) / this.panesSize;
|
||||||
if (pos > dragPanePosition.current) {
|
if (pos > this.dragPanePosition) {
|
||||||
sizes_[i] += d;
|
sizes_[i] += d;
|
||||||
sizes_[i + 1] -= d;
|
sizes_[i + 1] -= d;
|
||||||
} else {
|
} else {
|
||||||
sizes_[i] -= d;
|
sizes_[i] -= d;
|
||||||
sizes_[i + 1] += d;
|
sizes_[i + 1] += d;
|
||||||
}
|
}
|
||||||
props.onResize(sizes_);
|
this.props.onResize(sizes_);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDragEnd = () => {
|
onDragEnd = () => {
|
||||||
window.removeEventListener('mousemove', onDrag);
|
if (this.state.dragging) {
|
||||||
window.removeEventListener('mouseup', onDragEnd);
|
window.removeEventListener('mousemove', this.onDrag);
|
||||||
setDragging(false);
|
window.removeEventListener('mouseup', this.onDragEnd);
|
||||||
|
this.setState({dragging: false});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
render() {
|
||||||
return () => {
|
const children = this.props.children as React.ReactNodeArray;
|
||||||
onDragEnd();
|
const {direction, borderColor} = this.props;
|
||||||
};
|
const sizeProperty = direction === 'horizontal' ? 'height' : 'width';
|
||||||
}, []);
|
// workaround for the fact that if we don't specify
|
||||||
|
// sizes, sometimes flex fails to calculate the
|
||||||
const {children, direction, borderColor} = props;
|
// right height for the horizontal panes
|
||||||
const sizeProperty = direction === 'horizontal' ? 'height' : 'width';
|
const sizes = this.props.sizes || new Array<number>(children.length).fill(1 / children.length);
|
||||||
// workaround for the fact that if we don't specify
|
return (
|
||||||
// sizes, sometimes flex fails to calculate the
|
<div className={`splitpane_panes splitpane_panes_${direction}`}>
|
||||||
// right height for the horizontal panes
|
{React.Children.map(children, (child, i) => {
|
||||||
const sizes = props.sizes || new Array<number>(children.length).fill(1 / children.length);
|
const style = {
|
||||||
return (
|
// flexBasis doesn't work for the first horizontal pane, height need to be specified
|
||||||
<div className={`splitpane_panes splitpane_panes_${direction}`} ref={ref}>
|
[sizeProperty]: `${sizes[i] * 100}%`,
|
||||||
{children.map((child, i) => {
|
flexBasis: `${sizes[i] * 100}%`,
|
||||||
const style = {
|
flexGrow: 0
|
||||||
// flexBasis doesn't work for the first horizontal pane, height need to be specified
|
};
|
||||||
[sizeProperty]: `${sizes[i] * 100}%`,
|
return [
|
||||||
flexBasis: `${sizes[i] * 100}%`,
|
<div key="pane" className="splitpane_pane" style={style}>
|
||||||
flexGrow: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment key={i}>
|
|
||||||
<div className="splitpane_pane" style={style}>
|
|
||||||
{child}
|
{child}
|
||||||
</div>
|
</div>,
|
||||||
{i < children.length - 1 ? (
|
i < children.length - 1 ? (
|
||||||
<div
|
<div
|
||||||
onMouseDown={(e) => handleDragStart(e, i)}
|
key="divider"
|
||||||
onDoubleClick={(e) => handleAutoResize(e, i)}
|
onMouseDown={this.handleDragStart}
|
||||||
|
onDoubleClick={this.handleAutoResize}
|
||||||
style={{backgroundColor: borderColor}}
|
style={{backgroundColor: borderColor}}
|
||||||
className={`splitpane_divider splitpane_divider_${direction}`}
|
className={`splitpane_divider splitpane_divider_${direction}`}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null
|
||||||
</React.Fragment>
|
];
|
||||||
);
|
})}
|
||||||
})}
|
<div style={{display: this.state.dragging ? 'block' : 'none'}} className="splitpane_shim" />
|
||||||
<div style={{display: dragging ? 'block' : 'none'}} className="splitpane_shim" />
|
|
||||||
|
|
||||||
<style jsx>{`
|
<style jsx>{`
|
||||||
.splitpane_panes {
|
.splitpane_panes {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
outline: none;
|
outline: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpane_panes_vertical {
|
.splitpane_panes_vertical {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpane_panes_horizontal {
|
.splitpane_panes_horizontal {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpane_pane {
|
.splitpane_pane {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
outline: none;
|
outline: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpane_divider {
|
.splitpane_divider {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpane_divider_vertical {
|
.splitpane_divider_vertical {
|
||||||
border-left: 5px solid rgba(255, 255, 255, 0);
|
border-left: 5px solid rgba(255, 255, 255, 0);
|
||||||
border-right: 5px solid rgba(255, 255, 255, 0);
|
border-right: 5px solid rgba(255, 255, 255, 0);
|
||||||
width: 11px;
|
width: 11px;
|
||||||
margin: 0 -5px;
|
margin: 0 -5px;
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.splitpane_divider_horizontal {
|
.splitpane_divider_horizontal {
|
||||||
height: 11px;
|
height: 11px;
|
||||||
margin: -5px 0;
|
margin: -5px 0;
|
||||||
border-top: 5px solid rgba(255, 255, 255, 0);
|
border-top: 5px solid rgba(255, 255, 255, 0);
|
||||||
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
||||||
cursor: row-resize;
|
cursor: row-resize;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
this shim is used to make sure mousemove events
|
this shim is used to make sure mousemove events
|
||||||
trigger in all the draggable area of the screen
|
trigger in all the draggable area of the screen
|
||||||
this is not the case due to hterm's <iframe>
|
this is not the case due to hterm's <iframe>
|
||||||
*/
|
*/
|
||||||
.splitpane_shim {
|
.splitpane_shim {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
SplitPane.displayName = 'SplitPane';
|
componentWillUnmount() {
|
||||||
|
// ensure drag end
|
||||||
export default SplitPane;
|
if (this.dragging) {
|
||||||
|
this.onDragEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,24 @@
|
||||||
import React, {forwardRef} from 'react';
|
import React from 'react';
|
||||||
|
import {StyleSheetProps} from '../hyper';
|
||||||
|
|
||||||
import {useDevicePixelRatio} from 'use-device-pixel-ratio';
|
export default class StyleSheet extends React.PureComponent<StyleSheetProps> {
|
||||||
|
render() {
|
||||||
|
const {borderColor} = this.props;
|
||||||
|
|
||||||
import type {StyleSheetProps} from '../../typings/hyper';
|
return (
|
||||||
|
<style jsx global>{`
|
||||||
const StyleSheet = forwardRef<HTMLStyleElement, StyleSheetProps>((props, ref) => {
|
::-webkit-scrollbar {
|
||||||
const {borderColor} = props;
|
width: 5px;
|
||||||
|
}
|
||||||
const dpr = useDevicePixelRatio();
|
::-webkit-scrollbar-thumb {
|
||||||
|
-webkit-border-radius: 10px;
|
||||||
return (
|
border-radius: 10px;
|
||||||
<style jsx global ref={ref}>{`
|
background: ${borderColor};
|
||||||
::-webkit-scrollbar {
|
}
|
||||||
width: ${5 * dpr}px;
|
::-webkit-scrollbar-thumb:window-inactive {
|
||||||
}
|
background: ${borderColor};
|
||||||
::-webkit-scrollbar-thumb {
|
}
|
||||||
-webkit-border-radius: 10px;
|
`}</style>
|
||||||
border-radius: 10px;
|
);
|
||||||
background: ${borderColor};
|
}
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar-thumb:window-inactive {
|
|
||||||
background: ${borderColor};
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
StyleSheet.displayName = 'StyleSheet';
|
|
||||||
|
|
||||||
export default StyleSheet;
|
|
||||||
|
|
|
||||||
|
|
@ -1,179 +1,164 @@
|
||||||
import React, {useEffect, useRef} from 'react';
|
import React from 'react';
|
||||||
|
import {TabProps} from '../hyper';
|
||||||
|
|
||||||
import type {TabProps} from '../../typings/hyper';
|
export default class Tab extends React.PureComponent<TabProps> {
|
||||||
|
constructor(props: TabProps) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
const Tab = (props: TabProps) => {
|
handleClick = (event: React.MouseEvent) => {
|
||||||
const handleClick = (event: React.MouseEvent) => {
|
|
||||||
const isLeftClick = event.nativeEvent.which === 1;
|
const isLeftClick = event.nativeEvent.which === 1;
|
||||||
|
|
||||||
if (isLeftClick && !props.isActive) {
|
if (isLeftClick && !this.props.isActive) {
|
||||||
props.onSelect();
|
this.props.onSelect();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseUp = (event: React.MouseEvent) => {
|
handleMouseUp = (event: React.MouseEvent) => {
|
||||||
const isMiddleClick = event.nativeEvent.which === 2;
|
const isMiddleClick = event.nativeEvent.which === 2;
|
||||||
|
|
||||||
if (isMiddleClick) {
|
if (isMiddleClick) {
|
||||||
props.onClose();
|
this.props.onClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ref = useRef<HTMLLIElement>(null);
|
render() {
|
||||||
|
const {isActive, isFirst, isLast, borderColor, hasActivity} = this.props;
|
||||||
|
|
||||||
useEffect(() => {
|
return (
|
||||||
if (props.lastFocused) {
|
<React.Fragment>
|
||||||
ref?.current?.scrollIntoView({
|
<li
|
||||||
behavior: 'smooth'
|
onClick={this.props.onClick}
|
||||||
});
|
style={{borderColor}}
|
||||||
}
|
className={`tab_tab ${isFirst ? 'tab_first' : ''} ${isActive ? 'tab_active' : ''} ${
|
||||||
}, [props.lastFocused]);
|
isFirst && isActive ? 'tab_firstActive' : ''
|
||||||
|
} ${hasActivity ? 'tab_hasActivity' : ''}`}
|
||||||
const {isActive, isFirst, isLast, borderColor, hasActivity} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<li
|
|
||||||
onClick={props.onClick}
|
|
||||||
style={{borderColor}}
|
|
||||||
className={`tab_tab ${isFirst ? 'tab_first' : ''} ${isActive ? 'tab_active' : ''} ${
|
|
||||||
isFirst && isActive ? 'tab_firstActive' : ''
|
|
||||||
} ${hasActivity ? 'tab_hasActivity' : ''}`}
|
|
||||||
ref={ref}
|
|
||||||
>
|
|
||||||
{props.customChildrenBefore}
|
|
||||||
<span
|
|
||||||
className={`tab_text ${isLast ? 'tab_textLast' : ''} ${isActive ? 'tab_textActive' : ''}`}
|
|
||||||
onClick={handleClick}
|
|
||||||
onMouseUp={handleMouseUp}
|
|
||||||
>
|
>
|
||||||
<span title={props.text} className="tab_textInner">
|
{this.props.customChildrenBefore}
|
||||||
{props.text}
|
<span
|
||||||
|
className={`tab_text ${isLast ? 'tab_textLast' : ''} ${isActive ? 'tab_textActive' : ''}`}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
onMouseUp={this.handleMouseUp}
|
||||||
|
>
|
||||||
|
<span title={this.props.text} className="tab_textInner">
|
||||||
|
{this.props.text}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
<i className="tab_icon" onClick={this.props.onClose}>
|
||||||
<i className="tab_icon" onClick={props.onClose}>
|
<svg className="tab_shape">
|
||||||
<svg className="tab_shape">
|
<use xlinkHref="./renderer/assets/icons.svg#close-tab" />
|
||||||
<use xlinkHref="./renderer/assets/icons.svg#close-tab" />
|
</svg>
|
||||||
</svg>
|
</i>
|
||||||
</i>
|
{this.props.customChildren}
|
||||||
{props.customChildren}
|
</li>
|
||||||
</li>
|
|
||||||
|
|
||||||
<style jsx>{`
|
<style jsx>{`
|
||||||
.tab_tab {
|
.tab_tab {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
border-color: #ccc;
|
border-color: #ccc;
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
border-left-width: 1px;
|
border-left-width: 1px;
|
||||||
border-left-style: solid;
|
border-left-style: solid;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
min-width: 10em;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.tab_tab:hover {
|
.tab_tab:hover {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab_first {
|
.tab_first {
|
||||||
border-left-width: 0;
|
border-left-width: 0;
|
||||||
padding-left: 1px;
|
padding-left: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab_firstActive {
|
.tab_firstActive {
|
||||||
border-left-width: 1px;
|
border-left-width: 1px;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab_active {
|
.tab_active {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-bottom-width: 0;
|
border-bottom-width: 0;
|
||||||
}
|
}
|
||||||
.tab_active:hover {
|
.tab_active:hover {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab_hasActivity {
|
.tab_hasActivity {
|
||||||
color: #50e3c2;
|
color: #50e3c2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab_hasActivity:hover {
|
.tab_hasActivity:hover {
|
||||||
color: #50e3c2;
|
color: #50e3c2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab_text {
|
.tab_text {
|
||||||
transition: color 0.2s ease;
|
transition: color 0.2s ease;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab_textInner {
|
.tab_textInner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 24px;
|
left: 24px;
|
||||||
right: 24px;
|
right: 24px;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab_icon {
|
.tab_icon {
|
||||||
transition:
|
transition: opacity 0.2s ease, color 0.2s ease, transform 0.25s ease, background-color 0.1s ease;
|
||||||
opacity 0.2s ease,
|
pointer-events: none;
|
||||||
color 0.2s ease,
|
position: absolute;
|
||||||
transform 0.25s ease,
|
right: 7px;
|
||||||
background-color 0.1s ease;
|
top: 10px;
|
||||||
pointer-events: none;
|
display: inline-block;
|
||||||
position: absolute;
|
width: 14px;
|
||||||
right: 7px;
|
height: 14px;
|
||||||
top: 10px;
|
border-radius: 100%;
|
||||||
display: inline-block;
|
color: #e9e9e9;
|
||||||
width: 14px;
|
opacity: 0;
|
||||||
height: 14px;
|
transform: scale(0.95);
|
||||||
border-radius: 100%;
|
}
|
||||||
color: #e9e9e9;
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab_icon:hover {
|
.tab_icon:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.13);
|
background-color: rgba(255, 255, 255, 0.13);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab_icon:active {
|
.tab_icon:active {
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
color: #909090;
|
color: #909090;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab_tab:hover .tab_icon {
|
.tab_tab:hover .tab_icon {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: none;
|
transform: none;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab_shape {
|
.tab_shape {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 4px;
|
left: 4px;
|
||||||
top: 4px;
|
top: 4px;
|
||||||
width: 6px;
|
width: 6px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
shape-rendering: crispEdges;
|
shape-rendering: crispEdges;
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
</>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
Tab.displayName = 'Tab';
|
|
||||||
|
|
||||||
export default Tab;
|
|
||||||
|
|
|
||||||
|
|
@ -1,142 +1,105 @@
|
||||||
import React, {forwardRef, useEffect, useState} from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import debounce from 'lodash/debounce';
|
|
||||||
|
|
||||||
import type {ITab, TabsProps} from '../../typings/hyper';
|
|
||||||
import {decorate, getTabProps} from '../utils/plugins';
|
import {decorate, getTabProps} from '../utils/plugins';
|
||||||
|
|
||||||
import DropdownButton from './new-tab';
|
|
||||||
import Tab_ from './tab';
|
import Tab_ from './tab';
|
||||||
|
import {TabsProps} from '../hyper';
|
||||||
|
|
||||||
const Tab = decorate(Tab_, 'Tab');
|
const Tab = decorate(Tab_, 'Tab');
|
||||||
const isMac = /Mac/.test(navigator.userAgent);
|
const isMac = /Mac/.test(navigator.userAgent);
|
||||||
|
|
||||||
const Tabs = forwardRef<HTMLElement, TabsProps>((props, ref) => {
|
export default class Tabs extends React.PureComponent<TabsProps> {
|
||||||
const {tabs = [], borderColor, onChange, onClose, fullScreen} = props;
|
render() {
|
||||||
|
const {tabs = [], borderColor, onChange, onClose, fullScreen} = this.props;
|
||||||
|
|
||||||
const [shouldFocusCounter, setShouldFocusCounter] = useState({
|
const hide = !isMac && tabs.length === 1;
|
||||||
index: 0,
|
|
||||||
when: undefined as Date | undefined
|
|
||||||
});
|
|
||||||
|
|
||||||
const scrollToActiveTab = debounce((currTabs: ITab[]) => {
|
return (
|
||||||
const activeTab = currTabs.findIndex((t) => t.isActive);
|
<nav className={`tabs_nav ${hide ? 'tabs_hiddenNav' : ''}`}>
|
||||||
setShouldFocusCounter({
|
{this.props.customChildrenBefore}
|
||||||
index: activeTab,
|
{tabs.length === 1 && isMac ? <div className="tabs_title">{tabs[0].title}</div> : null}
|
||||||
when: new Date()
|
{tabs.length > 1
|
||||||
});
|
? [
|
||||||
}, 100);
|
<ul key="list" className={`tabs_list ${fullScreen && isMac ? 'tabs_fullScreen' : ''}`}>
|
||||||
|
{tabs.map((tab, i) => {
|
||||||
|
const {uid, title, isActive, hasActivity} = tab;
|
||||||
|
const props = getTabProps(tab, this.props, {
|
||||||
|
text: title === '' ? 'Shell' : title,
|
||||||
|
isFirst: i === 0,
|
||||||
|
isLast: tabs.length - 1 === i,
|
||||||
|
borderColor,
|
||||||
|
isActive,
|
||||||
|
hasActivity,
|
||||||
|
onSelect: onChange.bind(null, uid),
|
||||||
|
onClose: onClose.bind(null, uid)
|
||||||
|
});
|
||||||
|
return <Tab key={`tab-${uid}`} {...props} />;
|
||||||
|
})}
|
||||||
|
</ul>,
|
||||||
|
isMac && (
|
||||||
|
<div
|
||||||
|
key="shim"
|
||||||
|
style={{borderColor}}
|
||||||
|
className={`tabs_borderShim ${fullScreen ? 'tabs_borderShimUndo' : ''}`}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
]
|
||||||
|
: null}
|
||||||
|
{this.props.customChildren}
|
||||||
|
|
||||||
useEffect(() => {
|
<style jsx>{`
|
||||||
scrollToActiveTab(tabs);
|
.tabs_nav {
|
||||||
}, [tabs, tabs.length]);
|
font-size: 12px;
|
||||||
|
height: 34px;
|
||||||
|
line-height: 34px;
|
||||||
|
vertical-align: middle;
|
||||||
|
color: #9b9b9b;
|
||||||
|
cursor: default;
|
||||||
|
position: relative;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-webkit-app-region: ${isMac ? 'drag' : ''};
|
||||||
|
top: ${isMac ? '0px' : '34px'};
|
||||||
|
}
|
||||||
|
|
||||||
const hide = !isMac && tabs.length === 1;
|
.tabs_hiddenNav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
.tabs_title {
|
||||||
<nav className={`tabs_nav ${hide ? 'tabs_hiddenNav' : ''}`} ref={ref}>
|
text-align: center;
|
||||||
{props.customChildrenBefore}
|
color: #fff;
|
||||||
{tabs.length === 1 && isMac ? <div className="tabs_title">{tabs[0].title}</div> : null}
|
overflow: hidden;
|
||||||
{tabs.length > 1 ? (
|
text-overflow: ellipsis;
|
||||||
<>
|
white-space: nowrap;
|
||||||
<ul key="list" className={`tabs_list ${fullScreen && isMac ? 'tabs_fullScreen' : ''}`}>
|
padding-left: 76px;
|
||||||
{tabs.map((tab, i) => {
|
padding-right: 76px;
|
||||||
const {uid, title, isActive, hasActivity} = tab;
|
}
|
||||||
const tabProps = getTabProps(tab, props, {
|
|
||||||
text: title === '' ? 'Shell' : title,
|
|
||||||
isFirst: i === 0,
|
|
||||||
isLast: tabs.length - 1 === i,
|
|
||||||
borderColor,
|
|
||||||
isActive,
|
|
||||||
hasActivity,
|
|
||||||
onSelect: onChange.bind(null, uid),
|
|
||||||
onClose: onClose.bind(null, uid),
|
|
||||||
lastFocused: undefined as Date | undefined
|
|
||||||
});
|
|
||||||
if (shouldFocusCounter.index === i) {
|
|
||||||
tabProps.lastFocused = shouldFocusCounter.when;
|
|
||||||
}
|
|
||||||
return <Tab key={`tab-${uid}`} {...tabProps} />;
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
{isMac && (
|
|
||||||
<div
|
|
||||||
key="shim"
|
|
||||||
style={{borderColor}}
|
|
||||||
className={`tabs_borderShim ${fullScreen ? 'tabs_borderShimUndo' : ''}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
<DropdownButton {...props} tabsVisible={tabs.length > 1} />
|
|
||||||
{props.customChildren}
|
|
||||||
|
|
||||||
<style jsx>{`
|
.tabs_list {
|
||||||
.tabs_nav {
|
max-height: 34px;
|
||||||
font-size: 12px;
|
display: flex;
|
||||||
height: 34px;
|
flex-flow: row;
|
||||||
line-height: 34px;
|
margin-left: ${isMac ? '76px' : '0'};
|
||||||
vertical-align: middle;
|
}
|
||||||
color: #9b9b9b;
|
|
||||||
cursor: default;
|
|
||||||
position: relative;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-webkit-app-region: ${isMac ? 'drag' : ''};
|
|
||||||
top: ${isMac ? '0px' : '34px'};
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs_hiddenNav {
|
.tabs_fullScreen {
|
||||||
display: none;
|
margin-left: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs_title {
|
.tabs_borderShim {
|
||||||
text-align: center;
|
position: absolute;
|
||||||
color: #fff;
|
width: 76px;
|
||||||
overflow: hidden;
|
bottom: 0;
|
||||||
text-overflow: ellipsis;
|
border-color: #ccc;
|
||||||
white-space: nowrap;
|
border-bottom-style: solid;
|
||||||
padding-left: 76px;
|
border-bottom-width: 1px;
|
||||||
padding-right: 76px;
|
}
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs_list {
|
.tabs_borderShimUndo {
|
||||||
max-height: 34px;
|
border-bottom-width: 0px;
|
||||||
display: flex;
|
}
|
||||||
flex-flow: row;
|
`}</style>
|
||||||
margin-left: ${isMac ? '76px' : '0'};
|
</nav>
|
||||||
flex-grow: 1;
|
);
|
||||||
overflow-x: auto;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs_list::-webkit-scrollbar,
|
|
||||||
.tabs_list::-webkit-scrollbar-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs_fullScreen {
|
|
||||||
margin-left: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs_borderShim {
|
|
||||||
position: absolute;
|
|
||||||
width: 76px;
|
|
||||||
bottom: 0;
|
|
||||||
border-color: #ccc;
|
|
||||||
border-bottom-style: solid;
|
|
||||||
border-bottom-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs_borderShimUndo {
|
|
||||||
border-bottom-width: 0px;
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
Tabs.displayName = 'Tabs';
|
|
||||||
|
|
||||||
export default Tabs;
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
import type {HyperState, HyperDispatch, TermGroupProps, TermGroupOwnProps} from '../../typings/hyper';
|
|
||||||
import {resizeTermGroup} from '../actions/term-groups';
|
|
||||||
import {decorate, getTermProps, getTermGroupProps} from '../utils/plugins';
|
import {decorate, getTermProps, getTermGroupProps} from '../utils/plugins';
|
||||||
|
import {resizeTermGroup} from '../actions/term-groups';
|
||||||
import SplitPane_ from './split-pane';
|
|
||||||
import Term_ from './term';
|
import Term_ from './term';
|
||||||
|
import SplitPane_ from './split-pane';
|
||||||
|
import {HyperState, HyperDispatch, TermGroupProps, TermGroupOwnProps} from '../hyper';
|
||||||
|
|
||||||
const Term = decorate(Term_, 'Term');
|
const Term = decorate(Term_, 'Term');
|
||||||
const SplitPane = decorate(SplitPane_, 'SplitPane');
|
const SplitPane = decorate(SplitPane_, 'SplitPane');
|
||||||
|
|
@ -85,6 +82,7 @@ class TermGroup_ extends React.PureComponent<TermGroupProps> {
|
||||||
letterSpacing: this.props.letterSpacing,
|
letterSpacing: this.props.letterSpacing,
|
||||||
modifierKeys: this.props.modifierKeys,
|
modifierKeys: this.props.modifierKeys,
|
||||||
padding: this.props.padding,
|
padding: this.props.padding,
|
||||||
|
url: session.url,
|
||||||
cleared: session.cleared,
|
cleared: session.cleared,
|
||||||
search: session.search,
|
search: session.search,
|
||||||
cols: session.cols,
|
cols: session.cols,
|
||||||
|
|
@ -108,8 +106,6 @@ class TermGroup_ extends React.PureComponent<TermGroupProps> {
|
||||||
macOptionSelectionMode: this.props.macOptionSelectionMode,
|
macOptionSelectionMode: this.props.macOptionSelectionMode,
|
||||||
disableLigatures: this.props.disableLigatures,
|
disableLigatures: this.props.disableLigatures,
|
||||||
screenReaderMode: this.props.screenReaderMode,
|
screenReaderMode: this.props.screenReaderMode,
|
||||||
windowsPty: this.props.windowsPty,
|
|
||||||
imageSupport: this.props.imageSupport,
|
|
||||||
uid
|
uid
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,24 @@
|
||||||
import {clipboard, shell} from 'electron';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {Terminal, ITerminalOptions, IDisposable} from 'xterm';
|
||||||
import {CanvasAddon} from '@xterm/addon-canvas';
|
import {FitAddon} from 'xterm-addon-fit';
|
||||||
import {FitAddon} from '@xterm/addon-fit';
|
import {WebLinksAddon} from 'xterm-addon-web-links';
|
||||||
import {ImageAddon} from '@xterm/addon-image';
|
import {SearchAddon, ISearchDecorationOptions} from 'xterm-addon-search';
|
||||||
import {LigaturesAddon} from '@xterm/addon-ligatures';
|
import {WebglAddon} from 'xterm-addon-webgl';
|
||||||
import {SearchAddon} from '@xterm/addon-search';
|
import {LigaturesAddon} from 'xterm-addon-ligatures';
|
||||||
import type {ISearchDecorationOptions} from '@xterm/addon-search';
|
import {Unicode11Addon} from 'xterm-addon-unicode11';
|
||||||
import {Unicode11Addon} from '@xterm/addon-unicode11';
|
import {clipboard, shell} from 'electron';
|
||||||
import {WebLinksAddon} from '@xterm/addon-web-links';
|
|
||||||
import {WebglAddon} from '@xterm/addon-webgl';
|
|
||||||
import {Terminal} from '@xterm/xterm';
|
|
||||||
import type {ITerminalOptions, IDisposable} from '@xterm/xterm';
|
|
||||||
import Color from 'color';
|
import Color from 'color';
|
||||||
import isEqual from 'lodash/isEqual';
|
|
||||||
import pickBy from 'lodash/pickBy';
|
|
||||||
|
|
||||||
import type {TermProps} from '../../typings/hyper';
|
|
||||||
import terms from '../terms';
|
import terms from '../terms';
|
||||||
import processClipboard from '../utils/paste';
|
import processClipboard from '../utils/paste';
|
||||||
import {decorate} from '../utils/plugins';
|
|
||||||
|
|
||||||
import _SearchBox from './searchBox';
|
import _SearchBox from './searchBox';
|
||||||
|
import {TermProps} from '../hyper';
|
||||||
import '@xterm/xterm/css/xterm.css';
|
import {ObjectTypedKeys} from '../utils/object';
|
||||||
|
import {decorate} from '../utils/plugins';
|
||||||
|
import 'xterm/css/xterm.css';
|
||||||
|
|
||||||
const SearchBox = decorate(_SearchBox, 'SearchBox');
|
const SearchBox = decorate(_SearchBox, 'SearchBox');
|
||||||
|
|
||||||
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].includes(navigator.platform) || process.platform === 'win32';
|
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].includes(navigator.platform);
|
||||||
|
|
||||||
// map old hterm constants to xterm.js
|
// map old hterm constants to xterm.js
|
||||||
const CURSOR_STYLES = {
|
const CURSOR_STYLES = {
|
||||||
|
|
@ -51,7 +42,7 @@ const isWebgl2Supported = (() => {
|
||||||
const getTermOptions = (props: TermProps): ITerminalOptions => {
|
const getTermOptions = (props: TermProps): ITerminalOptions => {
|
||||||
// Set a background color only if it is opaque
|
// Set a background color only if it is opaque
|
||||||
const needTransparency = Color(props.backgroundColor).alpha() < 1;
|
const needTransparency = Color(props.backgroundColor).alpha() < 1;
|
||||||
const backgroundColor = needTransparency ? 'rgba(0,0,0,0)' : props.backgroundColor;
|
const backgroundColor = needTransparency ? 'transparent' : props.backgroundColor;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
macOptionIsMeta: props.modifierKeys.altIsMeta,
|
macOptionIsMeta: props.modifierKeys.altIsMeta,
|
||||||
|
|
@ -66,14 +57,14 @@ const getTermOptions = (props: TermProps): ITerminalOptions => {
|
||||||
letterSpacing: props.letterSpacing,
|
letterSpacing: props.letterSpacing,
|
||||||
allowTransparency: needTransparency,
|
allowTransparency: needTransparency,
|
||||||
macOptionClickForcesSelection: props.macOptionSelectionMode === 'force',
|
macOptionClickForcesSelection: props.macOptionSelectionMode === 'force',
|
||||||
|
bellStyle: props.bell === 'SOUND' ? 'sound' : 'none',
|
||||||
windowsMode: isWindows,
|
windowsMode: isWindows,
|
||||||
...(isWindows && props.windowsPty && {windowsPty: props.windowsPty}),
|
|
||||||
theme: {
|
theme: {
|
||||||
foreground: props.foregroundColor,
|
foreground: props.foregroundColor,
|
||||||
background: backgroundColor,
|
background: backgroundColor,
|
||||||
cursor: props.cursorColor,
|
cursor: props.cursorColor,
|
||||||
cursorAccent: props.cursorAccentColor,
|
cursorAccent: props.cursorAccentColor,
|
||||||
selectionBackground: props.selectionColor,
|
selection: props.selectionColor,
|
||||||
black: props.colors.black,
|
black: props.colors.black,
|
||||||
red: props.colors.red,
|
red: props.colors.red,
|
||||||
green: props.colors.green,
|
green: props.colors.green,
|
||||||
|
|
@ -92,8 +83,7 @@ const getTermOptions = (props: TermProps): ITerminalOptions => {
|
||||||
brightWhite: props.colors.lightWhite
|
brightWhite: props.colors.lightWhite
|
||||||
},
|
},
|
||||||
screenReaderMode: props.screenReaderMode,
|
screenReaderMode: props.screenReaderMode,
|
||||||
overviewRulerWidth: 20,
|
overviewRulerWidth: 20
|
||||||
allowProposedApi: true
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -117,8 +107,7 @@ export default class Term extends React.PureComponent<
|
||||||
termWrapperRef: HTMLElement | null;
|
termWrapperRef: HTMLElement | null;
|
||||||
termOptions: ITerminalOptions;
|
termOptions: ITerminalOptions;
|
||||||
disposableListeners: IDisposable[];
|
disposableListeners: IDisposable[];
|
||||||
defaultBellSound: HTMLAudioElement | null;
|
termDefaultBellSound: string | null;
|
||||||
bellSound: HTMLAudioElement | null;
|
|
||||||
fitAddon: FitAddon;
|
fitAddon: FitAddon;
|
||||||
searchAddon: SearchAddon;
|
searchAddon: SearchAddon;
|
||||||
static rendererTypes: Record<string, string>;
|
static rendererTypes: Record<string, string>;
|
||||||
|
|
@ -142,8 +131,7 @@ export default class Term extends React.PureComponent<
|
||||||
this.termWrapperRef = null;
|
this.termWrapperRef = null;
|
||||||
this.termOptions = {};
|
this.termOptions = {};
|
||||||
this.disposableListeners = [];
|
this.disposableListeners = [];
|
||||||
this.defaultBellSound = null;
|
this.termDefaultBellSound = null;
|
||||||
this.bellSound = null;
|
|
||||||
this.fitAddon = new FitAddon();
|
this.fitAddon = new FitAddon();
|
||||||
this.searchAddon = new SearchAddon();
|
this.searchAddon = new SearchAddon();
|
||||||
this.searchDecorations = {
|
this.searchDecorations = {
|
||||||
|
|
@ -170,14 +158,7 @@ export default class Term extends React.PureComponent<
|
||||||
|
|
||||||
this.termOptions = getTermOptions(props);
|
this.termOptions = getTermOptions(props);
|
||||||
this.term = props.term || new Terminal(this.termOptions);
|
this.term = props.term || new Terminal(this.termOptions);
|
||||||
this.defaultBellSound = new Audio(
|
this.termDefaultBellSound = this.term.getOption('bellSound');
|
||||||
// Source: https://freesound.org/people/altemark/sounds/45759/
|
|
||||||
// This sound is released under the Creative Commons Attribution 3.0 Unported
|
|
||||||
// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been
|
|
||||||
// made, apart from the conversion to base64.
|
|
||||||
'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'
|
|
||||||
);
|
|
||||||
this.setBellSound(props.bell, props.bellSound);
|
|
||||||
|
|
||||||
// The parent element for the terminal is attached and removed manually so
|
// The parent element for the terminal is attached and removed manually so
|
||||||
// that we can preserve it across mounts and unmounts of the component
|
// that we can preserve it across mounts and unmounts of the component
|
||||||
|
|
@ -205,9 +186,9 @@ export default class Term extends React.PureComponent<
|
||||||
}
|
}
|
||||||
Term.reportRenderer(props.uid, useWebGL ? 'WebGL' : 'Canvas');
|
Term.reportRenderer(props.uid, useWebGL ? 'WebGL' : 'Canvas');
|
||||||
|
|
||||||
const shallActivateWebLink = (event: MouseEvent): boolean => {
|
const shallActivateWebLink = (event: Record<string, any> | undefined): boolean => {
|
||||||
if (!event) return false;
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
return props.webLinksActivationKey ? event[`${props.webLinksActivationKey}Key`] : true;
|
return event && (!props.webLinksActivationKey || event[`${props.webLinksActivationKey}Key`]);
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
|
|
@ -215,34 +196,29 @@ export default class Term extends React.PureComponent<
|
||||||
this.term.loadAddon(this.fitAddon);
|
this.term.loadAddon(this.fitAddon);
|
||||||
this.term.loadAddon(this.searchAddon);
|
this.term.loadAddon(this.searchAddon);
|
||||||
this.term.loadAddon(
|
this.term.loadAddon(
|
||||||
new WebLinksAddon((event, uri) => {
|
new WebLinksAddon(
|
||||||
if (shallActivateWebLink(event)) void shell.openExternal(uri);
|
(event: MouseEvent | undefined, uri: string) => {
|
||||||
})
|
if (shallActivateWebLink(event)) void shell.openExternal(uri);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// prevent default electron link handling to allow selection, e.g. via double-click
|
||||||
|
willLinkActivate: (event: MouseEvent | undefined) => {
|
||||||
|
event?.preventDefault();
|
||||||
|
return shallActivateWebLink(event);
|
||||||
|
},
|
||||||
|
priority: Date.now()
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
this.term.open(this.termRef);
|
this.term.open(this.termRef);
|
||||||
|
|
||||||
if (useWebGL) {
|
if (useWebGL) {
|
||||||
const webglAddon = new WebglAddon();
|
this.term.loadAddon(new WebglAddon());
|
||||||
this.term.loadAddon(webglAddon);
|
|
||||||
webglAddon.onContextLoss(() => {
|
|
||||||
console.warn('WebGL context lost. Falling back to canvas-based rendering.');
|
|
||||||
webglAddon.dispose();
|
|
||||||
this.term.loadAddon(new CanvasAddon());
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.term.loadAddon(new CanvasAddon());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.disableLigatures !== true && !useWebGL) {
|
if (props.disableLigatures !== true && !useWebGL) {
|
||||||
this.term.loadAddon(new LigaturesAddon());
|
this.term.loadAddon(new LigaturesAddon());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.term.loadAddon(new Unicode11Addon());
|
this.term.loadAddon(new Unicode11Addon());
|
||||||
this.term.unicode.activeVersion = '11';
|
this.term.unicode.activeVersion = '11';
|
||||||
|
|
||||||
if (props.imageSupport) {
|
|
||||||
this.term.loadAddon(new ImageAddon());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// get the cached plugins
|
// get the cached plugins
|
||||||
this.fitAddon = props.fitAddon!;
|
this.fitAddon = props.fitAddon!;
|
||||||
|
|
@ -276,10 +252,6 @@ export default class Term extends React.PureComponent<
|
||||||
this.disposableListeners.push(this.term.onData(props.onData));
|
this.disposableListeners.push(this.term.onData(props.onData));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.term.onBell(() => {
|
|
||||||
this.ringBell();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (props.onResize) {
|
if (props.onResize) {
|
||||||
this.disposableListeners.push(
|
this.disposableListeners.push(
|
||||||
this.term.onResize(({cols, rows}) => {
|
this.term.onResize(({cols, rows}) => {
|
||||||
|
|
@ -421,18 +393,6 @@ export default class Term extends React.PureComponent<
|
||||||
return !e.catched;
|
return !e.catched;
|
||||||
}
|
}
|
||||||
|
|
||||||
setBellSound(bell: 'SOUND' | false, sound: string | null) {
|
|
||||||
if (bell && bell.toUpperCase() === 'SOUND') {
|
|
||||||
this.bellSound = sound ? new Audio(sound) : this.defaultBellSound;
|
|
||||||
} else {
|
|
||||||
this.bellSound = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ringBell() {
|
|
||||||
void this.bellSound?.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps: TermProps) {
|
componentDidUpdate(prevProps: TermProps) {
|
||||||
if (!prevProps.cleared && this.props.cleared) {
|
if (!prevProps.cleared && this.props.cleared) {
|
||||||
this.clear();
|
this.clear();
|
||||||
|
|
@ -440,19 +400,40 @@ export default class Term extends React.PureComponent<
|
||||||
|
|
||||||
const nextTermOptions = getTermOptions(this.props);
|
const nextTermOptions = getTermOptions(this.props);
|
||||||
|
|
||||||
if (prevProps.bell !== this.props.bell || prevProps.bellSound !== this.props.bellSound) {
|
// Use bellSound in nextProps if it exists
|
||||||
this.setBellSound(this.props.bell, this.props.bellSound);
|
// otherwise use the default sound found in xterm.
|
||||||
}
|
nextTermOptions.bellSound = this.props.bellSound || this.termDefaultBellSound!;
|
||||||
|
|
||||||
if (prevProps.search && !this.props.search) {
|
if (prevProps.search && !this.props.search) {
|
||||||
this.closeSearchBox();
|
this.closeSearchBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update only options that have changed.
|
// Update only options that have changed.
|
||||||
this.term.options = pickBy(
|
ObjectTypedKeys(nextTermOptions)
|
||||||
nextTermOptions,
|
.filter((option) => option !== 'theme' && nextTermOptions[option] !== this.termOptions[option])
|
||||||
(value, key) => !isEqual(this.termOptions[key as keyof ITerminalOptions], value)
|
.forEach((option) => {
|
||||||
);
|
try {
|
||||||
|
this.term.setOption(option, nextTermOptions[option]);
|
||||||
|
} catch (_e) {
|
||||||
|
const e = _e as {message: string};
|
||||||
|
if (/The webgl renderer only works with the webgl char atlas/i.test(e.message)) {
|
||||||
|
// Ignore this because the char atlas will also be changed
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Do we need to update theme?
|
||||||
|
const shouldUpdateTheme =
|
||||||
|
!this.termOptions.theme ||
|
||||||
|
nextTermOptions.rendererType !== this.termOptions.rendererType ||
|
||||||
|
ObjectTypedKeys(nextTermOptions.theme!).some(
|
||||||
|
(option) => nextTermOptions.theme![option] !== this.termOptions.theme![option]
|
||||||
|
);
|
||||||
|
if (shouldUpdateTheme) {
|
||||||
|
this.term.setOption('theme', nextTermOptions.theme);
|
||||||
|
}
|
||||||
|
|
||||||
this.termOptions = nextTermOptions;
|
this.termOptions = nextTermOptions;
|
||||||
|
|
||||||
|
|
@ -518,7 +499,6 @@ export default class Term extends React.PureComponent<
|
||||||
{this.props.customChildren}
|
{this.props.customChildren}
|
||||||
{this.props.search ? (
|
{this.props.search ? (
|
||||||
<SearchBox
|
<SearchBox
|
||||||
dateFocused={this.props.search}
|
|
||||||
next={this.searchNext}
|
next={this.searchNext}
|
||||||
prev={this.searchPrevious}
|
prev={this.searchPrevious}
|
||||||
close={this.closeSearchBox}
|
close={this.closeSearchBox}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import type {TermsProps, HyperDispatch} from '../../typings/hyper';
|
|
||||||
import {registerCommandHandlers} from '../command-registry';
|
|
||||||
import {ObjectTypedKeys} from '../utils/object';
|
|
||||||
import {decorate, getTermGroupProps} from '../utils/plugins';
|
import {decorate, getTermGroupProps} from '../utils/plugins';
|
||||||
|
import {registerCommandHandlers} from '../command-registry';
|
||||||
import StyleSheet_ from './style-sheet';
|
|
||||||
import type Term from './term';
|
|
||||||
import TermGroup_ from './term-group';
|
import TermGroup_ from './term-group';
|
||||||
|
import StyleSheet_ from './style-sheet';
|
||||||
|
import {TermsProps, HyperDispatch} from '../hyper';
|
||||||
|
import Term from './term';
|
||||||
|
import {ObjectTypedKeys} from '../utils/object';
|
||||||
|
|
||||||
const TermGroup = decorate(TermGroup_, 'TermGroup');
|
const TermGroup = decorate(TermGroup_, 'TermGroup');
|
||||||
const StyleSheet = decorate(StyleSheet_, 'StyleSheet');
|
const StyleSheet = decorate(StyleSheet_, 'StyleSheet');
|
||||||
|
|
||||||
const isMac = /Mac/.test(navigator.userAgent);
|
const isMac = /Mac/.test(navigator.userAgent);
|
||||||
|
|
||||||
export default class Terms extends React.Component<React.PropsWithChildren<TermsProps>> {
|
export default class Terms extends React.Component<TermsProps> {
|
||||||
terms: Record<string, Term>;
|
terms: Record<string, Term>;
|
||||||
registerCommands: (cmds: Record<string, (e: any, dispatch: HyperDispatch) => void>) => void;
|
registerCommands: (cmds: Record<string, (e: any, dispatch: HyperDispatch) => void>) => void;
|
||||||
constructor(props: TermsProps, context: any) {
|
constructor(props: TermsProps, context: any) {
|
||||||
|
|
@ -121,8 +119,6 @@ export default class Terms extends React.Component<React.PropsWithChildren<Terms
|
||||||
macOptionSelectionMode: this.props.macOptionSelectionMode,
|
macOptionSelectionMode: this.props.macOptionSelectionMode,
|
||||||
disableLigatures: this.props.disableLigatures,
|
disableLigatures: this.props.disableLigatures,
|
||||||
screenReaderMode: this.props.screenReaderMode,
|
screenReaderMode: this.props.screenReaderMode,
|
||||||
windowsPty: this.props.windowsPty,
|
|
||||||
imageSupport: this.props.imageSupport,
|
|
||||||
parentProps: this.props
|
parentProps: this.props
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
50
typings/config.d.ts → lib/config.d.ts
vendored
50
typings/config.d.ts → lib/config.d.ts
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
import type {FontWeight} from '@xterm/xterm';
|
import {FontWeight} from 'xterm';
|
||||||
|
|
||||||
export type ColorMap = {
|
export type ColorMap = {
|
||||||
black: string;
|
black: string;
|
||||||
|
|
@ -19,22 +19,12 @@ export type ColorMap = {
|
||||||
yellow: string;
|
yellow: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type rootConfigOptions = {
|
export type configOptions = {
|
||||||
/**
|
/**
|
||||||
* if `true` (default), Hyper will update plugins every 5 hours
|
* if `true` (default), Hyper will update plugins every 5 hours
|
||||||
* you can also set it to a custom time e.g. `1d` or `2h`
|
* you can also set it to a custom time e.g. `1d` or `2h`
|
||||||
*/
|
*/
|
||||||
autoUpdatePlugins: boolean | string;
|
autoUpdatePlugins: boolean | string;
|
||||||
/** if `true` hyper will be set as the default protocol client for SSH */
|
|
||||||
defaultSSHApp: boolean;
|
|
||||||
/** if `true` hyper will not check for updates */
|
|
||||||
disableAutoUpdates: boolean;
|
|
||||||
/** choose either `'stable'` for receiving highly polished, or `'canary'` for less polished but more frequent updates */
|
|
||||||
updateChannel: 'stable' | 'canary';
|
|
||||||
useConpty?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type profileConfigOptions = {
|
|
||||||
/**
|
/**
|
||||||
* terminal background color
|
* terminal background color
|
||||||
*
|
*
|
||||||
|
|
@ -46,7 +36,7 @@ type profileConfigOptions = {
|
||||||
* 1. 'SOUND' -> Enables the bell as a sound
|
* 1. 'SOUND' -> Enables the bell as a sound
|
||||||
* 2. false: turns off the bell
|
* 2. false: turns off the bell
|
||||||
*/
|
*/
|
||||||
bell: 'SOUND' | false;
|
bell: string;
|
||||||
/**
|
/**
|
||||||
* base64 encoded string of the sound file to use for the bell
|
* base64 encoded string of the sound file to use for the bell
|
||||||
* if null, the default bell will be used
|
* if null, the default bell will be used
|
||||||
|
|
@ -78,6 +68,10 @@ type profileConfigOptions = {
|
||||||
cursorColor: string;
|
cursorColor: string;
|
||||||
/** `'BEAM'` for |, `'UNDERLINE'` for _, `'BLOCK'` for █ */
|
/** `'BEAM'` for |, `'UNDERLINE'` for _, `'BLOCK'` for █ */
|
||||||
cursorShape: 'BEAM' | 'UNDERLINE' | 'BLOCK';
|
cursorShape: 'BEAM' | 'UNDERLINE' | 'BLOCK';
|
||||||
|
/** if `true` hyper will be set as the default protocol client for SSH */
|
||||||
|
defaultSSHApp: boolean;
|
||||||
|
/** if `true` hyper will not check for updates */
|
||||||
|
disableAutoUpdates: boolean;
|
||||||
/** if `false` Hyper will use ligatures provided by some fonts */
|
/** if `false` Hyper will use ligatures provided by some fonts */
|
||||||
disableLigatures: boolean;
|
disableLigatures: boolean;
|
||||||
/** for environment variables */
|
/** for environment variables */
|
||||||
|
|
@ -92,10 +86,6 @@ type profileConfigOptions = {
|
||||||
fontWeightBold: FontWeight;
|
fontWeightBold: FontWeight;
|
||||||
/** color of the text */
|
/** color of the text */
|
||||||
foregroundColor: string;
|
foregroundColor: string;
|
||||||
/**
|
|
||||||
* Whether to enable Sixel and iTerm2 inline image protocol support or not.
|
|
||||||
*/
|
|
||||||
imageSupport: boolean;
|
|
||||||
/** letter spacing as a relative unit */
|
/** letter spacing as a relative unit */
|
||||||
letterSpacing: number;
|
letterSpacing: number;
|
||||||
/** line height as a relative unit */
|
/** line height as a relative unit */
|
||||||
|
|
@ -129,7 +119,7 @@ type profileConfigOptions = {
|
||||||
/** terminal selection color */
|
/** terminal selection color */
|
||||||
selectionColor: string;
|
selectionColor: string;
|
||||||
/**
|
/**
|
||||||
* the shell to run when spawning a new session (e.g. /usr/local/bin/fish)
|
* the shell to run when spawning a new session (i.e. /usr/local/bin/fish)
|
||||||
* if left empty, your system's login shell will be used by default
|
* if left empty, your system's login shell will be used by default
|
||||||
*
|
*
|
||||||
* Windows
|
* Windows
|
||||||
|
|
@ -154,7 +144,7 @@ type profileConfigOptions = {
|
||||||
*/
|
*/
|
||||||
shell: string;
|
shell: string;
|
||||||
/**
|
/**
|
||||||
* for setting shell arguments (e.g. for using interactive shellArgs: `['-i']`)
|
* for setting shell arguments (i.e. for using interactive shellArgs: `['-i']`)
|
||||||
* by default `['--login']` will be used
|
* by default `['--login']` will be used
|
||||||
*/
|
*/
|
||||||
shellArgs: string[];
|
shellArgs: string[];
|
||||||
|
|
@ -175,6 +165,9 @@ type profileConfigOptions = {
|
||||||
/** custom CSS to embed in the terminal window */
|
/** custom CSS to embed in the terminal window */
|
||||||
termCSS: string;
|
termCSS: string;
|
||||||
uiFontFamily?: string;
|
uiFontFamily?: string;
|
||||||
|
/** choose either `'stable'` for receiving highly polished, or `'canary'` for less polished but more frequent updates */
|
||||||
|
updateChannel: 'stable' | 'canary';
|
||||||
|
useConpty?: boolean;
|
||||||
/**
|
/**
|
||||||
* Whether to use the WebGL renderer. Set it to false to use canvas-based
|
* Whether to use the WebGL renderer. Set it to false to use canvas-based
|
||||||
* rendering (slower, but supports transparent backgrounds)
|
* rendering (slower, but supports transparent backgrounds)
|
||||||
|
|
@ -191,25 +184,6 @@ type profileConfigOptions = {
|
||||||
workingDirectory: string;
|
workingDirectory: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type configOptions = rootConfigOptions &
|
|
||||||
profileConfigOptions & {
|
|
||||||
/**
|
|
||||||
* The default profile name to use when launching a new session
|
|
||||||
*/
|
|
||||||
defaultProfile: string;
|
|
||||||
/**
|
|
||||||
* A list of profiles to use
|
|
||||||
*/
|
|
||||||
profiles: {
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* Specify all the options you want to override for each profile.
|
|
||||||
* Options set here override the defaults set in the root.
|
|
||||||
*/
|
|
||||||
config: Partial<profileConfigOptions>;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type rawConfig = {
|
export type rawConfig = {
|
||||||
config?: configOptions;
|
config?: configOptions;
|
||||||
/**
|
/**
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type {configOptions} from '../config';
|
import {configOptions} from '../config';
|
||||||
|
|
||||||
export const CONFIG_LOAD = 'CONFIG_LOAD';
|
export const CONFIG_LOAD = 'CONFIG_LOAD';
|
||||||
export const CONFIG_RELOAD = 'CONFIG_RELOAD';
|
export const CONFIG_RELOAD = 'CONFIG_RELOAD';
|
||||||
|
|
@ -8,6 +8,8 @@ export const SESSION_USER_EXIT = 'SESSION_USER_EXIT';
|
||||||
export const SESSION_SET_ACTIVE = 'SESSION_SET_ACTIVE';
|
export const SESSION_SET_ACTIVE = 'SESSION_SET_ACTIVE';
|
||||||
export const SESSION_CLEAR_ACTIVE = 'SESSION_CLEAR_ACTIVE';
|
export const SESSION_CLEAR_ACTIVE = 'SESSION_CLEAR_ACTIVE';
|
||||||
export const SESSION_USER_DATA = 'SESSION_USER_DATA';
|
export const SESSION_USER_DATA = 'SESSION_USER_DATA';
|
||||||
|
export const SESSION_URL_SET = 'SESSION_URL_SET';
|
||||||
|
export const SESSION_URL_UNSET = 'SESSION_URL_UNSET';
|
||||||
export const SESSION_SET_XTERM_TITLE = 'SESSION_SET_XTERM_TITLE';
|
export const SESSION_SET_XTERM_TITLE = 'SESSION_SET_XTERM_TITLE';
|
||||||
export const SESSION_SET_CWD = 'SESSION_SET_CWD';
|
export const SESSION_SET_CWD = 'SESSION_SET_CWD';
|
||||||
export const SESSION_SEARCH = 'SESSION_SEARCH';
|
export const SESSION_SEARCH = 'SESSION_SEARCH';
|
||||||
|
|
@ -22,7 +24,6 @@ export interface SessionAddAction {
|
||||||
splitDirection?: 'HORIZONTAL' | 'VERTICAL';
|
splitDirection?: 'HORIZONTAL' | 'VERTICAL';
|
||||||
activeUid: string | null;
|
activeUid: string | null;
|
||||||
now: number;
|
now: number;
|
||||||
profile: string;
|
|
||||||
}
|
}
|
||||||
export interface SessionResizeAction {
|
export interface SessionResizeAction {
|
||||||
type: typeof SESSION_RESIZE;
|
type: typeof SESSION_RESIZE;
|
||||||
|
|
@ -40,7 +41,6 @@ export interface SessionAddDataAction {
|
||||||
}
|
}
|
||||||
export interface SessionPtyDataAction {
|
export interface SessionPtyDataAction {
|
||||||
type: typeof SESSION_PTY_DATA;
|
type: typeof SESSION_PTY_DATA;
|
||||||
data: string;
|
|
||||||
uid: string;
|
uid: string;
|
||||||
now: number;
|
now: number;
|
||||||
}
|
}
|
||||||
|
|
@ -62,6 +62,14 @@ export interface SessionClearActiveAction {
|
||||||
export interface SessionUserDataAction {
|
export interface SessionUserDataAction {
|
||||||
type: typeof SESSION_USER_DATA;
|
type: typeof SESSION_USER_DATA;
|
||||||
}
|
}
|
||||||
|
export interface SessionUrlSetAction {
|
||||||
|
type: typeof SESSION_URL_SET;
|
||||||
|
uid: string;
|
||||||
|
}
|
||||||
|
export interface SessionUrlUnsetAction {
|
||||||
|
type: typeof SESSION_URL_UNSET;
|
||||||
|
uid: string;
|
||||||
|
}
|
||||||
export interface SessionSetXtermTitleAction {
|
export interface SessionSetXtermTitleAction {
|
||||||
type: typeof SESSION_SET_XTERM_TITLE;
|
type: typeof SESSION_SET_XTERM_TITLE;
|
||||||
uid: string;
|
uid: string;
|
||||||
|
|
@ -74,7 +82,7 @@ export interface SessionSetCwdAction {
|
||||||
export interface SessionSearchAction {
|
export interface SessionSearchAction {
|
||||||
type: typeof SESSION_SEARCH;
|
type: typeof SESSION_SEARCH;
|
||||||
uid: string;
|
uid: string;
|
||||||
value: Date | null;
|
value: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SessionActions =
|
export type SessionActions =
|
||||||
|
|
@ -88,6 +96,8 @@ export type SessionActions =
|
||||||
| SessionSetActiveAction
|
| SessionSetActiveAction
|
||||||
| SessionClearActiveAction
|
| SessionClearActiveAction
|
||||||
| SessionUserDataAction
|
| SessionUserDataAction
|
||||||
|
| SessionUrlSetAction
|
||||||
|
| SessionUrlUnsetAction
|
||||||
| SessionSetXtermTitleAction
|
| SessionSetXtermTitleAction
|
||||||
| SessionSetCwdAction
|
| SessionSetCwdAction
|
||||||
| SessionSearchAction;
|
| SessionSearchAction;
|
||||||
|
|
@ -2,10 +2,10 @@ export const TERM_GROUP_REQUEST = 'TERM_GROUP_REQUEST';
|
||||||
export const TERM_GROUP_EXIT = 'TERM_GROUP_EXIT';
|
export const TERM_GROUP_EXIT = 'TERM_GROUP_EXIT';
|
||||||
export const TERM_GROUP_RESIZE = 'TERM_GROUP_RESIZE';
|
export const TERM_GROUP_RESIZE = 'TERM_GROUP_RESIZE';
|
||||||
export const TERM_GROUP_EXIT_ACTIVE = 'TERM_GROUP_EXIT_ACTIVE';
|
export const TERM_GROUP_EXIT_ACTIVE = 'TERM_GROUP_EXIT_ACTIVE';
|
||||||
export enum DIRECTION {
|
export const DIRECTION = {
|
||||||
HORIZONTAL = 'HORIZONTAL',
|
HORIZONTAL: 'HORIZONTAL',
|
||||||
VERTICAL = 'VERTICAL'
|
VERTICAL: 'VERTICAL'
|
||||||
}
|
} as const;
|
||||||
|
|
||||||
export interface TermGroupRequestAction {
|
export interface TermGroupRequestAction {
|
||||||
type: typeof TERM_GROUP_REQUEST;
|
type: typeof TERM_GROUP_REQUEST;
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import {createSelector} from 'reselect';
|
import {createSelector} from 'reselect';
|
||||||
|
|
||||||
import type {HyperState, HyperDispatch, ITab} from '../../typings/hyper';
|
|
||||||
import {closeTab, changeTab, maximize, openHamburgerMenu, unmaximize, minimize, close} from '../actions/header';
|
|
||||||
import {requestTermGroup} from '../actions/term-groups';
|
|
||||||
import Header from '../components/header';
|
import Header from '../components/header';
|
||||||
import {getRootGroups} from '../selectors';
|
import {closeTab, changeTab, maximize, openHamburgerMenu, unmaximize, minimize, close} from '../actions/header';
|
||||||
import {connect} from '../utils/plugins';
|
import {connect} from '../utils/plugins';
|
||||||
|
import {getRootGroups} from '../selectors';
|
||||||
|
import {HyperState, HyperDispatch, ITab} from '../hyper';
|
||||||
|
|
||||||
const isMac = /Mac/.test(navigator.userAgent);
|
const isMac = /Mac/.test(navigator.userAgent);
|
||||||
|
|
||||||
|
|
@ -39,9 +38,7 @@ const mapStateToProps = (state: HyperState) => {
|
||||||
maximized: state.ui.maximized,
|
maximized: state.ui.maximized,
|
||||||
fullScreen: state.ui.fullScreen,
|
fullScreen: state.ui.fullScreen,
|
||||||
showHamburgerMenu: state.ui.showHamburgerMenu,
|
showHamburgerMenu: state.ui.showHamburgerMenu,
|
||||||
showWindowControls: state.ui.showWindowControls,
|
showWindowControls: state.ui.showWindowControls
|
||||||
defaultProfile: state.ui.defaultProfile,
|
|
||||||
profiles: state.ui.profiles
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -73,10 +70,6 @@ const mapDispatchToProps = (dispatch: HyperDispatch) => {
|
||||||
|
|
||||||
close: () => {
|
close: () => {
|
||||||
dispatch(close());
|
dispatch(close());
|
||||||
},
|
|
||||||
|
|
||||||
openNewTab: (profile: string) => {
|
|
||||||
dispatch(requestTermGroup(undefined, profile));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,142 +1,149 @@
|
||||||
import React, {forwardRef, useEffect, useRef} from 'react';
|
import React from 'react';
|
||||||
|
import Mousetrap, {MousetrapInstance} from 'mousetrap';
|
||||||
|
|
||||||
import Mousetrap from 'mousetrap';
|
import {connect} from '../utils/plugins';
|
||||||
import type {MousetrapInstance} from 'mousetrap';
|
|
||||||
import stylis from 'stylis';
|
|
||||||
|
|
||||||
import type {HyperState, HyperProps, HyperDispatch} from '../../typings/hyper';
|
|
||||||
import * as uiActions from '../actions/ui';
|
import * as uiActions from '../actions/ui';
|
||||||
import {getRegisteredKeys, getCommandHandler, shouldPreventDefault} from '../command-registry';
|
import {getRegisteredKeys, getCommandHandler, shouldPreventDefault} from '../command-registry';
|
||||||
import type Terms from '../components/terms';
|
import stylis from 'stylis';
|
||||||
import {connect} from '../utils/plugins';
|
|
||||||
|
|
||||||
import {HeaderContainer} from './header';
|
import {HeaderContainer} from './header';
|
||||||
import NotificationsContainer from './notifications';
|
|
||||||
import TermsContainer from './terms';
|
import TermsContainer from './terms';
|
||||||
|
import NotificationsContainer from './notifications';
|
||||||
|
import {HyperState, HyperProps, HyperDispatch} from '../hyper';
|
||||||
|
import Terms from '../components/terms';
|
||||||
|
|
||||||
const isMac = /Mac/.test(navigator.userAgent);
|
const isMac = /Mac/.test(navigator.userAgent);
|
||||||
|
|
||||||
const Hyper = forwardRef<HTMLDivElement, HyperProps>((props, ref) => {
|
class Hyper extends React.PureComponent<HyperProps> {
|
||||||
const mousetrap = useRef<MousetrapInstance | null>(null);
|
mousetrap!: MousetrapInstance;
|
||||||
const terms = useRef<Terms | null>(null);
|
terms!: Terms;
|
||||||
|
constructor(props: HyperProps) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
componentDidUpdate(prev: HyperProps) {
|
||||||
void attachKeyListeners();
|
if (this.props.backgroundColor !== prev.backgroundColor) {
|
||||||
}, [props.lastConfigUpdate]);
|
// this can be removed when `setBackgroundColor` in electron
|
||||||
useEffect(() => {
|
// starts working again
|
||||||
handleFocusActive(props.activeSession);
|
document.body.style.backgroundColor = this.props.backgroundColor;
|
||||||
}, [props.activeSession]);
|
}
|
||||||
|
const {lastConfigUpdate} = this.props;
|
||||||
|
if (lastConfigUpdate && lastConfigUpdate !== prev.lastConfigUpdate) {
|
||||||
|
this.attachKeyListeners();
|
||||||
|
}
|
||||||
|
if (prev.activeSession !== this.props.activeSession) {
|
||||||
|
this.handleFocusActive(this.props.activeSession!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleFocusActive = (uid?: string | null) => {
|
handleFocusActive = (uid?: string) => {
|
||||||
const term = uid && terms.current?.getTermByUid(uid);
|
const term = uid && this.terms.getTermByUid(uid);
|
||||||
if (term) {
|
if (term) {
|
||||||
term.focus();
|
term.focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectAll = () => {
|
handleSelectAll = () => {
|
||||||
const term = terms.current?.getActiveTerm();
|
const term = this.terms.getActiveTerm();
|
||||||
if (term) {
|
if (term) {
|
||||||
term.selectAll();
|
term.selectAll();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const attachKeyListeners = async () => {
|
attachKeyListeners() {
|
||||||
if (!mousetrap.current) {
|
if (!this.mousetrap) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
mousetrap.current = new (Mousetrap as any)(window, true);
|
this.mousetrap = new (Mousetrap as any)(window, true);
|
||||||
mousetrap.current!.stopCallback = () => {
|
this.mousetrap.stopCallback = () => {
|
||||||
// All events should be intercepted even if focus is in an input/textarea
|
// All events should be intercepted even if focus is in an input/textarea
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
mousetrap.current.reset();
|
this.mousetrap.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
const keys = await getRegisteredKeys();
|
const keys = getRegisteredKeys();
|
||||||
Object.keys(keys).forEach((commandKeys) => {
|
Object.keys(keys).forEach((commandKeys) => {
|
||||||
mousetrap.current?.bind(
|
this.mousetrap.bind(
|
||||||
commandKeys,
|
commandKeys,
|
||||||
(e) => {
|
(e) => {
|
||||||
const command = keys[commandKeys];
|
const command = keys[commandKeys];
|
||||||
// We should tell xterm to ignore this event.
|
// We should tell to xterm that it should ignore this event.
|
||||||
(e as any).catched = true;
|
(e as any).catched = true;
|
||||||
props.execCommand(command, getCommandHandler(command), e);
|
this.props.execCommand(command, getCommandHandler(command), e);
|
||||||
shouldPreventDefault(command) && e.preventDefault();
|
shouldPreventDefault(command) && e.preventDefault();
|
||||||
},
|
},
|
||||||
'keydown'
|
'keydown'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
componentDidMount() {
|
||||||
void attachKeyListeners();
|
this.attachKeyListeners();
|
||||||
window.rpc.on('term selectAll', handleSelectAll);
|
window.rpc.on('term selectAll', this.handleSelectAll);
|
||||||
}, []);
|
}
|
||||||
|
|
||||||
const onTermsRef = (_terms: Terms | null) => {
|
onTermsRef = (terms: Terms) => {
|
||||||
terms.current = _terms;
|
this.terms = terms;
|
||||||
window.focusActiveTerm = (uid?: string) => {
|
window.focusActiveTerm = (uid?: string) => {
|
||||||
if (uid) {
|
if (uid) {
|
||||||
handleFocusActive(uid);
|
this.handleFocusActive(uid);
|
||||||
} else {
|
} else {
|
||||||
terms.current?.getActiveTerm()?.focus();
|
this.terms.getActiveTerm().focus();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
componentWillUnmount() {
|
||||||
return () => {
|
document.body.style.backgroundColor = 'inherit';
|
||||||
mousetrap.current?.reset();
|
this.mousetrap?.reset();
|
||||||
};
|
}
|
||||||
}, []);
|
|
||||||
|
|
||||||
const {isMac: isMac_, customCSS, uiFontFamily, borderColor, maximized, fullScreen} = props;
|
render() {
|
||||||
const borderWidth = isMac_ ? '' : `${maximized ? '0' : '1'}px`;
|
const {isMac: isMac_, customCSS, uiFontFamily, borderColor, maximized, fullScreen} = this.props;
|
||||||
stylis.set({prefix: false});
|
const borderWidth = isMac_ ? '' : `${maximized ? '0' : '1'}px`;
|
||||||
return (
|
stylis.set({prefix: false});
|
||||||
<div id="hyper" ref={ref}>
|
return (
|
||||||
<div
|
<div id="hyper">
|
||||||
style={{fontFamily: uiFontFamily, borderColor, borderWidth}}
|
<div
|
||||||
className={`hyper_main ${isMac_ && 'hyper_mainRounded'} ${fullScreen ? 'fullScreen' : ''}`}
|
style={{fontFamily: uiFontFamily, borderColor, borderWidth}}
|
||||||
>
|
className={`hyper_main ${isMac_ && 'hyper_mainRounded'} ${fullScreen ? 'fullScreen' : ''}`}
|
||||||
<HeaderContainer />
|
>
|
||||||
<TermsContainer ref_={onTermsRef} />
|
<HeaderContainer />
|
||||||
{props.customInnerChildren}
|
<TermsContainer ref_={this.onTermsRef} />
|
||||||
|
{this.props.customInnerChildren}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<NotificationsContainer />
|
||||||
|
|
||||||
|
{this.props.customChildren}
|
||||||
|
|
||||||
|
<style jsx>
|
||||||
|
{`
|
||||||
|
.hyper_main {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hyper_mainRounded {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{/*
|
||||||
|
Add custom CSS to Hyper.
|
||||||
|
We add a scope to the customCSS so that it can get around the weighting applied by styled-jsx
|
||||||
|
*/}
|
||||||
|
<style dangerouslySetInnerHTML={{__html: stylis('#hyper', customCSS)}} />
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
<NotificationsContainer />
|
}
|
||||||
|
}
|
||||||
{props.customChildren}
|
|
||||||
|
|
||||||
<style jsx>
|
|
||||||
{`
|
|
||||||
.hyper_main {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
border: 1px solid #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hyper_mainRounded {
|
|
||||||
border-radius: 10.5px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{/*
|
|
||||||
Add custom CSS to Hyper.
|
|
||||||
We add a scope to the customCSS so that it can get around the weighting applied by styled-jsx
|
|
||||||
*/}
|
|
||||||
<style dangerouslySetInnerHTML={{__html: stylis('#hyper', customCSS)}} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
Hyper.displayName = 'Hyper';
|
|
||||||
|
|
||||||
const mapStateToProps = (state: HyperState) => {
|
const mapStateToProps = (state: HyperState) => {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import type {HyperState, HyperDispatch} from '../../typings/hyper';
|
|
||||||
import {dismissNotification} from '../actions/notifications';
|
|
||||||
import {installUpdate} from '../actions/updater';
|
|
||||||
import Notifications from '../components/notifications';
|
import Notifications from '../components/notifications';
|
||||||
|
import {installUpdate} from '../actions/updater';
|
||||||
import {connect} from '../utils/plugins';
|
import {connect} from '../utils/plugins';
|
||||||
|
import {dismissNotification} from '../actions/notifications';
|
||||||
|
import {HyperState, HyperDispatch} from '../hyper';
|
||||||
|
|
||||||
const mapStateToProps = (state: HyperState) => {
|
const mapStateToProps = (state: HyperState) => {
|
||||||
const {ui} = state;
|
const {ui} = state;
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue