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.
Files changed (61) hide show
  1. package/.claude/settings.local.json +15 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
  3. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
  5. package/CHANGELOG.md +20 -0
  6. package/CLAUDE.md +159 -0
  7. package/CONTRIBUTING.md +64 -0
  8. package/LICENSE +21 -0
  9. package/README.md +61 -0
  10. package/RELEASE_PLAN.md +161 -0
  11. package/dist/build-config.d.ts +4 -0
  12. package/dist/build-config.d.ts.map +1 -0
  13. package/dist/components/paneEditor.component.d.ts +14 -0
  14. package/dist/components/paneEditor.component.d.ts.map +1 -0
  15. package/dist/components/splitPreview.component.d.ts +36 -0
  16. package/dist/components/splitPreview.component.d.ts.map +1 -0
  17. package/dist/components/workspaceEditor.component.d.ts +29 -0
  18. package/dist/components/workspaceEditor.component.d.ts.map +1 -0
  19. package/dist/components/workspaceList.component.d.ts +28 -0
  20. package/dist/components/workspaceList.component.d.ts.map +1 -0
  21. package/dist/index.d.ts +7 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +3 -0
  24. package/dist/index.js.LICENSE.txt +43 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/models/workspace.model.d.ts +77 -0
  27. package/dist/models/workspace.model.d.ts.map +1 -0
  28. package/dist/providers/config.provider.d.ts +9 -0
  29. package/dist/providers/config.provider.d.ts.map +1 -0
  30. package/dist/providers/settings.provider.d.ts +8 -0
  31. package/dist/providers/settings.provider.d.ts.map +1 -0
  32. package/dist/providers/toolbar.provider.d.ts +13 -0
  33. package/dist/providers/toolbar.provider.d.ts.map +1 -0
  34. package/dist/services/workspaceEditor.service.d.ts +25 -0
  35. package/dist/services/workspaceEditor.service.d.ts.map +1 -0
  36. package/package.json +73 -0
  37. package/screenshots/editor.png +0 -0
  38. package/screenshots/pane-edit.png +0 -0
  39. package/screenshots/workspace-edit.png +0 -0
  40. package/scripts/build-dev.js +46 -0
  41. package/src/build-config.ts +8 -0
  42. package/src/components/paneEditor.component.pug +46 -0
  43. package/src/components/paneEditor.component.scss +112 -0
  44. package/src/components/paneEditor.component.ts +33 -0
  45. package/src/components/splitPreview.component.pug +45 -0
  46. package/src/components/splitPreview.component.scss +126 -0
  47. package/src/components/splitPreview.component.ts +111 -0
  48. package/src/components/workspaceEditor.component.pug +84 -0
  49. package/src/components/workspaceEditor.component.scss +169 -0
  50. package/src/components/workspaceEditor.component.ts +181 -0
  51. package/src/components/workspaceList.component.pug +46 -0
  52. package/src/components/workspaceList.component.scss +112 -0
  53. package/src/components/workspaceList.component.ts +124 -0
  54. package/src/index.ts +38 -0
  55. package/src/models/workspace.model.ts +126 -0
  56. package/src/providers/config.provider.ts +12 -0
  57. package/src/providers/settings.provider.ts +15 -0
  58. package/src/providers/toolbar.provider.ts +81 -0
  59. package/src/services/workspaceEditor.service.ts +228 -0
  60. package/tsconfig.json +29 -0
  61. package/webpack.config.js +62 -0
@@ -0,0 +1,9 @@
1
+ import { ConfigProvider } from 'tabby-core';
2
+ export declare class WorkspaceEditorConfigProvider extends ConfigProvider {
3
+ defaults: {
4
+ [x: string]: {
5
+ workspaces: never[];
6
+ };
7
+ };
8
+ }
9
+ //# sourceMappingURL=config.provider.d.ts.map
@@ -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
+ }