tabby-tabbyspaces 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +15 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
- package/CHANGELOG.md +20 -0
- package/CLAUDE.md +159 -0
- package/CONTRIBUTING.md +64 -0
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/RELEASE_PLAN.md +161 -0
- package/dist/build-config.d.ts +4 -0
- package/dist/build-config.d.ts.map +1 -0
- package/dist/components/paneEditor.component.d.ts +14 -0
- package/dist/components/paneEditor.component.d.ts.map +1 -0
- package/dist/components/splitPreview.component.d.ts +36 -0
- package/dist/components/splitPreview.component.d.ts.map +1 -0
- package/dist/components/workspaceEditor.component.d.ts +29 -0
- package/dist/components/workspaceEditor.component.d.ts.map +1 -0
- package/dist/components/workspaceList.component.d.ts +28 -0
- package/dist/components/workspaceList.component.d.ts.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.LICENSE.txt +43 -0
- package/dist/index.js.map +1 -0
- package/dist/models/workspace.model.d.ts +77 -0
- package/dist/models/workspace.model.d.ts.map +1 -0
- package/dist/providers/config.provider.d.ts +9 -0
- package/dist/providers/config.provider.d.ts.map +1 -0
- package/dist/providers/settings.provider.d.ts +8 -0
- package/dist/providers/settings.provider.d.ts.map +1 -0
- package/dist/providers/toolbar.provider.d.ts +13 -0
- package/dist/providers/toolbar.provider.d.ts.map +1 -0
- package/dist/services/workspaceEditor.service.d.ts +25 -0
- package/dist/services/workspaceEditor.service.d.ts.map +1 -0
- package/package.json +73 -0
- package/screenshots/editor.png +0 -0
- package/screenshots/pane-edit.png +0 -0
- package/screenshots/workspace-edit.png +0 -0
- package/scripts/build-dev.js +46 -0
- package/src/build-config.ts +8 -0
- package/src/components/paneEditor.component.pug +46 -0
- package/src/components/paneEditor.component.scss +112 -0
- package/src/components/paneEditor.component.ts +33 -0
- package/src/components/splitPreview.component.pug +45 -0
- package/src/components/splitPreview.component.scss +126 -0
- package/src/components/splitPreview.component.ts +111 -0
- package/src/components/workspaceEditor.component.pug +84 -0
- package/src/components/workspaceEditor.component.scss +169 -0
- package/src/components/workspaceEditor.component.ts +181 -0
- package/src/components/workspaceList.component.pug +46 -0
- package/src/components/workspaceList.component.scss +112 -0
- package/src/components/workspaceList.component.ts +124 -0
- package/src/index.ts +38 -0
- package/src/models/workspace.model.ts +126 -0
- package/src/providers/config.provider.ts +12 -0
- package/src/providers/settings.provider.ts +15 -0
- package/src/providers/toolbar.provider.ts +81 -0
- package/src/services/workspaceEditor.service.ts +228 -0
- package/tsconfig.json +29 -0
- package/webpack.config.js +62 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.provider.d.ts","sourceRoot":"","sources":["../../src/providers/config.provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAG3C,qBACa,6BAA8B,SAAQ,cAAc;IAC/D,QAAQ;;;;MAIP;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SettingsTabProvider } from 'tabby-settings';
|
|
2
|
+
export declare class WorkspaceEditorSettingsProvider extends SettingsTabProvider {
|
|
3
|
+
id: string;
|
|
4
|
+
icon: string;
|
|
5
|
+
title: string;
|
|
6
|
+
getComponentType(): any;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=settings.provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.provider.d.ts","sourceRoot":"","sources":["../../src/providers/settings.provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAIpD,qBACa,+BAAgC,SAAQ,mBAAmB;IACtE,EAAE,SAAa;IACf,IAAI,SAAY;IAChB,KAAK,SAAe;IAEpB,gBAAgB,IAAI,GAAG;CAGxB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ToolbarButtonProvider, ToolbarButton, ProfilesService, AppService } from 'tabby-core';
|
|
2
|
+
import { WorkspaceEditorService } from '../services/workspaceEditor.service';
|
|
3
|
+
export declare class WorkspaceToolbarProvider extends ToolbarButtonProvider {
|
|
4
|
+
private workspaceService;
|
|
5
|
+
private profilesService;
|
|
6
|
+
private app;
|
|
7
|
+
constructor(workspaceService: WorkspaceEditorService, profilesService: ProfilesService, app: AppService);
|
|
8
|
+
provide(): ToolbarButton[];
|
|
9
|
+
private showWorkspaceSelector;
|
|
10
|
+
private openSettings;
|
|
11
|
+
private openWorkspace;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=toolbar.provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolbar.provider.d.ts","sourceRoot":"","sources":["../../src/providers/toolbar.provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC9F,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAA;AAK5E,qBACa,wBAAyB,SAAQ,qBAAqB;IAE/D,OAAO,CAAC,gBAAgB;IACxB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,GAAG;gBAFH,gBAAgB,EAAE,sBAAsB,EACxC,eAAe,EAAE,eAAe,EAChC,GAAG,EAAE,UAAU;IAKzB,OAAO,IAAI,aAAa,EAAE;YAgBZ,qBAAqB;IAkCnC,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,aAAa;CAStB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ConfigService, NotificationsService, ProfilesService } from 'tabby-core';
|
|
2
|
+
import { Workspace, TabbyProfile, TabbySplitLayoutProfile } from '../models/workspace.model';
|
|
3
|
+
export declare class WorkspaceEditorService {
|
|
4
|
+
private config;
|
|
5
|
+
private notifications;
|
|
6
|
+
private profilesService;
|
|
7
|
+
constructor(config: ConfigService, notifications: NotificationsService, profilesService: ProfilesService);
|
|
8
|
+
getWorkspaces(): Workspace[];
|
|
9
|
+
saveWorkspaces(workspaces: Workspace[]): Promise<boolean>;
|
|
10
|
+
addWorkspace(workspace: Workspace): Promise<void>;
|
|
11
|
+
updateWorkspace(workspace: Workspace): Promise<void>;
|
|
12
|
+
deleteWorkspace(workspaceId: string): Promise<void>;
|
|
13
|
+
getAvailableProfiles(): Promise<TabbyProfile[]>;
|
|
14
|
+
private syncTabbyProfiles;
|
|
15
|
+
generateTabbyProfile(workspace: Workspace): TabbySplitLayoutProfile;
|
|
16
|
+
private generateRecoveryToken;
|
|
17
|
+
private generatePaneToken;
|
|
18
|
+
duplicateWorkspace(workspace: Workspace): Workspace;
|
|
19
|
+
private regenerateIds;
|
|
20
|
+
private sanitizeForProfileId;
|
|
21
|
+
private getProfileById;
|
|
22
|
+
getProfileName(profileId: string): string | undefined;
|
|
23
|
+
private saveConfig;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=workspaceEditor.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspaceEditor.service.d.ts","sourceRoot":"","sources":["../../src/services/workspaceEditor.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjF,OAAO,EACL,SAAS,EAKT,YAAY,EAEZ,uBAAuB,EACxB,MAAM,2BAA2B,CAAA;AAGlC,qBACa,sBAAsB;IAE/B,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,eAAe;gBAFf,MAAM,EAAE,aAAa,EACrB,aAAa,EAAE,oBAAoB,EACnC,eAAe,EAAE,eAAe;IAG1C,aAAa,IAAI,SAAS,EAAE;IAItB,cAAc,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAMzD,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAOjD,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAUpD,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUnD,oBAAoB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAOrD,OAAO,CAAC,iBAAiB;IAiBzB,oBAAoB,CAAC,SAAS,EAAE,SAAS,GAAG,uBAAuB;IAgBnE,OAAO,CAAC,qBAAqB;IAc7B,OAAO,CAAC,iBAAiB;IAmEzB,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS;IASnD,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,cAAc;IAKtB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;YAIvC,UAAU;CAUzB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tabby-tabbyspaces",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Workspaces for Tabby - Visual split-layout workspace editor",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"tabby",
|
|
7
|
+
"tabby-plugin"
|
|
8
|
+
],
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"type": "commonjs",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"tabby-core": "^1.0.197-nightly.1",
|
|
13
|
+
"tabby-settings": "^1.0.197-nightly.1",
|
|
14
|
+
"tabby-terminal": "^1.0.197-nightly.1"
|
|
15
|
+
},
|
|
16
|
+
"typings": "dist/index.d.ts",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "webpack --mode production",
|
|
19
|
+
"build:dev": "node scripts/build-dev.js",
|
|
20
|
+
"watch": "webpack --mode development --watch",
|
|
21
|
+
"watch:dev": "webpack --mode development --watch --env dev --output-path dist-dev"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@angular/common": "^15.0.0",
|
|
25
|
+
"@angular/core": "^15.0.0",
|
|
26
|
+
"@angular/forms": "^15.0.0",
|
|
27
|
+
"tabby-core": "*",
|
|
28
|
+
"tabby-local": "*",
|
|
29
|
+
"tabby-settings": "*",
|
|
30
|
+
"tabby-terminal": "*"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@angular/common": "^15.0.0",
|
|
34
|
+
"@angular/compiler": "^15.0.0",
|
|
35
|
+
"@angular/compiler-cli": "^15.0.0",
|
|
36
|
+
"@angular/core": "^15.0.0",
|
|
37
|
+
"@angular/forms": "^15.0.0",
|
|
38
|
+
"@ngtools/webpack": "^15.0.0",
|
|
39
|
+
"apply-loader": "^2.0.0",
|
|
40
|
+
"css-loader": "^6.8.0",
|
|
41
|
+
"pug": "^3.0.2",
|
|
42
|
+
"pug-loader": "^2.4.0",
|
|
43
|
+
"raw-loader": "^4.0.2",
|
|
44
|
+
"rxjs": "^7.8.0",
|
|
45
|
+
"sass": "^1.63.0",
|
|
46
|
+
"sass-loader": "^13.3.0",
|
|
47
|
+
"tabby-core": "^1.0.197-nightly.1",
|
|
48
|
+
"tabby-local": "^1.0.197-nightly.1",
|
|
49
|
+
"tabby-settings": "^1.0.197-nightly.1",
|
|
50
|
+
"tabby-terminal": "^1.0.197-nightly.1",
|
|
51
|
+
"to-string-loader": "^1.2.0",
|
|
52
|
+
"ts-loader": "^9.5.4",
|
|
53
|
+
"typescript": "~4.9.0",
|
|
54
|
+
"webpack": "^5.88.0",
|
|
55
|
+
"webpack-cli": "^5.1.0",
|
|
56
|
+
"zone.js": "^0.13.0"
|
|
57
|
+
},
|
|
58
|
+
"author": "Igor Halilovic",
|
|
59
|
+
"license": "MIT",
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "git+https://github.com/halilc4/tabbyspaces.git"
|
|
63
|
+
},
|
|
64
|
+
"homepage": "https://github.com/halilc4/tabbyspaces#readme",
|
|
65
|
+
"bugs": {
|
|
66
|
+
"url": "https://github.com/halilc4/tabbyspaces/issues"
|
|
67
|
+
},
|
|
68
|
+
"tabbyPlugin": {
|
|
69
|
+
"name": "tabbyspaces",
|
|
70
|
+
"displayName": "TabbySpaces",
|
|
71
|
+
"description": "Workspaces for Tabby - Visual split-layout workspace editor"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const { execSync } = require('child_process')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
const rootDir = path.resolve(__dirname, '..')
|
|
6
|
+
const distDevDir = path.join(rootDir, 'dist-dev')
|
|
7
|
+
|
|
8
|
+
// 1. Clean dist-dev
|
|
9
|
+
if (fs.existsSync(distDevDir)) {
|
|
10
|
+
fs.rmSync(distDevDir, { recursive: true })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// 2. Run webpack with dev env directly to dist-dev
|
|
14
|
+
console.log('Building dev version...')
|
|
15
|
+
execSync(`npx webpack --mode production --env dev --output-path "${distDevDir}"`, {
|
|
16
|
+
cwd: rootDir,
|
|
17
|
+
stdio: 'inherit'
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// 3. Create dev package.json
|
|
21
|
+
const pkg = require(path.join(rootDir, 'package.json'))
|
|
22
|
+
const devPkg = {
|
|
23
|
+
name: 'tabby-tabbyspaces-dev',
|
|
24
|
+
version: pkg.version,
|
|
25
|
+
description: pkg.description + ' (DEV)',
|
|
26
|
+
main: 'index.js',
|
|
27
|
+
keywords: pkg.keywords,
|
|
28
|
+
peerDependencies: pkg.peerDependencies,
|
|
29
|
+
author: pkg.author,
|
|
30
|
+
license: pkg.license,
|
|
31
|
+
tabbyPlugin: {
|
|
32
|
+
name: 'tabbyspaces-dev',
|
|
33
|
+
displayName: 'TabbySpaces DEV',
|
|
34
|
+
description: pkg.tabbyPlugin.description + ' (DEV)'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fs.writeFileSync(
|
|
39
|
+
path.join(distDevDir, 'package.json'),
|
|
40
|
+
JSON.stringify(devPkg, null, 2)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
console.log('Dev build complete: dist-dev/')
|
|
44
|
+
console.log('')
|
|
45
|
+
console.log('To install:')
|
|
46
|
+
console.log(` cd "$APPDATA/tabby/plugins" && npm install "${distDevDir.replace(/\\/g, '/')}"`)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Build-time constants injected by webpack DefinePlugin
|
|
2
|
+
declare const __CONFIG_KEY__: string
|
|
3
|
+
declare const __DISPLAY_NAME__: string
|
|
4
|
+
declare const __IS_DEV__: boolean
|
|
5
|
+
|
|
6
|
+
export const CONFIG_KEY = typeof __CONFIG_KEY__ !== 'undefined' ? __CONFIG_KEY__ : 'tabbyspaces'
|
|
7
|
+
export const DISPLAY_NAME = typeof __DISPLAY_NAME__ !== 'undefined' ? __DISPLAY_NAME__ : 'TabbySpaces'
|
|
8
|
+
export const IS_DEV = typeof __IS_DEV__ !== 'undefined' ? __IS_DEV__ : false
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
.pane-editor-overlay((click)='onCancel()')
|
|
2
|
+
.pane-editor-modal((click)='$event.stopPropagation()')
|
|
3
|
+
.modal-header
|
|
4
|
+
h4 Edit Pane
|
|
5
|
+
button.btn.btn-link.close-btn(type='button', (click)='onCancel()')
|
|
6
|
+
i.fas.fa-times
|
|
7
|
+
|
|
8
|
+
.modal-body
|
|
9
|
+
.form-group
|
|
10
|
+
label Base Profile
|
|
11
|
+
select.form-control([(ngModel)]='editedPane.profileId')
|
|
12
|
+
option(value='') -- Select Profile --
|
|
13
|
+
option(*ngFor='let profile of profiles', [value]='profile.id')
|
|
14
|
+
| {{ profile.name }}
|
|
15
|
+
|
|
16
|
+
.form-group
|
|
17
|
+
label Working Directory
|
|
18
|
+
input.form-control(
|
|
19
|
+
type='text',
|
|
20
|
+
[(ngModel)]='editedPane.cwd',
|
|
21
|
+
placeholder='C:\\path\\to\\project'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
.form-group
|
|
25
|
+
label Startup Command
|
|
26
|
+
input.form-control(
|
|
27
|
+
type='text',
|
|
28
|
+
[(ngModel)]='editedPane.startupCommand',
|
|
29
|
+
placeholder='e.g., npm run dev'
|
|
30
|
+
)
|
|
31
|
+
small.help-text
|
|
32
|
+
| Command to execute when the pane opens. For Nushell, use commands like: uv run serve
|
|
33
|
+
|
|
34
|
+
.form-group
|
|
35
|
+
label Pane Title (optional)
|
|
36
|
+
input.form-control(
|
|
37
|
+
type='text',
|
|
38
|
+
[(ngModel)]='editedPane.title',
|
|
39
|
+
placeholder='Custom tab title'
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
.modal-footer
|
|
43
|
+
button.btn.btn-secondary(type='button', (click)='onCancel()') Cancel
|
|
44
|
+
button.btn.btn-primary(type='button', (click)='onSave()')
|
|
45
|
+
i.fas.fa-check
|
|
46
|
+
| Apply
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
.pane-editor-overlay {
|
|
2
|
+
position: fixed;
|
|
3
|
+
top: 0;
|
|
4
|
+
left: 0;
|
|
5
|
+
right: 0;
|
|
6
|
+
bottom: 0;
|
|
7
|
+
background: rgba(0, 0, 0, 0.7);
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
z-index: 1100;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.pane-editor-modal {
|
|
15
|
+
background: var(--theme-bg);
|
|
16
|
+
border-radius: 12px;
|
|
17
|
+
width: 90%;
|
|
18
|
+
max-width: 500px;
|
|
19
|
+
max-height: 80vh;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.modal-header {
|
|
27
|
+
display: flex;
|
|
28
|
+
justify-content: space-between;
|
|
29
|
+
align-items: center;
|
|
30
|
+
padding: 16px 20px;
|
|
31
|
+
border-bottom: 1px solid var(--theme-border);
|
|
32
|
+
|
|
33
|
+
h4 {
|
|
34
|
+
margin: 0;
|
|
35
|
+
font-size: 1.15rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.close-btn {
|
|
39
|
+
font-size: 1.15rem;
|
|
40
|
+
color: var(--theme-fg-more);
|
|
41
|
+
padding: 4px 8px;
|
|
42
|
+
|
|
43
|
+
&:hover {
|
|
44
|
+
color: var(--theme-fg);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.modal-body {
|
|
50
|
+
padding: 20px;
|
|
51
|
+
overflow-y: auto;
|
|
52
|
+
flex: 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.form-group {
|
|
56
|
+
margin-bottom: 16px;
|
|
57
|
+
|
|
58
|
+
&:last-child {
|
|
59
|
+
margin-bottom: 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
label {
|
|
63
|
+
display: block;
|
|
64
|
+
margin-bottom: 6px;
|
|
65
|
+
font-size: 0.9rem;
|
|
66
|
+
color: var(--theme-fg-more);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.form-control {
|
|
71
|
+
width: 100%;
|
|
72
|
+
padding: 10px 12px;
|
|
73
|
+
border-radius: 6px;
|
|
74
|
+
border: 1px solid var(--theme-border);
|
|
75
|
+
background: var(--theme-bg-more);
|
|
76
|
+
color: var(--theme-fg);
|
|
77
|
+
font-size: 0.95rem;
|
|
78
|
+
|
|
79
|
+
&:focus {
|
|
80
|
+
outline: none;
|
|
81
|
+
border-color: var(--theme-primary);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
select.form-control {
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.input-with-button {
|
|
90
|
+
display: flex;
|
|
91
|
+
gap: 8px;
|
|
92
|
+
|
|
93
|
+
input {
|
|
94
|
+
flex: 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.help-text {
|
|
99
|
+
display: block;
|
|
100
|
+
margin-top: 4px;
|
|
101
|
+
font-size: 0.8rem;
|
|
102
|
+
color: var(--theme-fg-more);
|
|
103
|
+
opacity: 0.8;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.modal-footer {
|
|
107
|
+
display: flex;
|
|
108
|
+
justify-content: flex-end;
|
|
109
|
+
gap: 12px;
|
|
110
|
+
padding: 16px 20px;
|
|
111
|
+
border-top: 1px solid var(--theme-border);
|
|
112
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'
|
|
2
|
+
import { WorkspacePane, TabbyProfile } from '../models/workspace.model'
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'pane-editor',
|
|
6
|
+
template: require('./paneEditor.component.pug'),
|
|
7
|
+
styles: [require('./paneEditor.component.scss')],
|
|
8
|
+
})
|
|
9
|
+
export class PaneEditorComponent implements OnInit {
|
|
10
|
+
@Input() pane!: WorkspacePane
|
|
11
|
+
@Input() profiles: TabbyProfile[] = []
|
|
12
|
+
@Output() save = new EventEmitter<WorkspacePane>()
|
|
13
|
+
@Output() cancel = new EventEmitter<void>()
|
|
14
|
+
|
|
15
|
+
editedPane!: WorkspacePane
|
|
16
|
+
|
|
17
|
+
ngOnInit(): void {
|
|
18
|
+
this.editedPane = { ...this.pane }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
onSave(): void {
|
|
22
|
+
this.save.emit(this.editedPane)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
onCancel(): void {
|
|
26
|
+
this.cancel.emit()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getProfileName(profileId: string): string {
|
|
30
|
+
const profile = this.profiles.find((p) => p.id === profileId)
|
|
31
|
+
return profile?.name ?? 'Unknown'
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
.split-preview([class.horizontal]='split.orientation === "horizontal"', [class.vertical]='split.orientation === "vertical"')
|
|
2
|
+
ng-container(*ngFor='let child of split.children; let i = index')
|
|
3
|
+
//- Pane
|
|
4
|
+
.preview-pane(
|
|
5
|
+
*ngIf='isPane(child)',
|
|
6
|
+
[style.flex-basis]='getFlexStyle(i)',
|
|
7
|
+
(click)='onPaneClick(asPane(child))',
|
|
8
|
+
(contextmenu)='onContextMenu($event, asPane(child))'
|
|
9
|
+
)
|
|
10
|
+
.pane-content
|
|
11
|
+
.pane-label {{ getPaneLabel(asPane(child)) }}
|
|
12
|
+
.pane-hint Click to edit
|
|
13
|
+
|
|
14
|
+
//- Nested split
|
|
15
|
+
split-preview(
|
|
16
|
+
*ngIf='isSplit(child)',
|
|
17
|
+
[split]='asSplit(child)',
|
|
18
|
+
[depth]='depth + 1',
|
|
19
|
+
[style.flex-basis]='getFlexStyle(i)',
|
|
20
|
+
(paneClick)='onNestedPaneClick($event)',
|
|
21
|
+
(splitHorizontal)='onNestedSplitH($event)',
|
|
22
|
+
(splitVertical)='onNestedSplitV($event)',
|
|
23
|
+
(removePane)='onNestedRemove($event)'
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
//- Context menu
|
|
27
|
+
.context-menu-overlay(
|
|
28
|
+
*ngIf='contextMenuPane',
|
|
29
|
+
(click)='closeContextMenu()'
|
|
30
|
+
)
|
|
31
|
+
.context-menu(
|
|
32
|
+
[style.left.px]='contextMenuPosition.x',
|
|
33
|
+
[style.top.px]='contextMenuPosition.y',
|
|
34
|
+
(click)='$event.stopPropagation()'
|
|
35
|
+
)
|
|
36
|
+
button.context-menu-item(type='button', (click)='onSplitH()')
|
|
37
|
+
i.fas.fa-arrows-alt-h
|
|
38
|
+
| Split Horizontal
|
|
39
|
+
button.context-menu-item(type='button', (click)='onSplitV()')
|
|
40
|
+
i.fas.fa-arrows-alt-v
|
|
41
|
+
| Split Vertical
|
|
42
|
+
.context-menu-divider
|
|
43
|
+
button.context-menu-item.danger(type='button', (click)='onRemove()')
|
|
44
|
+
i.fas.fa-trash
|
|
45
|
+
| Remove Pane
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
.split-preview {
|
|
2
|
+
display: flex;
|
|
3
|
+
width: 100%;
|
|
4
|
+
min-height: 200px;
|
|
5
|
+
gap: 4px;
|
|
6
|
+
border-radius: 8px;
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
background: var(--theme-bg-more);
|
|
9
|
+
border: 1px solid var(--theme-border);
|
|
10
|
+
|
|
11
|
+
&.horizontal {
|
|
12
|
+
flex-direction: row;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
&.vertical {
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.preview-pane {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
background: var(--theme-bg-more-more);
|
|
25
|
+
border-radius: 4px;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
transition: all 0.2s;
|
|
28
|
+
position: relative;
|
|
29
|
+
min-height: 80px;
|
|
30
|
+
|
|
31
|
+
&:hover {
|
|
32
|
+
background: var(--theme-primary);
|
|
33
|
+
|
|
34
|
+
.pane-content {
|
|
35
|
+
color: white;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.pane-hint {
|
|
39
|
+
opacity: 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.pane-content {
|
|
45
|
+
text-align: center;
|
|
46
|
+
padding: 12px;
|
|
47
|
+
color: var(--theme-fg);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.pane-label {
|
|
51
|
+
font-size: 0.9rem;
|
|
52
|
+
font-weight: 500;
|
|
53
|
+
margin-bottom: 4px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.pane-hint {
|
|
57
|
+
font-size: 0.75rem;
|
|
58
|
+
opacity: 0.5;
|
|
59
|
+
transition: opacity 0.2s;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Context menu
|
|
63
|
+
.context-menu-overlay {
|
|
64
|
+
position: fixed;
|
|
65
|
+
top: 0;
|
|
66
|
+
left: 0;
|
|
67
|
+
right: 0;
|
|
68
|
+
bottom: 0;
|
|
69
|
+
z-index: 1200;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.context-menu {
|
|
73
|
+
position: fixed;
|
|
74
|
+
background: var(--theme-bg);
|
|
75
|
+
border: 1px solid var(--theme-border);
|
|
76
|
+
border-radius: 8px;
|
|
77
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
|
78
|
+
min-width: 160px;
|
|
79
|
+
padding: 4px;
|
|
80
|
+
z-index: 1201;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.context-menu-item {
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
gap: 10px;
|
|
87
|
+
width: 100%;
|
|
88
|
+
padding: 8px 12px;
|
|
89
|
+
border: none;
|
|
90
|
+
background: none;
|
|
91
|
+
color: var(--theme-fg);
|
|
92
|
+
font-size: 0.9rem;
|
|
93
|
+
text-align: left;
|
|
94
|
+
cursor: pointer;
|
|
95
|
+
border-radius: 4px;
|
|
96
|
+
|
|
97
|
+
&:hover {
|
|
98
|
+
background: var(--theme-bg-more);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
&.danger {
|
|
102
|
+
color: var(--theme-danger, #ef4444);
|
|
103
|
+
|
|
104
|
+
&:hover {
|
|
105
|
+
background: rgba(239, 68, 68, 0.1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
i {
|
|
110
|
+
width: 16px;
|
|
111
|
+
text-align: center;
|
|
112
|
+
opacity: 0.7;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.context-menu-divider {
|
|
117
|
+
height: 1px;
|
|
118
|
+
background: var(--theme-border);
|
|
119
|
+
margin: 4px 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Nested splits styling
|
|
123
|
+
:host {
|
|
124
|
+
display: flex;
|
|
125
|
+
flex: 1;
|
|
126
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter } from '@angular/core'
|
|
2
|
+
import {
|
|
3
|
+
WorkspaceSplit,
|
|
4
|
+
WorkspacePane,
|
|
5
|
+
isWorkspaceSplit,
|
|
6
|
+
} from '../models/workspace.model'
|
|
7
|
+
import { WorkspaceEditorService } from '../services/workspaceEditor.service'
|
|
8
|
+
|
|
9
|
+
@Component({
|
|
10
|
+
selector: 'split-preview',
|
|
11
|
+
template: require('./splitPreview.component.pug'),
|
|
12
|
+
styles: [require('./splitPreview.component.scss')],
|
|
13
|
+
})
|
|
14
|
+
export class SplitPreviewComponent {
|
|
15
|
+
@Input() split!: WorkspaceSplit
|
|
16
|
+
@Input() depth = 0
|
|
17
|
+
@Output() paneClick = new EventEmitter<WorkspacePane>()
|
|
18
|
+
@Output() splitHorizontal = new EventEmitter<WorkspacePane>()
|
|
19
|
+
@Output() splitVertical = new EventEmitter<WorkspacePane>()
|
|
20
|
+
@Output() removePane = new EventEmitter<WorkspacePane>()
|
|
21
|
+
|
|
22
|
+
contextMenuPane: WorkspacePane | null = null
|
|
23
|
+
contextMenuPosition = { x: 0, y: 0 }
|
|
24
|
+
|
|
25
|
+
constructor(private workspaceService: WorkspaceEditorService) {}
|
|
26
|
+
|
|
27
|
+
isPane(child: WorkspacePane | WorkspaceSplit): boolean {
|
|
28
|
+
return !isWorkspaceSplit(child)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
isSplit(child: WorkspacePane | WorkspaceSplit): boolean {
|
|
32
|
+
return isWorkspaceSplit(child)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
asSplit(child: WorkspacePane | WorkspaceSplit): WorkspaceSplit {
|
|
36
|
+
return child as WorkspaceSplit
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
asPane(child: WorkspacePane | WorkspaceSplit): WorkspacePane {
|
|
40
|
+
return child as WorkspacePane
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getFlexStyle(index: number): string {
|
|
44
|
+
return `${this.split.ratios[index] * 100}%`
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
onPaneClick(pane: WorkspacePane): void {
|
|
48
|
+
this.paneClick.emit(pane)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
onContextMenu(event: MouseEvent, pane: WorkspacePane): void {
|
|
52
|
+
event.preventDefault()
|
|
53
|
+
this.contextMenuPane = pane
|
|
54
|
+
this.contextMenuPosition = { x: event.clientX, y: event.clientY }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
closeContextMenu(): void {
|
|
58
|
+
this.contextMenuPane = null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onSplitH(): void {
|
|
62
|
+
if (this.contextMenuPane) {
|
|
63
|
+
this.splitHorizontal.emit(this.contextMenuPane)
|
|
64
|
+
this.closeContextMenu()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
onSplitV(): void {
|
|
69
|
+
if (this.contextMenuPane) {
|
|
70
|
+
this.splitVertical.emit(this.contextMenuPane)
|
|
71
|
+
this.closeContextMenu()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
onRemove(): void {
|
|
76
|
+
if (this.contextMenuPane) {
|
|
77
|
+
this.removePane.emit(this.contextMenuPane)
|
|
78
|
+
this.closeContextMenu()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getProfileName(profileId: string): string {
|
|
83
|
+
return this.workspaceService.getProfileName(profileId) ?? 'Select profile'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getPaneLabel(pane: WorkspacePane): string {
|
|
87
|
+
if (pane.title) return pane.title
|
|
88
|
+
if (pane.startupCommand) {
|
|
89
|
+
const cmd = pane.startupCommand
|
|
90
|
+
return cmd.length > 20 ? cmd.substring(0, 17) + '...' : cmd
|
|
91
|
+
}
|
|
92
|
+
return this.getProfileName(pane.profileId)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Pass-through events from nested splits
|
|
96
|
+
onNestedPaneClick(pane: WorkspacePane): void {
|
|
97
|
+
this.paneClick.emit(pane)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
onNestedSplitH(pane: WorkspacePane): void {
|
|
101
|
+
this.splitHorizontal.emit(pane)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
onNestedSplitV(pane: WorkspacePane): void {
|
|
105
|
+
this.splitVertical.emit(pane)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
onNestedRemove(pane: WorkspacePane): void {
|
|
109
|
+
this.removePane.emit(pane)
|
|
110
|
+
}
|
|
111
|
+
}
|