diff --git a/backend/tabby/app/urls.py b/backend/tabby/app/urls.py index bb224df..698969c 100644 --- a/backend/tabby/app/urls.py +++ b/backend/tabby/app/urls.py @@ -15,7 +15,7 @@ urlpatterns = [ path('api/1/instance-info', api.InstanceInfoViewSet.as_view({'get': 'retrieve'})), path('api/1/gateways/choose', api.ChooseGatewayViewSet.as_view({'post': 'retrieve'})), - re_path('^(|login|app)$', views.IndexView.as_view()), + re_path('^(|login|app|about|features)$', views.IndexView.as_view()), path('terminal', views.TerminalView.as_view()), path('app-dist//', views.AppDistView.as_view()), diff --git a/frontend/package.json b/frontend/package.json index f0ba260..5e7d75f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -69,9 +69,11 @@ "throng": "^5.0.0", "typescript": "~4.1", "val-loader": "^4.0.0", + "vanta": "^0.5.21", "webpack": "^5.38.1", "webpack-bundle-analyzer": "^4.4.2", "webpack-cli": "^4.7.2", + "three": "^0.119.0", "zone.js": "^0.11.4" } } diff --git a/frontend/src/app.module.ts b/frontend/src/app.module.ts index b23192b..4a21d90 100644 --- a/frontend/src/app.module.ts +++ b/frontend/src/app.module.ts @@ -1,5 +1,5 @@ import { NgModule } from '@angular/core' -import { NgbDropdownModule, NgbModalModule } from '@ng-bootstrap/ng-bootstrap' +import { NgbDropdownModule, NgbModalModule, NgbNavModule } from '@ng-bootstrap/ng-bootstrap' import { BrowserModule } from '@angular/platform-browser' import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { CommonModule } from '@angular/common' @@ -16,10 +16,13 @@ import { AppComponent } from './components/app.component' import { MainComponent } from './components/main.component' import { ConfigModalComponent } from './components/configModal.component' import { SettingsModalComponent } from './components/settingsModal.component' -import { HomeComponent } from './components/home.component' +import { HomeComponent, HomeComponentPreloadResolver } from './components/home.component' import { LoginComponent } from './components/login.component' import { ConnectionListComponent } from './components/connectionList.component' import { UpgradeModalComponent } from './components/upgradeModal.component' +import { HomeIndexComponent } from './components/homeIndex.component' +import { DemoTerminalComponent } from './components/demoTerminal.component' +import { HomeFeaturesComponent } from './components/homeFeatures.component' import { InstanceInfoResolver } from './api' import '@fortawesome/fontawesome-svg-core/styles.css' @@ -30,7 +33,18 @@ const ROUTES = [ component: HomeComponent, resolve: { instanceInfo: InstanceInfoResolver, + preload: HomeComponentPreloadResolver, }, + children: [ + { + path: '', + component: HomeIndexComponent, + }, + { + path: 'features', + component: HomeFeaturesComponent, + }, + ], }, { path: 'app', @@ -61,6 +75,7 @@ const ROUTES = [ HttpClientXsrfModule, NgbDropdownModule, NgbModalModule, + NgbNavModule, FontAwesomeModule, ClipboardModule, NgxImageZoomModule, @@ -74,11 +89,13 @@ const ROUTES = [ AppComponent, MainComponent, HomeComponent, + HomeIndexComponent, LoginComponent, ConfigModalComponent, SettingsModalComponent, ConnectionListComponent, UpgradeModalComponent, + DemoTerminalComponent, ], bootstrap: [AppComponent], }) diff --git a/frontend/src/assets/screenshots/progress.png b/frontend/src/assets/screenshots/progress.png new file mode 100644 index 0000000..5ed8eb9 Binary files /dev/null and b/frontend/src/assets/screenshots/progress.png differ diff --git a/frontend/src/assets/screenshots/zmodem.png b/frontend/src/assets/screenshots/zmodem.png new file mode 100644 index 0000000..44342fc Binary files /dev/null and b/frontend/src/assets/screenshots/zmodem.png differ diff --git a/frontend/src/components/demoTerminal.component.scss b/frontend/src/components/demoTerminal.component.scss new file mode 100644 index 0000000..293c847 --- /dev/null +++ b/frontend/src/components/demoTerminal.component.scss @@ -0,0 +1,16 @@ +@import "../theme/vars.scss"; + +:host { + display: flex; + flex-direction: column; + overflow: hidden; + border-radius: 5px; + background: $body-bg; + box-shadow: 0 0 2px black, 0 0 50px #6ef2ff05, 0 0 150px #6854ff14; +} + +iframe { + flex: auto; + display: block; + overflow: hidden; +} diff --git a/frontend/src/components/demoTerminal.component.ts b/frontend/src/components/demoTerminal.component.ts new file mode 100644 index 0000000..299662b --- /dev/null +++ b/frontend/src/components/demoTerminal.component.ts @@ -0,0 +1,102 @@ +import { Subject } from 'rxjs' +import * as semverCompare from 'semver/functions/compare-loose' +import { HttpClient } from '@angular/common/http' +import { Component, ElementRef, ViewChild } from '@angular/core' +import { Version } from '../api' +import { CommonService } from '../services/common.service' + +class DemoConnector { + constructor ( + targetWindow: Window, + private commonService: CommonService, + private version: Version, + ) { + targetWindow['tabbyWebDemoDataPath'] = `${this.getDistURL()}/${version.version}/tabby-web-demo/data` + } + + async loadConfig (): Promise { + return `{ + recoverTabs: false, + web: { + preventAccidentalTabClosure: false, + }, + }` + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + async saveConfig (_content: string): Promise { } + + getAppVersion (): string { + return this.version.version + } + + getDistURL (): string { + return this.commonService.backendURL + '/app-dist' + } + + getPluginsToLoad (): string[] { + return [ + 'tabby-core', + 'tabby-settings', + 'tabby-terminal', + 'tabby-community-color-schemes', + 'tabby-ssh', + 'tabby-telnet', + 'tabby-web', + 'tabby-web-demo', + ] + } + + createSocket () { + return new DemoSocketProxy() + } +} + + +export class DemoSocketProxy { + connect$ = new Subject() + data$ = new Subject() + error$ = new Subject() + close$ = new Subject() + + async connect (options) { + this.error$.next(new Error('This web demo can\'t actually access Internet, but feel free to download the release and try it out!')) + } +} + +@Component({ + selector: 'demo-terminal', + template: '', + styleUrls: ['./demoTerminal.component.scss'], +}) +export class DemoTerminalComponent { + @ViewChild('iframe') iframe: ElementRef + connector: DemoConnector + + + constructor ( + private http: HttpClient, + private commonService: CommonService, + ) { + window.addEventListener('message', this.connectorRequestHandler) + } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + connectorRequestHandler = event => { + if (event.data === 'request-connector') { + this.iframe.nativeElement.contentWindow['__connector__'] = this.connector + this.iframe.nativeElement.contentWindow.postMessage('connector-ready', '*') + } + } + + async ngAfterViewInit (): Promise { + const versions = await this.http.get('/api/1/versions').toPromise() + versions.sort((a, b) => -semverCompare(a.version, b.version)) + this.connector = new DemoConnector(this.iframe.nativeElement.contentWindow, this.commonService, versions[0]) + this.iframe.nativeElement.src = '/terminal' + } + + ngOnDestroy (): void { + window.removeEventListener('message', this.connectorRequestHandler) + } +} diff --git a/frontend/src/components/home.component.pug b/frontend/src/components/home.component.pug index 32e3c57..1f4118b 100644 --- a/frontend/src/components/home.component.pug +++ b/frontend/src/components/home.component.pug @@ -1,159 +1,32 @@ .top-half - .navbar - img.brand(src='{{_logo}}') - .me-auto - a.btn.btn-primary([href]='releaseURL', target='_blank') - fa-icon([icon]='_downloadIcon', [fixedWidth]='true') - span Download - a.btn.btn-secondary([href]='donationURL', target='_blank') - fa-icon([icon]='_donateIcon', [fixedWidth]='true') - span Donate - a.btn.btn-secondary(routerLink='/login', *ngIf='instanceInfo.login_enabled') - fa-icon([icon]='_loginIcon', [fixedWidth]='true') - span Login + .container.overflow-hidden + .navbar + img.brand(src='{{_logo}}') + .me-auto + a.btn.btn-primary([href]='releaseURL', target='_blank') + fa-icon([icon]='_downloadIcon', [fixedWidth]='true') + span Download + a.btn.btn-secondary([href]='donationURL', target='_blank') + fa-icon([icon]='_donateIcon', [fixedWidth]='true') + span Donate + a.btn.btn-secondary(routerLink='/login', *ngIf='instanceInfo.login_enabled') + fa-icon([icon]='_loginIcon', [fixedWidth]='true') + span Login + + ul.nav-pills.mb-4(ngbNav, [activeId]='router.url') + li([ngbNavItem]='link.link', *ngFor='let link of navLinks') + a(ngbNavLink, routerLink='.', [routerLink]='link.link') {{ link.title }} .container - .intro - h1 Hey. - div My name is Eugene and I've built a nice terminal app, #[em.ms-1 just for you]. - div Crossplatform, local, SSH, serial, Telnet - it's all there. - div Here's a demo 👇 + div(*ngIf='router.url == "/"') + .intro + h1 Hey. + div My name is Eugene and I've built a nice terminal app, #[em.ms-1 just for you]. + div Crossplatform, local, SSH, serial, Telnet - it's all there. + div Here's a demo 👇 - iframe(#iframe) + demo-terminal .bottom-half - .container - .d-flex.m-auto.mb-5 - a.btn.btn-lg.btn-primary.ms-auto.me-3([href]='releaseURL', target='_blank') - fa-icon([icon]='_downloadIcon', [fixedWidth]='true') - span Latest release - - a.btn.btn-lg.btn-secondary.me-auto([href]='githubURL', target='_blank') - fa-icon([icon]='_githubIcon', [fixedWidth]='true') - span GitHub - - .quotes - .quote - .text "reasonable" - .author — Michael - - .quote - .text "cool" - .author — some dude on my ko-fi - - .quote - .text "very cool" - .author — datsukan - - .section.section-a - .container - .row - .col-12.col-xl-6 - lib-ngx-image-zoom( - [fullImage]='screenshots.window', - [thumbImage]='screenshots.window' - ) - .col-12.col-xl-6 - h1 The important stuff - ul - li Runs on #[strong Windows, Mac and Linux] - li Integrated #[strong SSH client] with a connection manager - li Integrated #[strong serial terminal] - li PowerShell, PS Core, WSL, Git-Bash, Cygwin, Cmder and CMD support - li Full #[strong Unicode support] including double-width characters - li File transfer from/to SSH sessions via #[strong SFTP and Zmodem] - li Theming and color schemes - li Fully #[strong configurable shortcuts] and multi-chord shortcuts - li #[strong Remembers your tabs] and split panes - li Proper shell experience on Windows including #[strong tab completion] - li Integrated #[strong encrypted container] for SSH secrets and configuration - - .section.section-b - .container - .row - .col-12.col-xl-6 - h1 Terminal features - ul - li Multiple #[strong nested panes] - li #[strong Progress bars] and activity notifications for tabs - li Tabby remembers open tabs and panes where you left off - li Tabs on #[strong any side of the window] - li Optional #[strong quake mode] (terminal docked to a side of the screen) - li Optional #[strong global hotkey] to focus/hide the terminal - li Bracketed paste - .col-12.col-xl-6 - lib-ngx-image-zoom( - [fullImage]='screenshots.tabs', - [thumbImage]='screenshots.tabs' - ) - - .section.section-a - .container - .row - .col-12.col-xl-6 - lib-ngx-image-zoom( - [fullImage]='screenshots.ssh', - [thumbImage]='screenshots.ssh' - ) - .col-12.col-xl-6 - h1 SSH Client - ul - li SSH2 client with a connection manager - li #[strong SFTP and Zmodem] file transfers - li #[strong X11] and #[strong port forwarding] - li Jump hosts - li #[strong Agent forwarding] - including Pageant and Windows native OpenSSH Agent - li Login scripts - li Optional built-in #[strong password manager] with a master passphrase - li #[strong Proxy command] support - - .section.section-b - .container - .row - .col-12.col-xl-6 - h1 Windows, but nice - ul - li Support for #[strong different shells] in the same window - li Better tab completion #[strong cmd.exe] thanks to Clink. - li Explorer menu integration - li Optional #[strong portable mode] - li Current directory detection that works - .col-12.col-xl-6 - lib-ngx-image-zoom( - [fullImage]='screenshots.win', - [thumbImage]='screenshots.win' - ) - - .section.section-a - .container - .row - .col-12.col-xl-6 - lib-ngx-image-zoom( - [fullImage]='screenshots.serial', - [thumbImage]='screenshots.serial' - ) - .col-12.col-xl-6 - h1 Serial Terminal - ul - li Multiple #[strong connection profiles] - li Newline conversion - li Text, #[strong readline] and #[strong byte-by-byte] input modes - li Text and #[strong hexdump] output modes - li Zmodem - li Non-standard baud rates - - .section.section-a - .container - h1 And just too much stuff to mention here: - ul - li Themes #[strong customizable with CSS] - li Extensible via #[strong plugins] (in JS) - li A bunch of color schemes already included - li Telnet client - li #[strong Font ligatures] and font fallback - li #[strong Clickable URLs], IPs and paths - li #[strong WinSCP] integration - li Shell #[strong profiles] - li Simultaneous #[strong multi-pane input] - li Optional PuTTY style #[strong right-click paste] and #[strong copy on select] - li macOS vibrancy and Win 10 fluent background support + .demo-offset(*ngIf='router.url == "/"') + router-outlet diff --git a/frontend/src/components/home.component.scss b/frontend/src/components/home.component.scss index d37a415..b4f20a6 100644 --- a/frontend/src/components/home.component.scss +++ b/frontend/src/components/home.component.scss @@ -4,18 +4,33 @@ :host { font-size: 20px; font-family: 'Fira Code', monospace; - - button, a, .quote { - font-family: $font-family-sans-serif; - } + position: absolute; + width: 100vw; + height: 100vh; + overflow: auto; } .top-half { - background: linear-gradient(#0c141c, #15202b); + background: linear-gradient(#0c141c00, #15202b); h1 { font-size: 80px; } + + .nav { + font-size: 14px; + padding: 0 35px; + } +} + +.demo-offset { + padding-top: 24vw; +} + +.bottom-half { + background: $body-bg; + overflow: hidden; + min-height: 100vh; } .navbar { @@ -31,87 +46,11 @@ width: 32px; } -h1 { - font-family: $font-family-monospace; - font-weight: bold; - font-size: 48px; - text-shadow: 0 0 1px black; - margin: 0 0 25px; -} - -.intro { - width: 60vw; - margin: auto; -} - -iframe { - display: block; +demo-terminal { margin: auto; + width: calc(min(max(480px, 60vw), 100vw)); + height: calc(max(460px, 42vw)); position: relative; top: 20vw; margin-top: -16vw; - - width: calc(min(max(480px, 60vw), 100vw)); - height: calc(max(460px, 42vw)); - overflow: hidden; - border-radius: 5px; - box-shadow: 0 0 2px black, 0 0 50px #6ef2ff05, 0 0 150px #6854ff14; -} - - -.bottom-half { - padding-top: 24vw; -} - -.quotes { - margin: 50px 0; - display: flex; - justify-content: center; - text-align: center; - - .quote { - margin: 0 30px; - - .text { - font-size: 50px; - font-style: italic; - } - - .author { - font-size: 14px; - } - } - - @media (max-width: 600px) { - & { display: none;} - } -} - -strong { - background: #849dff; - font-weight: normal; - padding: 2px 7px; - color: black; -} - -.section { - padding: 50px 0; -} - -.section-a { - background: rgba(0, 0, 0, .5); -} - -.section-b { -} - -::ng-deep lib-ngx-image-zoom { - width: 100%; - display: block; - - img { - min-width: 100px; - max-width: 100%; - width: 100%; - } } diff --git a/frontend/src/components/home.component.ts b/frontend/src/components/home.component.ts index 350024b..9bb19f5 100644 --- a/frontend/src/components/home.component.ts +++ b/frontend/src/components/home.component.ts @@ -1,72 +1,7 @@ -import { Subject } from 'rxjs' -import * as semverCompare from 'semver/functions/compare-loose' -import { HttpClient } from '@angular/common/http' -import { Component, ElementRef, ViewChild } from '@angular/core' -import { InstanceInfo, Version } from '../api' +import { Component, Injectable } from '@angular/core' +import { ActivatedRoute, Router, Resolve } from '@angular/router' import { faCoffee, faDownload, faSignInAlt } from '@fortawesome/free-solid-svg-icons' -import { faGithub } from '@fortawesome/free-brands-svg-icons' -import { ActivatedRoute, Router } from '@angular/router' -import { CommonService } from '../services/common.service' - - -class DemoConnector { - constructor ( - targetWindow: Window, - private commonService: CommonService, - private version: Version, - ) { - targetWindow['tabbyWebDemoDataPath'] = `${this.getDistURL()}/${version.version}/tabby-web-demo/data` - } - - async loadConfig (): Promise { - return `{ - recoverTabs: false, - web: { - preventAccidentalTabClosure: false, - }, - }` - } - - // eslint-disable-next-line @typescript-eslint/no-empty-function - async saveConfig (_content: string): Promise { } - - getAppVersion (): string { - return this.version.version - } - - getDistURL (): string { - return this.commonService.backendURL + '/app-dist' - } - - getPluginsToLoad (): string[] { - return [ - 'tabby-core', - 'tabby-settings', - 'tabby-terminal', - 'tabby-community-color-schemes', - 'tabby-ssh', - 'tabby-telnet', - 'tabby-web', - 'tabby-web-demo', - ] - } - - createSocket () { - return new DemoSocketProxy() - } -} - - -export class DemoSocketProxy { - connect$ = new Subject() - data$ = new Subject() - error$ = new Subject() - close$ = new Subject() - - async connect (options) { - this.error$.next(new Error('This web demo can\'t actually access Internet, but feel free to download the release and try it out!')) - } -} +import { InstanceInfo } from '../api' @Component({ @@ -75,8 +10,6 @@ export class DemoSocketProxy { styleUrls: ['./home.component.scss'], }) export class HomeComponent { - @ViewChild('iframe') iframe: ElementRef - connector: DemoConnector githubURL = 'https://github.com/Eugeny/tabby' releaseURL = `${this.githubURL}/releases/latest` donationURL = 'https://ko-fi.com/eugeny' @@ -84,48 +17,55 @@ export class HomeComponent { _logo = require('../assets/logo.svg') _downloadIcon = faDownload _loginIcon = faSignInAlt - _githubIcon = faGithub _donateIcon = faCoffee - screenshots = { - window: require('../assets/screenshots/window.png'), - tabs: require('../assets/screenshots/tabs.png'), - ssh: require('../assets/screenshots/ssh.png'), - serial: require('../assets/screenshots/serial.png'), - win: require('../assets/screenshots/win.png'), - } + navLinks = [ + { + title: 'About Tabby', + link: '/' + }, + { + title: 'Features', + link: '/features' + }, + ] instanceInfo: InstanceInfo constructor ( - private http: HttpClient, - private commonService: CommonService, - route: ActivatedRoute, - router: Router, + public route: ActivatedRoute, + public router: Router, ) { - window.addEventListener('message', this.connectorRequestHandler) this.instanceInfo = route.snapshot.data.instanceInfo if (!this.instanceInfo.homepage_enabled) { router.navigate(['/app']) } } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - connectorRequestHandler = event => { - if (event.data === 'request-connector') { - this.iframe.nativeElement.contentWindow['__connector__'] = this.connector - this.iframe.nativeElement.contentWindow.postMessage('connector-ready', '*') - } + static async preload () { + const three = await import(/* webpackChunkName: "gfx" */ 'three') + window['THREE'] = three + await import(/* webpackChunkName: "gfx" */ 'vanta/src/vanta.waves.js') } async ngAfterViewInit (): Promise { - const versions = await this.http.get('/api/1/versions').toPromise() - versions.sort((a, b) => -semverCompare(a.version, b.version)) - this.connector = new DemoConnector(this.iframe.nativeElement.contentWindow, this.commonService, versions[0]) - this.iframe.nativeElement.src = '/terminal' - } - - ngOnDestroy (): void { - window.removeEventListener('message', this.connectorRequestHandler) + window['VANTA'].WAVES({ + el: 'body', + mouseControls: true, + touchControls: true, + gyroControls: false, + minHeight: 200.00, + minWidth: 200.00, + scale: 1.00, + scaleMobile: 1.00, + color: 0x70f + }) + } +} + +@Injectable({ providedIn: 'root' }) +export class HomeComponentPreloadResolver implements Resolve> { + resolve () { + return HomeComponent.preload() } } diff --git a/frontend/src/components/homeFeatures.component.pug b/frontend/src/components/homeFeatures.component.pug new file mode 100644 index 0000000..c53fc0e --- /dev/null +++ b/frontend/src/components/homeFeatures.component.pug @@ -0,0 +1,17 @@ +.container.mt-5.mb-5 + h1 Features + + .row + .col-12.col-md-4 + .card.bg-dark + img.card-img-top([src]='screenshots.progress') + .card-body + h5.card-title Smart tabs + .card-text Tabs that detect progress and can notify you when a process is done. + + .col-12.col-md-4 + .card.bg-dark + img.card-img-top([src]='screenshots.zmodem') + .card-body + h5.card-title Zmodem transfers + .card-text Full support for Zmodem transfers in SSH, telnet and serial connections. diff --git a/frontend/src/components/homeFeatures.component.scss b/frontend/src/components/homeFeatures.component.scss new file mode 100644 index 0000000..53a165a --- /dev/null +++ b/frontend/src/components/homeFeatures.component.scss @@ -0,0 +1,3 @@ +:host { + font-size: 14px; +} diff --git a/frontend/src/components/homeFeatures.component.ts b/frontend/src/components/homeFeatures.component.ts new file mode 100644 index 0000000..a14ea2f --- /dev/null +++ b/frontend/src/components/homeFeatures.component.ts @@ -0,0 +1,13 @@ +import { Component } from '@angular/core' + +@Component({ + selector: 'home-features', + templateUrl: './homeFeatures.component.pug', + styleUrls: ['./homeFeatures.component.scss'], +}) +export class HomeFeaturesComponent { + screenshots = { + progress: require('../assets/screenshots/progress.png'), + zmodem: require('../assets/screenshots/zmodem.png'), + } +} diff --git a/frontend/src/components/homeIndex.component.pug b/frontend/src/components/homeIndex.component.pug new file mode 100644 index 0000000..8978370 --- /dev/null +++ b/frontend/src/components/homeIndex.component.pug @@ -0,0 +1,135 @@ +.container + .d-flex.m-auto.mb-5 + a.btn.btn-lg.btn-primary.ms-auto.me-3([href]='releaseURL', target='_blank') + fa-icon([icon]='_downloadIcon', [fixedWidth]='true') + span Latest release + + a.btn.btn-lg.btn-secondary.me-auto([href]='githubURL', target='_blank') + fa-icon([icon]='_githubIcon', [fixedWidth]='true') + span GitHub + + .quotes + .quote + .text "reasonable" + .author — Michael + + .quote + .text "cool" + .author — some dude on my ko-fi + + .quote + .text "very cool" + .author — datsukan + +.section.section-a + .container + .row + .col-12.col-xl-6 + lib-ngx-image-zoom( + [fullImage]='screenshots.window', + [thumbImage]='screenshots.window' + ) + .col-12.col-xl-6 + h1 The important stuff + ul + li Runs on #[strong Windows, Mac and Linux] + li Integrated #[strong SSH client] with a connection manager + li Integrated #[strong serial terminal] + li PowerShell, PS Core, WSL, Git-Bash, Cygwin, Cmder and CMD support + li Full #[strong Unicode support] including double-width characters + li File transfer from/to SSH sessions via #[strong SFTP and Zmodem] + li Theming and color schemes + li Fully #[strong configurable shortcuts] and multi-chord shortcuts + li #[strong Remembers your tabs] and split panes + li Proper shell experience on Windows including #[strong tab completion] + li Integrated #[strong encrypted container] for SSH secrets and configuration + +.section.section-b + .container + .row + .col-12.col-xl-6 + h1 Terminal features + ul + li Multiple #[strong nested panes] + li #[strong Progress bars] and activity notifications for tabs + li Tabby remembers open tabs and panes where you left off + li Tabs on #[strong any side of the window] + li Optional #[strong quake mode] (terminal docked to a side of the screen) + li Optional #[strong global hotkey] to focus/hide the terminal + li Bracketed paste + .col-12.col-xl-6 + lib-ngx-image-zoom( + [fullImage]='screenshots.tabs', + [thumbImage]='screenshots.tabs' + ) + +.section.section-a + .container + .row + .col-12.col-xl-6 + lib-ngx-image-zoom( + [fullImage]='screenshots.ssh', + [thumbImage]='screenshots.ssh' + ) + .col-12.col-xl-6 + h1 SSH Client + ul + li SSH2 client with a connection manager + li #[strong SFTP and Zmodem] file transfers + li #[strong X11] and #[strong port forwarding] + li Jump hosts + li #[strong Agent forwarding] - including Pageant and Windows native OpenSSH Agent + li Login scripts + li Optional built-in #[strong password manager] with a master passphrase + li #[strong Proxy command] support + +.section.section-b + .container + .row + .col-12.col-xl-6 + h1 Windows, but nice + ul + li Support for #[strong different shells] in the same window + li Better tab completion #[strong cmd.exe] thanks to Clink. + li Explorer menu integration + li Optional #[strong portable mode] + li Current directory detection that works + .col-12.col-xl-6 + lib-ngx-image-zoom( + [fullImage]='screenshots.win', + [thumbImage]='screenshots.win' + ) + +.section.section-a + .container + .row + .col-12.col-xl-6 + lib-ngx-image-zoom( + [fullImage]='screenshots.serial', + [thumbImage]='screenshots.serial' + ) + .col-12.col-xl-6 + h1 Serial Terminal + ul + li Multiple #[strong connection profiles] + li Newline conversion + li Text, #[strong readline] and #[strong byte-by-byte] input modes + li Text and #[strong hexdump] output modes + li Zmodem + li Non-standard baud rates + +.section.section-a + .container + h1 And just too much stuff to mention here: + ul + li Themes #[strong customizable with CSS] + li Extensible via #[strong plugins] (in JS) + li A bunch of color schemes already included + li Telnet client + li #[strong Font ligatures] and font fallback + li #[strong Clickable URLs], IPs and paths + li #[strong WinSCP] integration + li Shell #[strong profiles] + li Simultaneous #[strong multi-pane input] + li Optional PuTTY style #[strong right-click paste] and #[strong copy on select] + li macOS vibrancy and Win 10 fluent background support diff --git a/frontend/src/components/homeIndex.component.scss b/frontend/src/components/homeIndex.component.scss new file mode 100644 index 0000000..f760751 --- /dev/null +++ b/frontend/src/components/homeIndex.component.scss @@ -0,0 +1,71 @@ +@import "../theme/vars.scss"; + +h1 { + font-family: $font-family-monospace; + font-weight: bold; + font-size: 48px; + text-shadow: 0 0 1px black; + margin: 0 0 25px; +} + +button, a, .quote { + font-family: $font-family-sans-serif; +} + +.intro { + width: 60vw; + margin: auto; +} + +.quotes { + margin: 50px 0; + display: flex; + justify-content: center; + text-align: center; + + .quote { + margin: 0 30px; + + .text { + font-size: 50px; + font-style: italic; + } + + .author { + font-size: 14px; + } + } + + @media (max-width: 600px) { + & { display: none;} + } +} + +strong { + background: #849dff; + font-weight: normal; + padding: 2px 7px; + color: black; +} + +.section { + padding: 50px 0; +} + +.section-a { + background: rgba(0, 0, 0, .5); +} + +.section-b { +} + +::ng-deep lib-ngx-image-zoom { + width: 100%; + display: block; + + img { + min-width: 100px; + max-width: 100%; + width: 100%; + } +} diff --git a/frontend/src/components/homeIndex.component.ts b/frontend/src/components/homeIndex.component.ts new file mode 100644 index 0000000..0baa38b --- /dev/null +++ b/frontend/src/components/homeIndex.component.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core' +import { faDownload } from '@fortawesome/free-solid-svg-icons' +import { faGithub } from '@fortawesome/free-brands-svg-icons' + +@Component({ + selector: 'home-index', + templateUrl: './homeIndex.component.pug', + styleUrls: ['./homeIndex.component.scss'], +}) +export class HomeIndexComponent { + githubURL = 'https://github.com/Eugeny/tabby' + releaseURL = `${this.githubURL}/releases/latest` + + _downloadIcon = faDownload + _githubIcon = faGithub + + screenshots = { + window: require('../assets/screenshots/window.png'), + tabs: require('../assets/screenshots/tabs.png'), + ssh: require('../assets/screenshots/ssh.png'), + serial: require('../assets/screenshots/serial.png'), + win: require('../assets/screenshots/win.png'), + } +} diff --git a/frontend/src/theme/index.scss b/frontend/src/theme/index.scss index 38e382d..84bb2c9 100644 --- a/frontend/src/theme/index.scss +++ b/frontend/src/theme/index.scss @@ -17,11 +17,11 @@ @import "~bootstrap/scss/transitions"; @import "~bootstrap/scss/dropdown"; @import "~bootstrap/scss/button-group"; -// @import "~bootstrap/scss/nav"; +@import "~bootstrap/scss/nav"; // @import "~bootstrap/scss/navbar"; @import "~bootstrap/scss/card"; // @import "~bootstrap/scss/accordion"; -// @import "~bootstrap/scss/breadcrum/b"; +// @import "~bootstrap/scss/breadcrumb"; // @import "~bootstrap/scss/pagination"; @import "~bootstrap/scss/badge"; @import "~bootstrap/scss/alert"; @@ -58,6 +58,10 @@ background-color: $gray-700; } +body { + min-height: 100vh; +} + .dropdown-menu { box-shadow: $dropdown-box-shadow; diff --git a/frontend/src/theme/vars.scss b/frontend/src/theme/vars.scss index fac6560..1a6ac52 100644 --- a/frontend/src/theme/vars.scss +++ b/frontend/src/theme/vars.scss @@ -140,6 +140,8 @@ $nav-tabs-link-active-color: #eee; $nav-tabs-link-active-bg: transparent; $nav-tabs-link-active-border-color: #eee; +$nav-pills-link-active-bg: rgba(255, 255, 255, .125); + $navbar-padding-y: 0; $navbar-padding-x: 0; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 7122cb7..3f74b27 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "baseUrl": "src/", - "module": "es2015", + "module": "esnext", "target": "es6", "moduleResolution": "node", "noImplicitAny": false, diff --git a/frontend/webpack.config.base.js b/frontend/webpack.config.base.js index c934be8..244d36b 100644 --- a/frontend/webpack.config.base.js +++ b/frontend/webpack.config.base.js @@ -59,5 +59,8 @@ module.exports = { new webpack.DefinePlugin({ BACKEND_URL: JSON.stringify(process.env.BACKEND_URL || ''), }), + new webpack.ProvidePlugin({ + THREE: 'three', + }) ], } diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index f761ec1..f939321 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -25,7 +25,7 @@ module.exports = { new AngularWebpackPlugin({ tsconfig: 'tsconfig.json', directTemplateLoading: false, - skipCodeGeneration: false, + jitMode: false, }), new HtmlWebpackPlugin({ template: './src/index.html', diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 9fd3234..620244b 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -953,11 +953,6 @@ async-foreach@^0.1.3: resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= -async@~3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8" - integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg== - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2903,13 +2898,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-cache@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - magic-string@^0.25.0: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" @@ -3076,20 +3064,6 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -mustache-express@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/mustache-express/-/mustache-express-1.3.1.tgz#b5144513ab79a503c87aa8cb16fe019d2f61d9f9" - integrity sha512-RSSzrvM+CVAk9217dkWSNYyl6c2JnesNn6zaZ8+FvZSn8aLxY9l4kTnYqIoiE8GxdLyVQL2ak7XlMZS6t/l8YA== - dependencies: - async "~3.2.0" - lru-cache "~5.1.1" - mustache "^4.2.0" - -mustache@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" - integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== - nan@^2.13.2: version "2.14.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" @@ -4572,6 +4546,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +three@^0.119.0: + version "0.119.1" + resolved "https://registry.yarnpkg.com/three/-/three-0.119.1.tgz#9d979a082c4cd9622af8e3498a8dfa026a619332" + integrity sha512-GHyh/RiUfQ5VTiWIVRRTANYoXc1PFB1y+jDVRTb649nif1uX1F06PT1TKU3k2+F/MN4UJ3PWvQB53fY2OqKqKw== + throng@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/throng/-/throng-5.0.0.tgz#f9550c0221e579073f68a00be33a593d094e4d29" @@ -4765,6 +4744,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +vanta@^0.5.21: + version "0.5.21" + resolved "https://registry.yarnpkg.com/vanta/-/vanta-0.5.21.tgz#f3548b531ac0a53ea0b161f12c880474f0d92b29" + integrity sha512-UYW6rYXVl8klFbQrhmgDMZ7SCys9hKROIuTlq5M+v6j346BPu1wqBwVRQPHiYsuooWkUYY6HSXV/9HrJBSuo6g== + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -4976,11 +4960,6 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"