tabby-tabbyspaces 0.0.1 → 0.1.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 +28 -2
- package/CHANGELOG.md +46 -20
- package/CLAUDE.md +163 -15
- package/README.md +71 -61
- package/RELEASE.md +91 -0
- package/TEST_MCP.md +176 -0
- package/TODO.md +72 -0
- package/cdp-click.js +22 -0
- package/cdp-test.js +28 -0
- package/dist/components/paneEditor.component.d.ts +6 -1
- package/dist/components/paneEditor.component.d.ts.map +1 -1
- package/dist/components/splitPreview.component.d.ts +22 -7
- package/dist/components/splitPreview.component.d.ts.map +1 -1
- package/dist/components/workspaceEditor.component.d.ts +30 -4
- package/dist/components/workspaceEditor.component.d.ts.map +1 -1
- package/dist/components/workspaceList.component.d.ts +21 -9
- package/dist/components/workspaceList.component.d.ts.map +1 -1
- 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 +4 -2
- package/dist/models/workspace.model.d.ts.map +1 -1
- package/dist/package.json +26 -0
- package/dist/providers/settings.provider.d.ts.map +1 -1
- package/dist/providers/toolbar.provider.d.ts +4 -1
- package/dist/providers/toolbar.provider.d.ts.map +1 -1
- package/dist/services/startupCommand.service.d.ts +20 -0
- package/dist/services/startupCommand.service.d.ts.map +1 -0
- package/dist/services/workspaceEditor.service.d.ts +11 -3
- package/dist/services/workspaceEditor.service.d.ts.map +1 -1
- package/docs/marketing_status.md +92 -0
- package/package.json +2 -7
- package/screenshots/editor.png +0 -0
- package/screenshots/pane-edit.png +0 -0
- package/scripts/build-prod.js +39 -0
- package/src/components/paneEditor.component.pug +2 -2
- package/src/components/paneEditor.component.ts +19 -1
- package/src/components/splitPreview.component.pug +45 -5
- package/src/components/splitPreview.component.scss +79 -22
- package/src/components/splitPreview.component.ts +91 -16
- package/src/components/workspaceEditor.component.pug +130 -70
- package/src/components/workspaceEditor.component.scss +205 -120
- package/src/components/workspaceEditor.component.ts +193 -6
- package/src/components/workspaceList.component.pug +31 -20
- package/src/components/workspaceList.component.scss +12 -6
- package/src/components/workspaceList.component.ts +116 -34
- package/src/index.ts +2 -0
- package/src/models/workspace.model.ts +33 -6
- package/src/providers/settings.provider.ts +2 -2
- package/src/providers/toolbar.provider.ts +41 -10
- package/src/services/startupCommand.service.ts +142 -0
- package/src/services/workspaceEditor.service.ts +70 -38
- package/test_cdp.py +50 -0
- package/RELEASE_PLAN.md +0 -161
- package/screenshots/workspace-edit.png +0 -0
|
@@ -2,9 +2,9 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'
|
|
|
2
2
|
import {
|
|
3
3
|
WorkspaceSplit,
|
|
4
4
|
WorkspacePane,
|
|
5
|
+
TabbyProfile,
|
|
5
6
|
isWorkspaceSplit,
|
|
6
7
|
} from '../models/workspace.model'
|
|
7
|
-
import { WorkspaceEditorService } from '../services/workspaceEditor.service'
|
|
8
8
|
|
|
9
9
|
@Component({
|
|
10
10
|
selector: 'split-preview',
|
|
@@ -14,16 +14,21 @@ import { WorkspaceEditorService } from '../services/workspaceEditor.service'
|
|
|
14
14
|
export class SplitPreviewComponent {
|
|
15
15
|
@Input() split!: WorkspaceSplit
|
|
16
16
|
@Input() depth = 0
|
|
17
|
-
@
|
|
17
|
+
@Input() selectedPaneId: string | null = null
|
|
18
|
+
@Input() profiles: TabbyProfile[] = []
|
|
19
|
+
@Output() paneSelect = new EventEmitter<WorkspacePane>()
|
|
20
|
+
@Output() paneEdit = new EventEmitter<WorkspacePane>()
|
|
18
21
|
@Output() splitHorizontal = new EventEmitter<WorkspacePane>()
|
|
19
22
|
@Output() splitVertical = new EventEmitter<WorkspacePane>()
|
|
20
23
|
@Output() removePane = new EventEmitter<WorkspacePane>()
|
|
24
|
+
@Output() addLeft = new EventEmitter<WorkspacePane>()
|
|
25
|
+
@Output() addRight = new EventEmitter<WorkspacePane>()
|
|
26
|
+
@Output() addTop = new EventEmitter<WorkspacePane>()
|
|
27
|
+
@Output() addBottom = new EventEmitter<WorkspacePane>()
|
|
21
28
|
|
|
22
29
|
contextMenuPane: WorkspacePane | null = null
|
|
23
30
|
contextMenuPosition = { x: 0, y: 0 }
|
|
24
31
|
|
|
25
|
-
constructor(private workspaceService: WorkspaceEditorService) {}
|
|
26
|
-
|
|
27
32
|
isPane(child: WorkspacePane | WorkspaceSplit): boolean {
|
|
28
33
|
return !isWorkspaceSplit(child)
|
|
29
34
|
}
|
|
@@ -45,7 +50,18 @@ export class SplitPreviewComponent {
|
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
onPaneClick(pane: WorkspacePane): void {
|
|
48
|
-
this.
|
|
53
|
+
this.paneSelect.emit(pane)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onEditClick(event: MouseEvent, pane: WorkspacePane): void {
|
|
57
|
+
event.stopPropagation()
|
|
58
|
+
this.paneEdit.emit(pane)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
truncate(text: string, maxLength: number): string {
|
|
62
|
+
return text.length > maxLength
|
|
63
|
+
? text.substring(0, maxLength) + '...'
|
|
64
|
+
: text
|
|
49
65
|
}
|
|
50
66
|
|
|
51
67
|
onContextMenu(event: MouseEvent, pane: WorkspacePane): void {
|
|
@@ -58,6 +74,13 @@ export class SplitPreviewComponent {
|
|
|
58
74
|
this.contextMenuPane = null
|
|
59
75
|
}
|
|
60
76
|
|
|
77
|
+
onEdit(): void {
|
|
78
|
+
if (this.contextMenuPane) {
|
|
79
|
+
this.paneEdit.emit(this.contextMenuPane)
|
|
80
|
+
this.closeContextMenu()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
61
84
|
onSplitH(): void {
|
|
62
85
|
if (this.contextMenuPane) {
|
|
63
86
|
this.splitHorizontal.emit(this.contextMenuPane)
|
|
@@ -72,29 +95,65 @@ export class SplitPreviewComponent {
|
|
|
72
95
|
}
|
|
73
96
|
}
|
|
74
97
|
|
|
75
|
-
|
|
98
|
+
onAddLeft(): void {
|
|
76
99
|
if (this.contextMenuPane) {
|
|
77
|
-
this.
|
|
100
|
+
this.addLeft.emit(this.contextMenuPane)
|
|
78
101
|
this.closeContextMenu()
|
|
79
102
|
}
|
|
80
103
|
}
|
|
81
104
|
|
|
82
|
-
|
|
83
|
-
|
|
105
|
+
onAddRight(): void {
|
|
106
|
+
if (this.contextMenuPane) {
|
|
107
|
+
this.addRight.emit(this.contextMenuPane)
|
|
108
|
+
this.closeContextMenu()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
onAddTop(): void {
|
|
113
|
+
if (this.contextMenuPane) {
|
|
114
|
+
this.addTop.emit(this.contextMenuPane)
|
|
115
|
+
this.closeContextMenu()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
onAddBottom(): void {
|
|
120
|
+
if (this.contextMenuPane) {
|
|
121
|
+
this.addBottom.emit(this.contextMenuPane)
|
|
122
|
+
this.closeContextMenu()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
onRemove(): void {
|
|
127
|
+
if (this.contextMenuPane) {
|
|
128
|
+
this.removePane.emit(this.contextMenuPane)
|
|
129
|
+
this.closeContextMenu()
|
|
130
|
+
}
|
|
84
131
|
}
|
|
85
132
|
|
|
86
133
|
getPaneLabel(pane: WorkspacePane): string {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
134
|
+
// Base label is always the profile name
|
|
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'
|
|
142
|
+
|
|
143
|
+
// Format: "Title - Profile" or just "Profile"
|
|
144
|
+
if (pane.title) {
|
|
145
|
+
return `${pane.title} - ${profileName}`
|
|
91
146
|
}
|
|
92
|
-
return
|
|
147
|
+
return profileName
|
|
93
148
|
}
|
|
94
149
|
|
|
95
150
|
// Pass-through events from nested splits
|
|
96
|
-
|
|
97
|
-
this.
|
|
151
|
+
onNestedPaneSelect(pane: WorkspacePane): void {
|
|
152
|
+
this.paneSelect.emit(pane)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
onNestedPaneEdit(pane: WorkspacePane): void {
|
|
156
|
+
this.paneEdit.emit(pane)
|
|
98
157
|
}
|
|
99
158
|
|
|
100
159
|
onNestedSplitH(pane: WorkspacePane): void {
|
|
@@ -105,6 +164,22 @@ export class SplitPreviewComponent {
|
|
|
105
164
|
this.splitVertical.emit(pane)
|
|
106
165
|
}
|
|
107
166
|
|
|
167
|
+
onNestedAddLeft(pane: WorkspacePane): void {
|
|
168
|
+
this.addLeft.emit(pane)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
onNestedAddRight(pane: WorkspacePane): void {
|
|
172
|
+
this.addRight.emit(pane)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
onNestedAddTop(pane: WorkspacePane): void {
|
|
176
|
+
this.addTop.emit(pane)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
onNestedAddBottom(pane: WorkspacePane): void {
|
|
180
|
+
this.addBottom.emit(pane)
|
|
181
|
+
}
|
|
182
|
+
|
|
108
183
|
onNestedRemove(pane: WorkspacePane): void {
|
|
109
184
|
this.removePane.emit(pane)
|
|
110
185
|
}
|
|
@@ -1,83 +1,143 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
button.btn.btn-link.close-btn(type='button', (click)='onCancel()')
|
|
6
|
-
i.fas.fa-times
|
|
1
|
+
//- Inline workspace editor
|
|
2
|
+
.workspace-editor-inline
|
|
3
|
+
.editor-body
|
|
4
|
+
ng-container(*ngTemplateOutlet='editorContent')
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
.editor-footer
|
|
7
|
+
label.startup-checkbox
|
|
8
|
+
input(type='checkbox', [(ngModel)]='workspace.launchOnStartup')
|
|
9
|
+
| Launch on startup
|
|
10
|
+
.editor-actions
|
|
11
|
+
button.btn.btn-secondary.btn-sm(type='button', (click)='onCancel()') Cancel
|
|
12
|
+
button.btn.btn-primary.btn-sm(type='button', (click)='onSave()', [disabled]='!workspace.name?.trim() || !hasUnsavedChanges')
|
|
13
|
+
i.fas.fa-save
|
|
14
|
+
| Save
|
|
15
|
+
span.unsaved-indicator(*ngIf='hasUnsavedChanges') *
|
|
16
|
+
|
|
17
|
+
//- Shared editor content
|
|
18
|
+
ng-template(#editorContent)
|
|
19
|
+
//- Top row: Color picker, Name input, Icon grid
|
|
20
|
+
.editor-top-row
|
|
21
|
+
.color-picker
|
|
22
|
+
input.color-input(
|
|
23
|
+
type='color',
|
|
24
|
+
[(ngModel)]='workspace.color',
|
|
25
|
+
title='Workspace color'
|
|
26
|
+
)
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
type='button',
|
|
25
|
-
[class.selected]='workspace.icon === icon',
|
|
26
|
-
(click)='workspace.icon = icon'
|
|
27
|
-
)
|
|
28
|
-
i.fas([class]='"fa-" + icon')
|
|
28
|
+
input.name-input(
|
|
29
|
+
#nameInput,
|
|
30
|
+
type='text',
|
|
31
|
+
[(ngModel)]='workspace.name',
|
|
32
|
+
placeholder='Name your workspace'
|
|
33
|
+
)
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
.icon-picker
|
|
36
|
+
button.icon-trigger(
|
|
37
|
+
type='button',
|
|
38
|
+
[style.color]='workspace.color',
|
|
39
|
+
(click)='toggleIconDropdown()',
|
|
40
|
+
title='Select icon'
|
|
41
|
+
)
|
|
42
|
+
i.fas([class]='"fa-" + workspace.icon')
|
|
36
43
|
|
|
37
|
-
.
|
|
38
|
-
.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
.icon-dropdown(*ngIf='iconDropdownOpen')
|
|
45
|
+
button.icon-option(
|
|
46
|
+
*ngFor='let icon of availableIcons',
|
|
47
|
+
type='button',
|
|
48
|
+
[class.selected]='workspace.icon === icon',
|
|
49
|
+
[style.color]='workspace.icon === icon ? workspace.color : null',
|
|
50
|
+
(click)='selectIcon(icon)'
|
|
51
|
+
)
|
|
52
|
+
i.fas([class]='"fa-" + icon')
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
button.btn.btn-sm(
|
|
48
|
-
type='button',
|
|
49
|
-
[class.btn-primary]='workspace.root.orientation === "horizontal"',
|
|
50
|
-
[class.btn-outline-secondary]='workspace.root.orientation !== "horizontal"',
|
|
51
|
-
(click)='setOrientation("horizontal")'
|
|
52
|
-
)
|
|
53
|
-
i.fas.fa-arrows-alt-h
|
|
54
|
-
| Horizontal
|
|
55
|
-
button.btn.btn-sm(
|
|
56
|
-
type='button',
|
|
57
|
-
[class.btn-primary]='workspace.root.orientation === "vertical"',
|
|
58
|
-
[class.btn-outline-secondary]='workspace.root.orientation !== "vertical"',
|
|
59
|
-
(click)='setOrientation("vertical")'
|
|
60
|
-
)
|
|
61
|
-
i.fas.fa-arrows-alt-v
|
|
62
|
-
| Vertical
|
|
54
|
+
//- Layout section
|
|
55
|
+
.layout-section
|
|
56
|
+
.layout-header
|
|
57
|
+
span.layout-title Layout
|
|
63
58
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
.layout-toolbar
|
|
60
|
+
.toolbar-group.split-group
|
|
61
|
+
span.toolbar-label Split
|
|
62
|
+
button.toolbar-btn(
|
|
63
|
+
type='button',
|
|
64
|
+
[disabled]='!selectedPaneId',
|
|
65
|
+
(click)='splitSelectedPane("horizontal")',
|
|
66
|
+
title='Split Horizontal'
|
|
67
|
+
)
|
|
68
|
+
i.fas.fa-arrows-alt-h
|
|
69
|
+
button.toolbar-btn(
|
|
70
|
+
type='button',
|
|
71
|
+
[disabled]='!selectedPaneId',
|
|
72
|
+
(click)='splitSelectedPane("vertical")',
|
|
73
|
+
title='Split Vertical'
|
|
70
74
|
)
|
|
75
|
+
i.fas.fa-arrows-alt-v
|
|
71
76
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
.toolbar-divider
|
|
78
|
+
|
|
79
|
+
.toolbar-group.add-group
|
|
80
|
+
span.toolbar-label Add
|
|
81
|
+
button.toolbar-btn(
|
|
82
|
+
type='button',
|
|
83
|
+
[disabled]='!selectedPaneId',
|
|
84
|
+
(click)='addPane("left")',
|
|
85
|
+
title='Add Left'
|
|
86
|
+
)
|
|
87
|
+
i.fas.fa-caret-left
|
|
88
|
+
button.toolbar-btn(
|
|
89
|
+
type='button',
|
|
90
|
+
[disabled]='!selectedPaneId',
|
|
91
|
+
(click)='addPane("right")',
|
|
92
|
+
title='Add Right'
|
|
93
|
+
)
|
|
94
|
+
i.fas.fa-caret-right
|
|
95
|
+
button.toolbar-btn(
|
|
96
|
+
type='button',
|
|
97
|
+
[disabled]='!selectedPaneId',
|
|
98
|
+
(click)='addPane("top")',
|
|
99
|
+
title='Add Top'
|
|
100
|
+
)
|
|
101
|
+
i.fas.fa-caret-up
|
|
102
|
+
button.toolbar-btn(
|
|
103
|
+
type='button',
|
|
104
|
+
[disabled]='!selectedPaneId',
|
|
105
|
+
(click)='addPane("bottom")',
|
|
106
|
+
title='Add Bottom'
|
|
107
|
+
)
|
|
108
|
+
i.fas.fa-caret-down
|
|
109
|
+
|
|
110
|
+
.toolbar-spacer
|
|
111
|
+
|
|
112
|
+
.toolbar-group
|
|
113
|
+
button.toolbar-btn.danger(
|
|
114
|
+
type='button',
|
|
115
|
+
[disabled]='!selectedPaneId || !canRemovePane()',
|
|
116
|
+
(click)='removeSelectedPane()',
|
|
117
|
+
title='Remove pane'
|
|
118
|
+
)
|
|
119
|
+
i.fas.fa-trash
|
|
120
|
+
|
|
121
|
+
.layout-preview-container((click)='onPreviewBackgroundClick()')
|
|
122
|
+
split-preview(
|
|
123
|
+
[split]='workspace.root',
|
|
124
|
+
[selectedPaneId]='selectedPaneId',
|
|
125
|
+
[profiles]='profiles',
|
|
126
|
+
(paneSelect)='selectPane($event)',
|
|
127
|
+
(paneEdit)='editPane($event)',
|
|
128
|
+
(splitHorizontal)='splitPane($event, "horizontal")',
|
|
129
|
+
(splitVertical)='splitPane($event, "vertical")',
|
|
130
|
+
(addLeft)='addPaneFromEvent($event, "left")',
|
|
131
|
+
(addRight)='addPaneFromEvent($event, "right")',
|
|
132
|
+
(addTop)='addPaneFromEvent($event, "top")',
|
|
133
|
+
(addBottom)='addPaneFromEvent($event, "bottom")',
|
|
134
|
+
(removePane)='removePane($event)'
|
|
135
|
+
)
|
|
77
136
|
|
|
137
|
+
//- Pane editor (always modal)
|
|
78
138
|
pane-editor(
|
|
79
|
-
*ngIf='showPaneEditor &&
|
|
80
|
-
[pane]='
|
|
139
|
+
*ngIf='showPaneEditor && editingPane',
|
|
140
|
+
[pane]='editingPane',
|
|
81
141
|
[profiles]='profiles',
|
|
82
142
|
(save)='onPaneSave($event)',
|
|
83
143
|
(cancel)='closePaneEditor()'
|