mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-09 05:50:08 +00:00
.
This commit is contained in:
parent
b7bac490d2
commit
c9ead5e93d
@ -4,6 +4,7 @@ appearance:
|
|||||||
dock: 'off'
|
dock: 'off'
|
||||||
dockScreen: 'current'
|
dockScreen: 'current'
|
||||||
dockFill: 50
|
dockFill: 50
|
||||||
|
tabsOnTop: true
|
||||||
hotkeys:
|
hotkeys:
|
||||||
new-tab:
|
new-tab:
|
||||||
- ['Ctrl-A', 'C']
|
- ['Ctrl-A', 'C']
|
||||||
|
@ -8,5 +8,5 @@ html
|
|||||||
window.nodeRequire = require
|
window.nodeRequire = require
|
||||||
script(src='./preload.js')
|
script(src='./preload.js')
|
||||||
script(src='./bundle.js', defer)
|
script(src='./bundle.js', defer)
|
||||||
body(style='background: ; min-height: 100vh')
|
body(style='background: ; min-height: 100vh; overflow: hidden')
|
||||||
app-root
|
app-root
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"electron-config": "0.2.1",
|
"electron-config": "0.2.1",
|
||||||
"electron-debug": "1.0.1",
|
"electron-debug": "1.0.1",
|
||||||
"electron-is-dev": "0.1.2",
|
"electron-is-dev": "0.1.2",
|
||||||
|
"fs-promise": "^2.0.1",
|
||||||
"node-pty": "0.6.3",
|
"node-pty": "0.6.3",
|
||||||
"path": "0.12.7"
|
"path": "0.12.7"
|
||||||
},
|
},
|
||||||
|
@ -16,7 +16,7 @@ export class Tab {
|
|||||||
this.id = Tab.lastTabID++
|
this.id = Tab.lastTabID++
|
||||||
}
|
}
|
||||||
|
|
||||||
displayActivity () {
|
displayActivity (): void {
|
||||||
this.hasActivity = true
|
this.hasActivity = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,4 +27,7 @@ export class Tab {
|
|||||||
getRecoveryToken (): any {
|
getRecoveryToken (): any {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy (): void {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Tab } from './tab'
|
import { Tab } from './tab'
|
||||||
|
|
||||||
export abstract class TabRecoveryProvider {
|
export abstract class TabRecoveryProvider {
|
||||||
abstract recover (recoveryToken: any): Tab
|
abstract async recover (recoveryToken: any): Promise<Tab>
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,15 @@
|
|||||||
@tab-border-radius: 4px;
|
@tab-border-radius: 4px;
|
||||||
|
|
||||||
|
|
||||||
:host > .spacer {
|
.content {
|
||||||
flex: 0 0 5px;
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
background: @title-bg;
|
background: @title-bg;
|
||||||
|
|
||||||
|
&.tabs-on-top {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
title-bar(*ngIf='!config.store.appearance.useNativeFrame && config.store.appearance.dock == "off"')
|
title-bar(*ngIf='!config.full().appearance.useNativeFrame && config.store.appearance.dock == "off"')
|
||||||
|
|
||||||
.spacer
|
.content(
|
||||||
|
[class.tabs-on-top]='config.full().appearance.tabsOnTop'
|
||||||
.tabs(class='active-tab-{{app.tabs.indexOf(app.activeTab)}}')
|
)
|
||||||
|
.tabs(
|
||||||
|
[class.active-tab-0]='app.tabs[0] == app.activeTab',
|
||||||
|
)
|
||||||
button.btn.btn-secondary(
|
button.btn.btn-secondary(
|
||||||
*ngFor='let button of getToolbarButtons(false)',
|
*ngFor='let button of getToolbarButtons(false)',
|
||||||
[title]='button.title',
|
[title]='button.title',
|
||||||
|
@ -80,20 +80,26 @@ export class AppService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreTabs () {
|
async restoreTabs (): Promise<void> {
|
||||||
if (window.localStorage.tabsRecovery) {
|
if (window.localStorage.tabsRecovery) {
|
||||||
JSON.parse(window.localStorage.tabsRecovery).forEach((token) => {
|
for (let token of JSON.parse(window.localStorage.tabsRecovery)) {
|
||||||
|
let tab: Tab
|
||||||
for (let provider of this.tabRecoveryProviders) {
|
for (let provider of this.tabRecoveryProviders) {
|
||||||
try {
|
try {
|
||||||
let tab = provider.recover(token)
|
tab = await provider.recover(token)
|
||||||
|
if (tab) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn('Tab recovery crashed:', token, provider, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (tab) {
|
if (tab) {
|
||||||
this.openTab(tab)
|
this.openTab(tab)
|
||||||
return
|
} else {
|
||||||
}
|
|
||||||
} catch (_) { }
|
|
||||||
}
|
|
||||||
this.logger.warn('Cannot restore tab from the token:', token)
|
this.logger.warn('Cannot restore tab from the token:', token)
|
||||||
})
|
}
|
||||||
|
}
|
||||||
this.saveTabs()
|
this.saveTabs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ export interface IAppearanceData {
|
|||||||
dock: string
|
dock: string
|
||||||
dockScreen: string
|
dockScreen: string
|
||||||
dockFill: number
|
dockFill: number
|
||||||
|
tabsOnTop: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITerminalData {
|
export interface ITerminalData {
|
||||||
|
@ -5,6 +5,29 @@ ngb-tabset(type='tabs')
|
|||||||
template(ngbTabTitle)
|
template(ngbTabTitle)
|
||||||
| Application
|
| Application
|
||||||
template(ngbTabContent)
|
template(ngbTabContent)
|
||||||
|
.row
|
||||||
|
.col.col-sm-6
|
||||||
|
.form-group
|
||||||
|
label Show tabs
|
||||||
|
br
|
||||||
|
div(
|
||||||
|
'[(ngModel)]'='config.store.appearance.tabsOnTop'
|
||||||
|
'(ngModelChange)'='config.save()'
|
||||||
|
ngbRadioGroup
|
||||||
|
)
|
||||||
|
label.btn.btn-secondary
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
[value]='true'
|
||||||
|
)
|
||||||
|
| On top
|
||||||
|
label.btn.btn-secondary
|
||||||
|
input(
|
||||||
|
type='radio',
|
||||||
|
[value]='false'
|
||||||
|
)
|
||||||
|
| At the bottom
|
||||||
|
.col.col-sm-6
|
||||||
.form-group
|
.form-group
|
||||||
label Window frame
|
label Window frame
|
||||||
br
|
br
|
||||||
|
@ -5,7 +5,7 @@ import { SettingsTab } from './tab'
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RecoveryProvider extends TabRecoveryProvider {
|
export class RecoveryProvider extends TabRecoveryProvider {
|
||||||
recover (recoveryToken: any): Tab {
|
async recover (recoveryToken: any): Promise<Tab> {
|
||||||
if (recoveryToken.type == 'app:settings') {
|
if (recoveryToken.type == 'app:settings') {
|
||||||
return new SettingsTab()
|
return new SettingsTab()
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
export abstract class TerminalDecorator {
|
export abstract class TerminalDecorator {
|
||||||
abstract decorate (terminal): void
|
abstract decorate (terminal): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SessionOptions {
|
||||||
|
name?: string,
|
||||||
|
command?: string,
|
||||||
|
args?: string[],
|
||||||
|
cwd?: string,
|
||||||
|
env?: any,
|
||||||
|
recoveryId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class SessionPersistenceProvider {
|
||||||
|
abstract async recoverSession (recoveryId: any): Promise<SessionOptions>
|
||||||
|
abstract async createSession (options: SessionOptions): Promise<SessionOptions>
|
||||||
|
abstract async terminateSession (recoveryId: string): Promise<void>
|
||||||
|
}
|
||||||
|
@ -17,8 +17,8 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
return [{
|
return [{
|
||||||
icon: 'plus',
|
icon: 'plus',
|
||||||
title: 'New terminal',
|
title: 'New terminal',
|
||||||
click: () => {
|
click: async () => {
|
||||||
let session = this.sessions.createNewSession({ command: 'zsh' })
|
let session = await this.sessions.createNewSession({ command: 'zsh' })
|
||||||
this.app.openTab(new TerminalTab(session))
|
this.app.openTab(new TerminalTab(session))
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
@ -6,8 +6,10 @@ import { ToolbarButtonProvider, TabRecoveryProvider } from 'api'
|
|||||||
|
|
||||||
import { TerminalTabComponent } from './components/terminalTab'
|
import { TerminalTabComponent } from './components/terminalTab'
|
||||||
import { SessionsService } from './services/sessions'
|
import { SessionsService } from './services/sessions'
|
||||||
|
import { ScreenPersistenceProvider } from './persistenceProviders'
|
||||||
import { ButtonProvider } from './buttonProvider'
|
import { ButtonProvider } from './buttonProvider'
|
||||||
import { RecoveryProvider } from './recoveryProvider'
|
import { RecoveryProvider } from './recoveryProvider'
|
||||||
|
import { SessionPersistenceProvider } from './api'
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -19,6 +21,7 @@ import { RecoveryProvider } from './recoveryProvider'
|
|||||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
||||||
SessionsService,
|
SessionsService,
|
||||||
|
{ provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider },
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
TerminalTabComponent,
|
TerminalTabComponent,
|
||||||
|
64
app/src/terminal/persistenceProviders.ts
Normal file
64
app/src/terminal/persistenceProviders.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import * as fs from 'fs-promise'
|
||||||
|
const exec = require('child-process-promise').exec
|
||||||
|
|
||||||
|
import { SessionOptions, SessionPersistenceProvider } from './api'
|
||||||
|
|
||||||
|
|
||||||
|
export class NullPersistenceProvider extends SessionPersistenceProvider {
|
||||||
|
async recoverSession (_recoveryId: any): Promise<SessionOptions> {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSession (_options: SessionOptions): Promise<SessionOptions> {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async terminateSession (_recoveryId: string): Promise<void> {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
||||||
|
list(): Promise<any[]> {
|
||||||
|
return exec('screen -list').then((result) => {
|
||||||
|
return result.stdout.split('\n')
|
||||||
|
.filter((line) => /\bterm-tab-/.exec(line))
|
||||||
|
.map((line) => line.trim().split('.')[0])
|
||||||
|
}).catch(() => {
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async recoverSession (recoveryId: any): Promise<SessionOptions> {
|
||||||
|
// TODO check
|
||||||
|
return {
|
||||||
|
recoveryId,
|
||||||
|
command: 'screen',
|
||||||
|
args: ['-r', recoveryId],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSession (options: SessionOptions): Promise<SessionOptions> {
|
||||||
|
let configPath = '/tmp/.termScreenConfig'
|
||||||
|
await fs.writeFile(configPath, `
|
||||||
|
escape ^^^
|
||||||
|
vbell off
|
||||||
|
term xterm-color
|
||||||
|
bindkey "^[OH" beginning-of-line
|
||||||
|
bindkey "^[OF" end-of-line
|
||||||
|
termcapinfo xterm* 'hs:ts=\\E]0;:fs=\\007:ds=\\E]0;\\007'
|
||||||
|
defhstatus "^Et"
|
||||||
|
hardstatus off
|
||||||
|
`, 'utf-8')
|
||||||
|
let recoveryId = `term-tab-${Date.now()}`
|
||||||
|
options.args = ['-c', configPath, '-U', '-S', recoveryId, '--', options.command].concat(options.args || [])
|
||||||
|
options.command = 'screen'
|
||||||
|
options.recoveryId = recoveryId
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
async terminateSession (recoveryId: string): Promise<void> {
|
||||||
|
await exec(`screen -S ${recoveryId} -X quit`)
|
||||||
|
}
|
||||||
|
}
|
@ -10,11 +10,12 @@ export class RecoveryProvider extends TabRecoveryProvider {
|
|||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
recover (recoveryToken: any): Tab {
|
async recover (recoveryToken: any): Promise<Tab> {
|
||||||
if (recoveryToken.type == 'app:terminal') {
|
if (recoveryToken.type == 'app:terminal') {
|
||||||
const options = this.sessions.recoveryProvider.getRecoverySession(recoveryToken.recoveryId)
|
let session = await this.sessions.recover(recoveryToken.recoveryId)
|
||||||
let session = this.sessions.createSession(options)
|
if (!session) {
|
||||||
session.recoveryId = recoveryToken.recoveryId
|
return null
|
||||||
|
}
|
||||||
return new TerminalTab(session)
|
return new TerminalTab(session)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
@ -1,87 +1,9 @@
|
|||||||
import { Injectable, NgZone, EventEmitter } from '@angular/core'
|
|
||||||
import { Logger, LogService } from 'services/log'
|
|
||||||
const exec = require('child-process-promise').exec
|
|
||||||
import * as nodePTY from 'node-pty'
|
import * as nodePTY from 'node-pty'
|
||||||
import * as fs from 'fs'
|
|
||||||
|
|
||||||
|
import { Injectable, EventEmitter } from '@angular/core'
|
||||||
|
import { Logger, LogService } from 'services/log'
|
||||||
|
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||||
|
|
||||||
export interface ISessionRecoveryProvider {
|
|
||||||
list (): Promise<any[]>
|
|
||||||
getRecoverySession (recoveryId: any): SessionOptions
|
|
||||||
wrapNewSession (options: SessionOptions): SessionOptions
|
|
||||||
terminateSession (recoveryId: string): Promise<any>
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NullSessionRecoveryProvider implements ISessionRecoveryProvider {
|
|
||||||
async list (): Promise<any[]> {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
getRecoverySession (_recoveryId: any): SessionOptions {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapNewSession (options: SessionOptions): SessionOptions {
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
|
|
||||||
async terminateSession (_recoveryId: string): Promise<any> {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ScreenSessionRecoveryProvider implements ISessionRecoveryProvider {
|
|
||||||
list(): Promise<any[]> {
|
|
||||||
return exec('screen -list').then((result) => {
|
|
||||||
return result.stdout.split('\n')
|
|
||||||
.filter((line) => /\bterm-tab-/.exec(line))
|
|
||||||
.map((line) => line.trim().split('.')[0])
|
|
||||||
}).catch(() => {
|
|
||||||
return []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
getRecoverySession (recoveryId: any): SessionOptions {
|
|
||||||
return {
|
|
||||||
command: 'screen',
|
|
||||||
args: ['-r', recoveryId],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapNewSession (options: SessionOptions): SessionOptions {
|
|
||||||
// TODO
|
|
||||||
let configPath = '/tmp/.termScreenConfig'
|
|
||||||
fs.writeFileSync(configPath, `
|
|
||||||
escape ^^^
|
|
||||||
vbell off
|
|
||||||
term xterm-color
|
|
||||||
bindkey "^[OH" beginning-of-line
|
|
||||||
bindkey "^[OF" end-of-line
|
|
||||||
termcapinfo xterm* 'hs:ts=\\E]0;:fs=\\007:ds=\\E]0;\\007'
|
|
||||||
defhstatus "^Et"
|
|
||||||
hardstatus off
|
|
||||||
`, 'utf-8')
|
|
||||||
let recoveryId = `term-tab-${Date.now()}`
|
|
||||||
options.args = ['-c', configPath, '-U', '-S', recoveryId, '--', options.command].concat(options.args || [])
|
|
||||||
options.command = 'screen'
|
|
||||||
options.recoveryId = recoveryId
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
|
|
||||||
async terminateSession (recoveryId: string): Promise<any> {
|
|
||||||
return exec(`screen -S ${recoveryId} -X quit`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export interface SessionOptions {
|
|
||||||
name?: string,
|
|
||||||
command?: string,
|
|
||||||
args?: string[],
|
|
||||||
cwd?: string,
|
|
||||||
env?: any,
|
|
||||||
recoveryId?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Session {
|
export class Session {
|
||||||
open: boolean
|
open: boolean
|
||||||
@ -96,6 +18,7 @@ export class Session {
|
|||||||
|
|
||||||
constructor (options: SessionOptions) {
|
constructor (options: SessionOptions) {
|
||||||
this.name = options.name
|
this.name = options.name
|
||||||
|
this.recoveryId = options.recoveryId
|
||||||
console.log('Spawning', options.command)
|
console.log('Spawning', options.command)
|
||||||
|
|
||||||
let env = {
|
let env = {
|
||||||
@ -184,57 +107,44 @@ export class Session {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SessionsService {
|
export class SessionsService {
|
||||||
sessions: {[id: string]: Session} = {}
|
sessions: {[id: string]: Session} = {}
|
||||||
logger: Logger
|
logger: Logger
|
||||||
private lastID = 0
|
private lastID = 0
|
||||||
recoveryProvider: ISessionRecoveryProvider
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private zone: NgZone,
|
private persistence: SessionPersistenceProvider,
|
||||||
log: LogService,
|
log: LogService,
|
||||||
) {
|
) {
|
||||||
this.logger = log.create('sessions')
|
this.logger = log.create('sessions')
|
||||||
this.recoveryProvider = new ScreenSessionRecoveryProvider()
|
|
||||||
//this.recoveryProvider = new NullSessionRecoveryProvider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createNewSession (options: SessionOptions) : Session {
|
async createNewSession (options: SessionOptions) : Promise<Session> {
|
||||||
options = this.recoveryProvider.wrapNewSession(options)
|
options = await this.persistence.createSession(options)
|
||||||
let session = this.createSession(options)
|
let session = this.addSession(options)
|
||||||
session.recoveryId = options.recoveryId
|
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
createSession (options: SessionOptions) : Session {
|
addSession (options: SessionOptions) : Session {
|
||||||
this.lastID++
|
this.lastID++
|
||||||
options.name = `session-${this.lastID}`
|
options.name = `session-${this.lastID}`
|
||||||
let session = new Session(options)
|
let session = new Session(options)
|
||||||
const destroySubscription = session.destroyed.subscribe(() => {
|
const destroySubscription = session.destroyed.subscribe(() => {
|
||||||
delete this.sessions[session.name]
|
delete this.sessions[session.name]
|
||||||
|
this.persistence.terminateSession(session.recoveryId)
|
||||||
destroySubscription.unsubscribe()
|
destroySubscription.unsubscribe()
|
||||||
})
|
})
|
||||||
this.sessions[session.name] = session
|
this.sessions[session.name] = session
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
async destroySession (session: Session): Promise<any> {
|
async recover (recoveryId: string) : Promise<Session> {
|
||||||
await session.gracefullyDestroy()
|
const options = await this.persistence.recoverSession(recoveryId)
|
||||||
await this.recoveryProvider.terminateSession(session.recoveryId)
|
if (!options) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
return this.addSession(options)
|
||||||
recoverAll () : Promise<Session[]> {
|
|
||||||
return <Promise<Session[]>>(this.recoveryProvider.list().then((items) => {
|
|
||||||
return this.zone.run(() => {
|
|
||||||
return items.map((recoveryId) => {
|
|
||||||
const options = this.recoveryProvider.getRecoverySession(recoveryId)
|
|
||||||
let session = this.createSession(options)
|
|
||||||
session.recoveryId = recoveryId
|
|
||||||
return session
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,4 +20,8 @@ export class TerminalTab extends Tab {
|
|||||||
recoveryId: this.session.recoveryId,
|
recoveryId: this.session.recoveryId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy (): void {
|
||||||
|
this.session.gracefullyDestroy()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,13 +78,20 @@ title-bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.tabs tab-header {
|
app-root .content {
|
||||||
|
background: $body-bg2;
|
||||||
|
|
||||||
|
.tabs {
|
||||||
background: $body-bg;
|
background: $body-bg;
|
||||||
|
|
||||||
|
tab-header {
|
||||||
|
background: $body-bg;
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
background: $body-bg2;
|
background: $body-bg2;
|
||||||
|
|
||||||
.index {
|
.index {
|
||||||
color: #444;
|
color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@ -97,6 +104,28 @@ title-bar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: $body-bg2;
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
background: $body-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-activity:not(.active) {
|
||||||
|
.content-wrapper .index {
|
||||||
|
background: $blue;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0 1px 1px rgba(0,0,0,.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.tabs-on-top .tabs {
|
||||||
|
margin-top: 3px;
|
||||||
|
|
||||||
|
tab-header {
|
||||||
&.pre-selected, &:nth-last-child(1) {
|
&.pre-selected, &:nth-last-child(1) {
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
border-bottom-right-radius: $tab-border-radius;
|
border-bottom-right-radius: $tab-border-radius;
|
||||||
@ -109,22 +138,47 @@ title-bar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: $body-bg2;
|
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
|
border-top: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active .content-wrapper {
|
||||||
border-top: 1px solid $blue;
|
border-top: 1px solid $blue;
|
||||||
background: $body-bg;
|
|
||||||
border-top-left-radius: $tab-border-radius;
|
border-top-left-radius: $tab-border-radius;
|
||||||
border-top-right-radius: $tab-border-radius;
|
border-top-right-radius: $tab-border-radius;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.has-activity:not(.active) {
|
&:not(.tabs-on-top) .tabs {
|
||||||
.content-wrapper .index {
|
margin-bottom: 3px;
|
||||||
background: $blue;
|
|
||||||
color: white;
|
tab-header {
|
||||||
text-shadow: 0 1px 1px rgba(0,0,0,.95);
|
&.pre-selected, &:nth-last-child(1) {
|
||||||
|
.content-wrapper {
|
||||||
|
border-top-right-radius: $tab-border-radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.post-selected {
|
||||||
|
.content-wrapper {
|
||||||
|
border-top-left-radius: $tab-border-radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active .content-wrapper {
|
||||||
|
border-bottom: 1px solid $blue;
|
||||||
|
border-bottom-left-radius: $tab-border-radius;
|
||||||
|
border-bottom-right-radius: $tab-border-radius;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tab-body {
|
||||||
|
background: $body-bg;
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "term",
|
"name": "term",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/fs-promise": "^1.0.1",
|
||||||
"apply-loader": "^0.1.0",
|
"apply-loader": "^0.1.0",
|
||||||
"autoprefixer": "^6.7.7",
|
"autoprefixer": "^6.7.7",
|
||||||
"awesome-typescript-loader": "3.0.8",
|
"awesome-typescript-loader": "3.0.8",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user