This commit is contained in:
Eugene Pankov
2021-06-16 23:49:44 +02:00
parent 15d1fe4a46
commit 0b55657200
17 changed files with 665 additions and 191 deletions

View File

@@ -1,55 +1,2 @@
.sidebar
img.logo(src='{{_logo}}')
div(ngbDropdown, placement='bottom-right')
button.btn.btn-secondary(ngbDropdownToggle)
fa-icon([icon]='_cogIcon', [fixedWidth]='true')
.config-menu(ngbDropdownMenu)
.header(*ngIf='getActiveConfig()')
.dropdown-header Active config
.title {{getActiveConfig().modified_at}}
div(*ngIf='activeVersion')
div App version:
div(ngbDropdown)
button.btn.btn-secondary(ngbDropdownToggle) {{activeVersion.version}}
div(ngbDropdownMenu)
a(
*ngFor='let version of versions',
ngbDropdownItem,
[class.active]='version == activeVersion',
(click)='selectVersion(version)'
) {{version.version}}
.btn-toolbar
button.btn.btn-light.w-50((click)='duplicateConfig()')
fa-icon([icon]='_copyIcon', [fixedWidth]='true')
span Duplicate
button.btn.btn-light.w-50((click)='deleteConfig()')
fa-icon([icon]='_deleteIcon', [fixedWidth]='true')
span Delete
div(*ngIf='configs.length > 1')
.dropdown-header All configs
ng-container(*ngFor='let config of configs')
a(
*ngIf='config !== getActiveConfig()',
ngbDropdownItem,
(click)='selectConfig(config)'
) Config modified at {{config.modified_at}}
button.btn.btn-light.w-100((click)='createNewConfig()')
fa-icon([icon]='_addIcon', [fixedWidth]='true')
span New config
div(ngbDropdown, placement='bottom-right')
button.btn.btn-secondary(ngbDropdownToggle)
fa-icon([icon]='_userIcon', [fixedWidth]='true')
div(ngbDropdownMenu)
a(ngbDropdownItem, (click)='logout()') Logout
.terminal([hidden]='!activeVersion')
iframe(#iframe)
main(*ngIf='ready && user')
login(*ngIf='ready && !user')

View File

@@ -1,41 +0,0 @@
:host {
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
display: flex;
}
.sidebar {
width: 64px;
flex: none;
.logo {
width: 64px;
height: 64px;
}
}
.terminal {
flex: 1 1 0;
overflow: hidden;
position: relative;
}
iframe {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border: none;
}
.config-menu {
.header {
border-bottom: 1px solid black;
}
}

View File

@@ -1,9 +1,5 @@
import * as semverGT from 'semver/functions/gt'
import { Component, ElementRef, ViewChild } from '@angular/core'
import { Component } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { AppConnectorService } from '../services/appConnector.service'
import { faCog, faUser, faCopy, faTrash, faPlus } from '@fortawesome/free-solid-svg-icons'
@Component({
selector: 'app',
@@ -11,95 +7,18 @@ import { faCog, faUser, faCopy, faTrash, faPlus } from '@fortawesome/free-solid-
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
_logo = require('../assets/logo.svg')
_cogIcon = faCog
_userIcon = faUser
_copyIcon = faCopy
_addIcon = faPlus
_deleteIcon = faTrash
configs: any[] = []
versions: any[] = []
activeVersion?: any
@ViewChild('iframe') iframe: ElementRef
user: any
ready = false
constructor (
private appConnector: AppConnectorService,
private http: HttpClient,
) {
window.addEventListener('message', event => {
if (event.data === 'request-connector') {
this.iframe.nativeElement.contentWindow['__connector__'] = this.appConnector
this.iframe.nativeElement.contentWindow.postMessage('connector-ready', '*')
}
})
}
) { }
async ngAfterViewInit () {
this.configs = await this.http.get('/api/1/configs').toPromise()
this.versions = await this.http.get('/api/1/versions').toPromise()
this.versions.sort((a, b) => semverGT(a, b))
if (!this.configs.length) {
await this.createNewConfig()
async ngOnInit () {
const user = await this.http.get('/api/1/user').toPromise()
if (user.id) {
this.user = user
}
this.selectConfig(this.configs[0])
}
async createNewConfig () {
this.configs.push(await this.http.post('/api/1/configs', {
content: '{}',
last_used_with_version: this.versions[0].version,
}).toPromise())
}
async duplicateConfig () {
const copy = {...this.appConnector.config, pk: undefined}
this.configs.push(await this.http.post('/api/1/configs', copy).toPromise())
}
unloadApp () {
delete this.activeVersion
this.iframe.nativeElement.src = 'about:blank'
}
loadApp (version) {
this.iframe.nativeElement.src = `/terminal?${version.version}`
this.activeVersion = version
}
getActiveConfig () {
return this.appConnector.config
}
selectVersion (version: any) {
// TODO check config incompatibility
this.unloadApp()
setTimeout(() => {
this.loadApp(version)
})
}
async selectConfig (config: any) {
let matchingVersion = this.versions.find(x => x.version === config.last_used_with_version)
if (!matchingVersion) {
// TODO ask to upgrade
matchingVersion = this.versions[0]
}
this.appConnector.config = config
const result = await this.http.patch(`/api/1/configs/${config.id}`, {
last_used_with_version: matchingVersion.version,
}).toPromise()
Object.assign(config, result)
this.selectVersion(matchingVersion)
}
async logout () {
await this.http.post('/api/1/auth/logout', null).toPromise()
this.ready = true
}
}

View File

@@ -0,0 +1,57 @@
.sidebar
img.logo(src='{{_logo}}')
div(ngbDropdown, placement='bottom-right')
button.btn(ngbDropdownToggle)
fa-icon([icon]='_cogIcon', [fixedWidth]='true')
.config-menu(ngbDropdownMenu)
.header(*ngIf='getActiveConfig()')
.d-flex.align-items-center.py-2.px-4
.me-auto
label Active config
.title {{getActiveConfig().modified_at}}
button.btn.btn-semi.me-2((click)='duplicateConfig()')
fa-icon([icon]='_copyIcon', [fixedWidth]='true')
button.btn.btn-semi((click)='deleteConfig()')
fa-icon([icon]='_deleteIcon', [fixedWidth]='true')
.d-flex.align-items-center.py-2.px-4(*ngIf='activeVersion')
.me-auto App version:
div(ngbDropdown)
button.btn.btn-semi(ngbDropdownToggle) {{activeVersion.version}}
div(ngbDropdownMenu)
a(
*ngFor='let version of versions',
ngbDropdownItem,
[class.active]='version == activeVersion',
(click)='selectVersion(version)'
) {{version.version}}
div(*ngIf='configs.length > 1')
.dropdown-header All configs
ng-container(*ngFor='let config of configs')
a(
*ngIf='config !== getActiveConfig()',
ngbDropdownItem,
(click)='selectConfig(config)',
href='#'
) Config modified at {{config.modified_at}}
.p-3
button.btn.btn-semi.w-100((click)='createNewConfig()')
fa-icon([icon]='_addIcon', [fixedWidth]='true')
span New config
div(ngbDropdown, placement='bottom-right')
button.btn(ngbDropdownToggle)
fa-icon([icon]='_userIcon', [fixedWidth]='true')
div(ngbDropdownMenu)
a(ngbDropdownItem, (click)='logout()', href='#') Logout
.terminal([hidden]='!activeVersion')
iframe(#iframe)

View File

@@ -0,0 +1,61 @@
@import "../theme/vars";
:host {
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
overflow: hidden;
display: flex;
}
.sidebar {
width: 64px;
flex: none;
display: flex;
flex-direction: column;
align-items: stretch;
.logo {
width: 32px;
height: 32px;
align-self: center;
margin-top: 15px;
margin-bottom: 20px;
}
>button, >[ngbdropdown] > button {
width: 64px;
height: 64px;
background: transparent;
box-shadow: none;
&::after {
display: none;
}
}
}
.terminal {
flex: 1 1 0;
overflow: hidden;
position: relative;
}
iframe {
background: $body-bg;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border: none;
}
.config-menu {
.header {
border-bottom: 1px solid black;
}
}

View File

@@ -0,0 +1,106 @@
import * as semverGT from 'semver/functions/gt'
import { Component, ElementRef, ViewChild } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { AppConnectorService } from '../services/appConnector.service'
import { faCog, faUser, faCopy, faTrash, faPlus } from '@fortawesome/free-solid-svg-icons'
@Component({
selector: 'main',
templateUrl: './main.component.pug',
styleUrls: ['./main.component.scss'],
})
export class MainComponent {
_logo = require('../assets/logo.svg')
_cogIcon = faCog
_userIcon = faUser
_copyIcon = faCopy
_addIcon = faPlus
_deleteIcon = faTrash
configs: any[] = []
versions: any[] = []
activeVersion?: any
@ViewChild('iframe') iframe: ElementRef
constructor (
private appConnector: AppConnectorService,
private http: HttpClient,
) {
window.addEventListener('message', event => {
if (event.data === 'request-connector') {
this.iframe.nativeElement.contentWindow['__connector__'] = this.appConnector
this.iframe.nativeElement.contentWindow.postMessage('connector-ready', '*')
}
})
}
async ngAfterViewInit () {
this.configs = await this.http.get('/api/1/configs').toPromise()
this.versions = await this.http.get('/api/1/versions').toPromise()
this.versions.sort((a, b) => semverGT(a, b))
if (!this.configs.length) {
await this.createNewConfig()
}
this.selectConfig(this.configs[0])
}
async createNewConfig () {
this.configs.push(await this.http.post('/api/1/configs', {
content: '{}',
last_used_with_version: this.versions[0].version,
}).toPromise())
}
async duplicateConfig () {
const copy = {...this.appConnector.config, pk: undefined}
this.configs.push(await this.http.post('/api/1/configs', copy).toPromise())
}
unloadApp () {
delete this.activeVersion
this.iframe.nativeElement.src = 'about:blank'
}
loadApp (version) {
this.iframe.nativeElement.src = `/terminal?${version.version}`
this.activeVersion = version
}
getActiveConfig () {
return this.appConnector.config
}
selectVersion (version: any) {
// TODO check config incompatibility
this.unloadApp()
setTimeout(() => {
this.loadApp(version)
})
}
async selectConfig (config: any) {
let matchingVersion = this.versions.find(x => x.version === config.last_used_with_version)
if (!matchingVersion) {
// TODO ask to upgrade
matchingVersion = this.versions[0]
}
this.appConnector.config = config
const result = await this.http.patch(`/api/1/configs/${config.id}`, {
last_used_with_version: matchingVersion.version,
}).toPromise()
Object.assign(config, result)
this.selectVersion(matchingVersion)
}
async logout () {
await this.http.post('/api/1/auth/logout', null).toPromise()
location.href = '/'
}
}