tabby-tabbyspaces 0.1.0 → 0.2.0
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 +2 -1
- package/.github/workflows/ci.yml +26 -0
- package/.github/workflows/claude-code-review.yml +44 -0
- package/.github/workflows/claude.yml +81 -0
- package/.github/workflows/release.yml +30 -0
- package/CHANGELOG.md +46 -0
- package/CLAUDE.md +33 -0
- package/CONTRIBUTING.md +3 -1
- package/README.md +27 -18
- package/TODO.md +5 -0
- package/dist/build-config.d.ts +3 -3
- package/dist/components/deleteConfirmModal.component.d.ts +7 -0
- package/dist/components/deleteConfirmModal.component.d.ts.map +1 -0
- package/dist/components/paneEditor.component.d.ts +9 -18
- package/dist/components/paneEditor.component.d.ts.map +1 -1
- package/dist/components/splitPreview.component.d.ts +50 -50
- package/dist/components/splitPreview.component.d.ts.map +1 -1
- package/dist/components/workspaceEditor.component.d.ts +61 -54
- package/dist/components/workspaceEditor.component.d.ts.map +1 -1
- package/dist/components/workspaceList.component.d.ts +56 -39
- package/dist/components/workspaceList.component.d.ts.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.LICENSE.txt +1 -1
- package/dist/index.js.map +1 -1
- package/dist/models/workspace.model.d.ts +118 -78
- package/dist/models/workspace.model.d.ts.map +1 -1
- package/dist/package.json +1 -1
- package/dist/providers/config.provider.d.ts +8 -8
- package/dist/providers/settings.provider.d.ts +7 -7
- package/dist/providers/toolbar.provider.d.ts +23 -15
- package/dist/providers/toolbar.provider.d.ts.map +1 -1
- package/dist/services/startupCommand.service.d.ts +27 -19
- package/dist/services/startupCommand.service.d.ts.map +1 -1
- package/dist/services/workspaceBackground.service.d.ts +38 -0
- package/dist/services/workspaceBackground.service.d.ts.map +1 -0
- package/dist/services/workspaceEditor.service.d.ts +46 -32
- package/dist/services/workspaceEditor.service.d.ts.map +1 -1
- package/docs/DESIGN.md +57 -0
- package/docs/SESSION-2026-01-14-S1-DESIGN.md +134 -0
- package/mockups/index.html +162 -0
- package/mockups/s1-tight-sharp.html +522 -0
- package/mockups/shared/base.css +216 -0
- package/mockups/v06-tabbed.html +643 -0
- package/package.json +2 -1
- package/screenshots/editor.png +0 -0
- package/screenshots/pane-edit.png +0 -0
- package/scripts/build-dev.js +2 -1
- package/scripts/build-prod.js +2 -1
- package/src/components/deleteConfirmModal.component.ts +23 -0
- package/src/components/paneEditor.component.pug +27 -43
- package/src/components/paneEditor.component.scss +37 -85
- package/src/components/paneEditor.component.ts +4 -32
- package/src/components/splitPreview.component.pug +0 -9
- package/src/components/splitPreview.component.scss +46 -70
- package/src/components/splitPreview.component.ts +15 -25
- package/src/components/workspaceEditor.component.pug +140 -112
- package/src/components/workspaceEditor.component.scss +270 -202
- package/src/components/workspaceEditor.component.ts +161 -85
- package/src/components/workspaceList.component.pug +31 -51
- package/src/components/workspaceList.component.scss +86 -77
- package/src/components/workspaceList.component.ts +89 -34
- package/src/index.ts +4 -0
- package/src/models/workspace.model.ts +80 -2
- package/src/providers/toolbar.provider.ts +78 -9
- package/src/services/startupCommand.service.ts +30 -32
- package/src/services/workspaceBackground.service.ts +167 -0
- package/src/services/workspaceEditor.service.ts +77 -40
- package/src/styles/_index.scss +3 -0
- package/src/styles/_mixins.scss +180 -0
- package/src/styles/_variables.scss +67 -0
- package/TEST_MCP.md +0 -176
- package/cdp-click.js +0 -22
- package/cdp-test.js +0 -28
- package/test_cdp.py +0 -50
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Component, Input } from '@angular/core'
|
|
2
|
+
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: 'delete-confirm-modal',
|
|
6
|
+
template: `
|
|
7
|
+
<div class="modal-header">
|
|
8
|
+
<h5 class="modal-title">Delete Workspace</h5>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="modal-body">
|
|
11
|
+
<p>Delete workspace "{{ workspaceName }}"?</p>
|
|
12
|
+
<p class="text-muted">This action cannot be undone.</p>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="modal-footer">
|
|
15
|
+
<button class="btn btn-secondary" (click)="modal.dismiss()">Cancel</button>
|
|
16
|
+
<button class="btn btn-danger" (click)="modal.close()" ngbAutofocus>Delete</button>
|
|
17
|
+
</div>
|
|
18
|
+
`,
|
|
19
|
+
})
|
|
20
|
+
export class DeleteConfirmModalComponent {
|
|
21
|
+
@Input() workspaceName = ''
|
|
22
|
+
constructor(public modal: NgbActiveModal) {}
|
|
23
|
+
}
|
|
@@ -1,46 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
//- Inline pane editor panel (no modal overlay)
|
|
2
|
+
.pane-details
|
|
3
|
+
.pane-details-header
|
|
4
|
+
span.pane-details-title
|
|
5
|
+
i.fas.fa-terminal
|
|
6
|
+
| Pane Configuration
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
.pane-form
|
|
9
|
+
.form-group
|
|
10
|
+
label Profile
|
|
11
|
+
select.form-control([(ngModel)]='pane.profileId')
|
|
12
|
+
option(value='') -- Select Profile --
|
|
13
|
+
option(*ngFor='let profile of profiles', [value]='profile.id')
|
|
14
|
+
| {{ profile.name }}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
.form-group
|
|
17
|
+
label Working Directory
|
|
18
|
+
input.form-control(
|
|
19
|
+
type='text',
|
|
20
|
+
[(ngModel)]='pane.cwd',
|
|
21
|
+
placeholder='C:\\path\\to\\project'
|
|
22
|
+
)
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
24
|
+
.form-group
|
|
25
|
+
label Startup Command
|
|
26
|
+
input.form-control(
|
|
27
|
+
type='text',
|
|
28
|
+
[(ngModel)]='pane.startupCommand',
|
|
29
|
+
placeholder='e.g., npm run dev'
|
|
30
|
+
)
|
|
@@ -1,112 +1,64 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
@use '../styles/index' as *;
|
|
2
|
+
|
|
3
|
+
// Inline pane details panel
|
|
4
|
+
.pane-details {
|
|
5
|
+
background: var(--theme-bg-more-more);
|
|
6
|
+
border-radius: $radius-sm;
|
|
7
|
+
padding: $spacing-lg;
|
|
8
|
+
padding-top: $spacing-lg;
|
|
9
|
+
margin-top: $spacing-xl;
|
|
10
|
+
border-left: 2px solid var(--theme-primary);
|
|
11
|
+
border-top: 1px solid var(--theme-border, $fallback-border);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
.pane-
|
|
15
|
-
background: var(--theme-bg);
|
|
16
|
-
border-radius: 12px;
|
|
17
|
-
width: 90%;
|
|
18
|
-
max-width: 500px;
|
|
19
|
-
max-height: 80vh;
|
|
20
|
-
overflow: hidden;
|
|
14
|
+
.pane-details-header {
|
|
21
15
|
display: flex;
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
justify-content: space-between;
|
|
17
|
+
align-items: center;
|
|
18
|
+
margin-bottom: $spacing-lg;
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
.
|
|
21
|
+
.pane-details-title {
|
|
22
|
+
font-size: $font-sm;
|
|
23
|
+
color: var(--theme-fg);
|
|
24
|
+
font-weight: 500;
|
|
27
25
|
display: flex;
|
|
28
|
-
justify-content: space-between;
|
|
29
26
|
align-items: center;
|
|
30
|
-
|
|
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
|
-
}
|
|
27
|
+
gap: $spacing-sm;
|
|
47
28
|
}
|
|
48
29
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
30
|
+
// 2-column grid for pane form
|
|
31
|
+
.pane-form {
|
|
32
|
+
display: grid;
|
|
33
|
+
grid-template-columns: 1fr 1fr;
|
|
34
|
+
gap: $spacing-lg;
|
|
53
35
|
}
|
|
54
36
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
&:last-child {
|
|
59
|
-
margin-bottom: 0;
|
|
37
|
+
@media (max-width: 600px) {
|
|
38
|
+
.pane-form {
|
|
39
|
+
grid-template-columns: 1fr;
|
|
60
40
|
}
|
|
41
|
+
}
|
|
61
42
|
|
|
43
|
+
.form-group {
|
|
62
44
|
label {
|
|
63
|
-
|
|
64
|
-
margin-bottom: 6px;
|
|
65
|
-
font-size: 0.9rem;
|
|
66
|
-
color: var(--theme-fg-more);
|
|
45
|
+
@include form-label;
|
|
67
46
|
}
|
|
68
47
|
}
|
|
69
48
|
|
|
70
49
|
.form-control {
|
|
71
50
|
width: 100%;
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
color: var(--theme-fg);
|
|
77
|
-
font-size: 0.95rem;
|
|
51
|
+
@include form-input(var(--theme-bg));
|
|
52
|
+
font-size: $font-sm;
|
|
53
|
+
// Override Tabby's global border reset
|
|
54
|
+
border: 1px solid var(--theme-border, $fallback-border) !important;
|
|
78
55
|
|
|
79
56
|
&:focus {
|
|
80
|
-
|
|
81
|
-
border-color: var(--theme-primary);
|
|
57
|
+
border-color: var(--theme-primary) !important;
|
|
82
58
|
}
|
|
83
59
|
}
|
|
84
60
|
|
|
85
61
|
select.form-control {
|
|
86
62
|
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);
|
|
63
|
+
appearance: auto; // Show dropdown arrow
|
|
112
64
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component, Input, Output, EventEmitter,
|
|
1
|
+
import { Component, Input, Output, EventEmitter, HostListener } from '@angular/core'
|
|
2
2
|
import { WorkspacePane, TabbyProfile } from '../models/workspace.model'
|
|
3
3
|
|
|
4
4
|
@Component({
|
|
@@ -6,42 +6,14 @@ import { WorkspacePane, TabbyProfile } from '../models/workspace.model'
|
|
|
6
6
|
template: require('./paneEditor.component.pug'),
|
|
7
7
|
styles: [require('./paneEditor.component.scss')],
|
|
8
8
|
})
|
|
9
|
-
export class PaneEditorComponent
|
|
9
|
+
export class PaneEditorComponent {
|
|
10
10
|
@Input() pane!: WorkspacePane
|
|
11
11
|
@Input() profiles: TabbyProfile[] = []
|
|
12
|
-
@Output()
|
|
13
|
-
@Output() cancel = new EventEmitter<void>()
|
|
14
|
-
@ViewChild('modal', { static: true }) modalRef!: ElementRef<HTMLElement>
|
|
15
|
-
|
|
16
|
-
editedPane!: WorkspacePane
|
|
17
|
-
private pointerDownInsideModal = false
|
|
18
|
-
|
|
19
|
-
ngOnInit(): void {
|
|
20
|
-
this.editedPane = { ...this.pane }
|
|
21
|
-
}
|
|
12
|
+
@Output() close = new EventEmitter<void>()
|
|
22
13
|
|
|
23
14
|
@HostListener('document:keydown.escape')
|
|
24
15
|
onEscapeKey(): void {
|
|
25
|
-
this.
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
@HostListener('document:pointerdown', ['$event'])
|
|
29
|
-
onDocumentPointerDown(event: PointerEvent): void {
|
|
30
|
-
this.pointerDownInsideModal = this.modalRef.nativeElement.contains(event.target as Node)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
onOverlayClick(event: MouseEvent): void {
|
|
34
|
-
if (!this.pointerDownInsideModal && event.target === event.currentTarget) {
|
|
35
|
-
this.cancel.emit()
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
onSave(): void {
|
|
40
|
-
this.save.emit(this.editedPane)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
onCancel(): void {
|
|
44
|
-
this.cancel.emit()
|
|
16
|
+
this.close.emit()
|
|
45
17
|
}
|
|
46
18
|
|
|
47
19
|
getProfileName(profileId: string): string {
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
[style.flex-basis]='getFlexStyle(i)',
|
|
7
7
|
[class.selected]='asPane(child).id === selectedPaneId',
|
|
8
8
|
(click)='onPaneClick(asPane(child)); $event.stopPropagation()',
|
|
9
|
-
(dblclick)='onEditClick($event, asPane(child))',
|
|
10
9
|
(contextmenu)='onContextMenu($event, asPane(child))'
|
|
11
10
|
)
|
|
12
11
|
.pane-content
|
|
@@ -20,13 +19,6 @@
|
|
|
20
19
|
i.fas.fa-terminal
|
|
21
20
|
span {{ truncate(asPane(child).startupCommand, 20) }}
|
|
22
21
|
|
|
23
|
-
button.pane-edit-btn(
|
|
24
|
-
type='button',
|
|
25
|
-
(click)='onEditClick($event, asPane(child))',
|
|
26
|
-
title='Edit pane'
|
|
27
|
-
)
|
|
28
|
-
i.fas.fa-pen
|
|
29
|
-
|
|
30
22
|
//- Nested split
|
|
31
23
|
split-preview(
|
|
32
24
|
*ngIf='isSplit(child)',
|
|
@@ -35,7 +27,6 @@
|
|
|
35
27
|
[selectedPaneId]='selectedPaneId',
|
|
36
28
|
[profiles]='profiles',
|
|
37
29
|
[style.flex-basis]='getFlexStyle(i)',
|
|
38
|
-
(paneSelect)='onNestedPaneSelect($event)',
|
|
39
30
|
(paneEdit)='onNestedPaneEdit($event)',
|
|
40
31
|
(splitHorizontal)='onNestedSplitH($event)',
|
|
41
32
|
(splitVertical)='onNestedSplitV($event)',
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
@use '../styles/index' as *;
|
|
2
|
+
|
|
1
3
|
.split-preview {
|
|
2
4
|
display: flex;
|
|
3
5
|
width: 100%;
|
|
4
|
-
height:
|
|
5
|
-
gap:
|
|
6
|
-
border-radius:
|
|
6
|
+
height: $preview-height;
|
|
7
|
+
gap: $spacing-sm;
|
|
8
|
+
border-radius: $radius-md;
|
|
7
9
|
overflow: hidden;
|
|
8
10
|
background: var(--theme-bg);
|
|
9
|
-
border: 1px solid var(--theme-border);
|
|
11
|
+
border: 1px solid var(--theme-border, rgba(255, 255, 255, 0.1));
|
|
10
12
|
|
|
11
13
|
&.horizontal {
|
|
12
14
|
flex-direction: row;
|
|
@@ -19,62 +21,68 @@
|
|
|
19
21
|
&.nested {
|
|
20
22
|
height: auto;
|
|
21
23
|
border: 1px dashed var(--theme-fg-more);
|
|
22
|
-
background:
|
|
23
|
-
padding:
|
|
24
|
-
border-radius:
|
|
24
|
+
background: $nested-split-bg;
|
|
25
|
+
padding: $spacing-sm;
|
|
26
|
+
border-radius: $radius-sm;
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
.preview-pane {
|
|
29
|
-
|
|
30
|
-
align-items: center;
|
|
31
|
-
justify-content: center;
|
|
31
|
+
@include flex-center;
|
|
32
32
|
background: var(--theme-bg-more);
|
|
33
|
-
border-radius:
|
|
34
|
-
border: 2px solid
|
|
33
|
+
border-radius: $radius-sm;
|
|
34
|
+
border: 2px solid var(--theme-border, rgba(255, 255, 255, 0.1));
|
|
35
35
|
cursor: pointer;
|
|
36
|
-
transition: all
|
|
36
|
+
transition: all $transition-fast;
|
|
37
37
|
position: relative;
|
|
38
38
|
min-height: 50px;
|
|
39
39
|
|
|
40
40
|
&:hover {
|
|
41
41
|
background: var(--theme-bg-more-more);
|
|
42
|
+
border-color: var(--theme-fg-more, rgba(255, 255, 255, 0.3));
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
&.selected {
|
|
45
46
|
border-color: var(--theme-primary);
|
|
46
|
-
background:
|
|
47
|
+
background: linear-gradient(
|
|
48
|
+
to bottom,
|
|
49
|
+
$selected-pane-gradient-start,
|
|
50
|
+
$selected-pane-gradient-end
|
|
51
|
+
);
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
.pane-content {
|
|
51
56
|
text-align: center;
|
|
52
|
-
padding:
|
|
57
|
+
padding: $spacing-md;
|
|
53
58
|
color: var(--theme-fg);
|
|
54
59
|
max-width: 100%;
|
|
55
60
|
overflow: hidden;
|
|
56
61
|
}
|
|
57
62
|
|
|
63
|
+
.pane-label {
|
|
64
|
+
font-size: 0.85rem;
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
margin-bottom: $spacing-sm;
|
|
67
|
+
@include text-ellipsis;
|
|
68
|
+
}
|
|
69
|
+
|
|
58
70
|
.pane-title,
|
|
59
71
|
.pane-profile {
|
|
60
72
|
font-size: 0.8rem;
|
|
61
73
|
font-weight: 500;
|
|
62
|
-
margin-bottom:
|
|
63
|
-
|
|
64
|
-
overflow: hidden;
|
|
65
|
-
text-overflow: ellipsis;
|
|
74
|
+
margin-bottom: $spacing-xs;
|
|
75
|
+
@include text-ellipsis;
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
.pane-details {
|
|
69
79
|
font-size: 0.7rem;
|
|
70
80
|
opacity: 0.7;
|
|
71
|
-
margin-top:
|
|
81
|
+
margin-top: $spacing-sm;
|
|
72
82
|
|
|
73
83
|
.pane-detail {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
justify-content: center;
|
|
77
|
-
gap: 4px;
|
|
84
|
+
@include flex-center;
|
|
85
|
+
gap: $spacing-sm;
|
|
78
86
|
|
|
79
87
|
i {
|
|
80
88
|
width: 12px;
|
|
@@ -83,83 +91,51 @@
|
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
span {
|
|
86
|
-
|
|
87
|
-
text-overflow: ellipsis;
|
|
88
|
-
white-space: nowrap;
|
|
94
|
+
@include text-ellipsis;
|
|
89
95
|
max-width: 100px;
|
|
90
96
|
}
|
|
91
97
|
}
|
|
92
98
|
}
|
|
93
99
|
|
|
94
|
-
.pane-edit-btn {
|
|
95
|
-
position: absolute;
|
|
96
|
-
top: 4px;
|
|
97
|
-
right: 4px;
|
|
98
|
-
padding: 4px 6px;
|
|
99
|
-
background: var(--theme-bg);
|
|
100
|
-
border: 1px solid var(--theme-border);
|
|
101
|
-
border-radius: 4px;
|
|
102
|
-
color: var(--theme-fg);
|
|
103
|
-
cursor: pointer;
|
|
104
|
-
font-size: 0.7rem;
|
|
105
|
-
transition: all 0.15s;
|
|
106
|
-
opacity: 0;
|
|
107
|
-
|
|
108
|
-
&:hover {
|
|
109
|
-
background: var(--theme-primary);
|
|
110
|
-
color: white;
|
|
111
|
-
border-color: var(--theme-primary);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.preview-pane:hover .pane-edit-btn {
|
|
116
|
-
opacity: 1;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
100
|
// Context menu
|
|
120
101
|
.context-menu-overlay {
|
|
121
|
-
|
|
122
|
-
top: 0;
|
|
123
|
-
left: 0;
|
|
124
|
-
right: 0;
|
|
125
|
-
bottom: 0;
|
|
126
|
-
z-index: 1200;
|
|
102
|
+
@include full-overlay($z-context-menu-overlay);
|
|
127
103
|
}
|
|
128
104
|
|
|
129
105
|
.context-menu {
|
|
130
106
|
position: fixed;
|
|
131
107
|
background: var(--theme-bg);
|
|
132
|
-
border: 1px solid var(--theme-border);
|
|
133
|
-
border-radius:
|
|
134
|
-
box-shadow:
|
|
108
|
+
border: 1px solid var(--theme-border, $fallback-border);
|
|
109
|
+
border-radius: $radius-lg;
|
|
110
|
+
box-shadow: $shadow-context-menu;
|
|
135
111
|
min-width: 160px;
|
|
136
|
-
padding:
|
|
137
|
-
z-index:
|
|
112
|
+
padding: $spacing-sm;
|
|
113
|
+
z-index: $z-context-menu;
|
|
138
114
|
}
|
|
139
115
|
|
|
140
116
|
.context-menu-item {
|
|
141
117
|
display: flex;
|
|
142
118
|
align-items: center;
|
|
143
|
-
gap:
|
|
119
|
+
gap: $spacing-lg;
|
|
144
120
|
width: 100%;
|
|
145
|
-
padding:
|
|
121
|
+
padding: $spacing-md $spacing-lg;
|
|
146
122
|
border: none;
|
|
147
123
|
background: none;
|
|
148
124
|
color: var(--theme-fg);
|
|
149
125
|
font-size: 0.9rem;
|
|
150
126
|
text-align: left;
|
|
151
127
|
cursor: pointer;
|
|
152
|
-
border-radius:
|
|
128
|
+
border-radius: $radius-sm;
|
|
153
129
|
|
|
154
130
|
&:hover {
|
|
155
131
|
background: var(--theme-bg-more);
|
|
156
132
|
}
|
|
157
133
|
|
|
158
134
|
&.danger {
|
|
159
|
-
color: var(--theme-danger,
|
|
135
|
+
color: var(--theme-danger, $color-danger);
|
|
160
136
|
|
|
161
137
|
&:hover {
|
|
162
|
-
background: rgba(
|
|
138
|
+
background: rgba($color-danger, 0.1);
|
|
163
139
|
}
|
|
164
140
|
}
|
|
165
141
|
|
|
@@ -172,8 +148,8 @@
|
|
|
172
148
|
|
|
173
149
|
.context-menu-divider {
|
|
174
150
|
height: 1px;
|
|
175
|
-
background: var(--theme-border);
|
|
176
|
-
margin:
|
|
151
|
+
background: var(--theme-border, $fallback-border);
|
|
152
|
+
margin: $spacing-sm 0;
|
|
177
153
|
}
|
|
178
154
|
|
|
179
155
|
// Nested splits styling
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Component, Input, Output, EventEmitter } from '@angular/core'
|
|
1
|
+
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges, ChangeDetectorRef } from '@angular/core'
|
|
2
2
|
import {
|
|
3
3
|
WorkspaceSplit,
|
|
4
4
|
WorkspacePane,
|
|
@@ -11,12 +11,11 @@ import {
|
|
|
11
11
|
template: require('./splitPreview.component.pug'),
|
|
12
12
|
styles: [require('./splitPreview.component.scss')],
|
|
13
13
|
})
|
|
14
|
-
export class SplitPreviewComponent {
|
|
14
|
+
export class SplitPreviewComponent implements OnChanges {
|
|
15
15
|
@Input() split!: WorkspaceSplit
|
|
16
16
|
@Input() depth = 0
|
|
17
17
|
@Input() selectedPaneId: string | null = null
|
|
18
18
|
@Input() profiles: TabbyProfile[] = []
|
|
19
|
-
@Output() paneSelect = new EventEmitter<WorkspacePane>()
|
|
20
19
|
@Output() paneEdit = new EventEmitter<WorkspacePane>()
|
|
21
20
|
@Output() splitHorizontal = new EventEmitter<WorkspacePane>()
|
|
22
21
|
@Output() splitVertical = new EventEmitter<WorkspacePane>()
|
|
@@ -29,6 +28,15 @@ export class SplitPreviewComponent {
|
|
|
29
28
|
contextMenuPane: WorkspacePane | null = null
|
|
30
29
|
contextMenuPosition = { x: 0, y: 0 }
|
|
31
30
|
|
|
31
|
+
constructor(private cdr: ChangeDetectorRef) {}
|
|
32
|
+
|
|
33
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
34
|
+
// Clear context menu when split input changes to avoid stale state
|
|
35
|
+
if (changes['split']) {
|
|
36
|
+
this.closeContextMenu()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
isPane(child: WorkspacePane | WorkspaceSplit): boolean {
|
|
33
41
|
return !isWorkspaceSplit(child)
|
|
34
42
|
}
|
|
@@ -50,11 +58,6 @@ export class SplitPreviewComponent {
|
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
onPaneClick(pane: WorkspacePane): void {
|
|
53
|
-
this.paneSelect.emit(pane)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
onEditClick(event: MouseEvent, pane: WorkspacePane): void {
|
|
57
|
-
event.stopPropagation()
|
|
58
61
|
this.paneEdit.emit(pane)
|
|
59
62
|
}
|
|
60
63
|
|
|
@@ -72,6 +75,7 @@ export class SplitPreviewComponent {
|
|
|
72
75
|
|
|
73
76
|
closeContextMenu(): void {
|
|
74
77
|
this.contextMenuPane = null
|
|
78
|
+
this.cdr.detectChanges()
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
onEdit(): void {
|
|
@@ -131,27 +135,13 @@ export class SplitPreviewComponent {
|
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
getPaneLabel(pane: WorkspacePane): string {
|
|
134
|
-
|
|
135
|
-
let profileName = ''
|
|
136
|
-
if (pane.profileId) {
|
|
137
|
-
const profile = this.profiles.find(p => p.id === pane.profileId)
|
|
138
|
-
if (profile?.name) profileName = profile.name
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (!profileName) return 'Select profile'
|
|
138
|
+
if (!pane.profileId) return 'Select profile'
|
|
142
139
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return `${pane.title} - ${profileName}`
|
|
146
|
-
}
|
|
147
|
-
return profileName
|
|
140
|
+
const profile = this.profiles.find(p => p.id === pane.profileId)
|
|
141
|
+
return profile?.name || 'Select profile'
|
|
148
142
|
}
|
|
149
143
|
|
|
150
144
|
// Pass-through events from nested splits
|
|
151
|
-
onNestedPaneSelect(pane: WorkspacePane): void {
|
|
152
|
-
this.paneSelect.emit(pane)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
145
|
onNestedPaneEdit(pane: WorkspacePane): void {
|
|
156
146
|
this.paneEdit.emit(pane)
|
|
157
147
|
}
|