Skip to content

Commit 6c18989

Browse files
committed
Keep AI Assistant button visible when panel open, in a new active state
- add isHidden arg to ResizablePanel - add splattributes to ResizablePanel
1 parent f76bbd3 commit 6c18989

15 files changed

+169
-58
lines changed

packages/boxel-ui/addon/src/components/resizable-panel-group/index.gts

+1
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ export default class ResizablePanelGroup extends Component<Signature> {
245245
(panelContext) => panelContext.lengthPx,
246246
);
247247

248+
this.panelRatios = [];
248249
for (let index = 0; index < panelLengths.length; index++) {
249250
let panelLength = panelLengths[index];
250251
if (panelLength == undefined) {

packages/boxel-ui/addon/src/components/resizable-panel-group/panel.gts

+32-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { registerDestructor } from '@ember/destroyable';
2+
import { action } from '@ember/object';
23
import { scheduleOnce } from '@ember/runloop';
34
import { htmlSafe } from '@ember/template';
45
import Component from '@glimmer/component';
56
import { tracked } from '@glimmer/tracking';
7+
import { modifier } from 'ember-modifier';
68
import createRef from 'ember-ref-bucket/modifiers/create-ref';
79

810
import cssVars from '../../helpers/css-var.ts';
@@ -22,6 +24,7 @@ interface Signature {
2224
Args: {
2325
collapsible?: boolean; //default true
2426
defaultLengthFraction: number;
27+
isHidden?: boolean; //default false
2528
isLastPanel: (panelId: number) => boolean;
2629
lengthPx?: number;
2730
minLengthPx?: number;
@@ -43,6 +46,16 @@ interface Signature {
4346
Element: HTMLDivElement;
4447
}
4548

49+
let managePanelRegistration = modifier(
50+
(_element, [panel, isHidden]: [Panel, boolean | undefined]) => {
51+
if (isHidden) {
52+
scheduleOnce('afterRender', panel, panel.unregisterPanel);
53+
} else {
54+
scheduleOnce('afterRender', panel, panel.registerPanel);
55+
}
56+
},
57+
);
58+
4659
export default class Panel extends Component<Signature> {
4760
<template>
4861
<div
@@ -60,6 +73,8 @@ export default class Panel extends Component<Signature> {
6073
)
6174
}}
6275
{{createRef (@resizablePanelElId this.id) bucket=@panelGroupComponent}}
76+
{{managePanelRegistration this @isHidden}}
77+
...attributes
6378
>
6479
{{yield}}
6580
</div>
@@ -90,16 +105,11 @@ export default class Panel extends Component<Signature> {
90105

91106
constructor(owner: any, args: any) {
92107
super(owner, args);
93-
scheduleOnce('afterRender', this, this.registerPanel);
94-
95-
registerDestructor(this, () => {
96-
if (this.id) {
97-
this.args.unregisterPanel(this.id);
98-
}
99-
});
108+
registerDestructor(this, this.unregisterPanel);
100109
}
101110

102-
private registerPanel() {
111+
@action
112+
registerPanel() {
103113
if (this.id == undefined) {
104114
this.id = this.args.registerPanel({
105115
lengthPx: this.args.lengthPx,
@@ -110,6 +120,14 @@ export default class Panel extends Component<Signature> {
110120
}
111121
}
112122

123+
@action
124+
unregisterPanel() {
125+
if (this.id) {
126+
this.args.unregisterPanel(this.id);
127+
this.id = undefined;
128+
}
129+
}
130+
113131
get panelContext() {
114132
if (this.id == undefined) {
115133
return {
@@ -122,7 +140,9 @@ export default class Panel extends Component<Signature> {
122140
}
123141

124142
get minLengthCssValue() {
125-
if (this.panelContext?.minLengthPx !== undefined) {
143+
if (this.args.isHidden) {
144+
return htmlSafe('0px');
145+
} else if (this.panelContext?.minLengthPx !== undefined) {
126146
return htmlSafe(`${this.panelContext.minLengthPx}px`);
127147
} else if (this.args.minLengthPx !== undefined) {
128148
return htmlSafe(`${this.args.minLengthPx}px`);
@@ -133,7 +153,9 @@ export default class Panel extends Component<Signature> {
133153
get lengthCssValue() {
134154
let lengthPx = this.panelContext?.lengthPx;
135155
let defaultLengthFraction = this.panelContext?.defaultLengthFraction;
136-
if (lengthPx === -1 && defaultLengthFraction) {
156+
if (this.args.isHidden) {
157+
return htmlSafe('0px');
158+
} else if (lengthPx === -1 && defaultLengthFraction) {
137159
return htmlSafe(`${defaultLengthFraction * 100}%`);
138160
} else if (lengthPx !== -1 && lengthPx !== undefined) {
139161
return htmlSafe(`${lengthPx}px`);

packages/boxel-ui/addon/src/components/resizable-panel-group/usage.gts

+10-25
Original file line numberDiff line numberDiff line change
@@ -40,40 +40,30 @@ export default class ResizablePanelUsage extends Component {
4040
<:example>
4141
<ResizablePanelGroup
4242
@orientation='horizontal'
43+
style={{cssVar
44+
boxel-panel-resize-handler-height=this.boxelPanelResizeHandleHeight.value
45+
boxel-panel-resize-handler-background-color=this.boxelPanelResizeHandleBackgroundColor.value
46+
boxel-panel-resize-handler-hover-background-color=this.boxelPanelResizeHandleHoverBackgroundColor.value
47+
}}
4348
as |ResizablePanel ResizeHandle|
4449
>
4550
<ResizablePanel
4651
@defaultLengthFraction={{this.horizontalPanel1DefaultWidthFraction}}
4752
@minLengthPx={{this.horizontalPanel1MinWidthPx}}
48-
style={{cssVar
49-
boxel-panel-resize-handler-height=this.boxelPanelResizeHandleHeight.value
50-
boxel-panel-resize-handler-background-color=this.boxelPanelResizeHandleBackgroundColor.value
51-
boxel-panel-resize-handler-hover-background-color=this.boxelPanelResizeHandleHoverBackgroundColor.value
52-
}}
5353
>
5454
Panel 1
5555
</ResizablePanel>
5656
<ResizeHandle />
5757
<ResizablePanel
5858
@defaultLengthFraction={{this.horizontalPanel2DefaultWidthFraction}}
5959
@minLengthPx={{this.horizontalPanel2MinWidthPx}}
60-
style={{cssVar
61-
boxel-panel-resize-handler-height=this.boxelPanelResizeHandleHeight.value
62-
boxel-panel-resize-handler-background-color=this.boxelPanelResizeHandleBackgroundColor.value
63-
boxel-panel-resize-handler-hover-background-color=this.boxelPanelResizeHandleHoverBackgroundColor.value
64-
}}
6560
>
6661
Panel 2
6762
</ResizablePanel>
6863
<ResizeHandle />
6964
<ResizablePanel
7065
@defaultLengthFraction={{this.horizontalPanel3DefaultWidthFraction}}
7166
@minLengthPx={{this.horizontalPanel3MinWidthPx}}
72-
style={{cssVar
73-
boxel-panel-resize-handler-height=this.boxelPanelResizeHandleHeight.value
74-
boxel-panel-resize-handler-background-color=this.boxelPanelResizeHandleBackgroundColor.value
75-
boxel-panel-resize-handler-hover-background-color=this.boxelPanelResizeHandleHoverBackgroundColor.value
76-
}}
7767
>
7868
Panel 3
7969
</ResizablePanel>
@@ -158,28 +148,23 @@ export default class ResizablePanelUsage extends Component {
158148
<ResizablePanelGroup
159149
@orientation='vertical'
160150
@reverseCollapse={{this.verticalReverseCollapse}}
151+
style={{cssVar
152+
boxel-panel-resize-handler-width=this.boxelPanelResizeHandleWidth.value
153+
boxel-panel-resize-handler-background-color=this.boxelPanelResizeHandleBackgroundColor.value
154+
boxel-panel-resize-handler-hover-background-color=this.boxelPanelResizeHandleHoverBackgroundColor.value
155+
}}
161156
as |ResizablePanel ResizeHandle|
162157
>
163158
<ResizablePanel
164159
@defaultLengthFraction={{this.verticalPanel1DefaultHeightFraction}}
165160
@minLengthPx={{this.verticalPanel1MinHeightPx}}
166-
style={{cssVar
167-
boxel-panel-resize-handler-width=this.boxelPanelResizeHandleWidth.value
168-
boxel-panel-resize-handler-background-color=this.boxelPanelResizeHandleBackgroundColor.value
169-
boxel-panel-resize-handler-hover-background-color=this.boxelPanelResizeHandleHoverBackgroundColor.value
170-
}}
171161
>
172162
Panel 1
173163
</ResizablePanel>
174164
<ResizeHandle />
175165
<ResizablePanel
176166
@defaultLengthFraction={{this.verticalPanel2DefaultHeightFraction}}
177167
@minLengthPx={{this.verticalPanel2MinHeightPx}}
178-
style={{cssVar
179-
boxel-panel-resize-handler-width=this.boxelPanelResizeHandleWidth.value
180-
boxel-panel-resize-handler-background-color=this.boxelPanelResizeHandleBackgroundColor.value
181-
boxel-panel-resize-handler-hover-background-color=this.boxelPanelResizeHandleHoverBackgroundColor.value
182-
}}
183168
>
184169
Panel 2
185170
</ResizablePanel>

packages/boxel-ui/test-app/tests/integration/components/resizable-panel-group-test.gts

+54
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class RenderController {
1414
@tracked containerStyle = '';
1515
@tracked panel1LengthPx: number | undefined;
1616
@tracked panel2LengthPx: number | undefined;
17+
@tracked isPanel2Hidden = false;
1718
panel1InnerContentStyle: string | undefined;
1819
}
1920

@@ -56,6 +57,7 @@ module('Integration | ResizablePanelGroup', function (hooks) {
5657
@defaultLengthFraction={{0.4}}
5758
@minLengthPx={{50}}
5859
@lengthPx={{renderController.panel2LengthPx}}
60+
@isHidden={{renderController.isPanel2Hidden}}
5961
>
6062
<div
6163
class='panel-2-content'
@@ -210,4 +212,56 @@ module('Integration | ResizablePanelGroup', function (hooks) {
210212
assert.hasNumericStyle('.panel-1-content', 'height', 360, 1);
211213
assert.hasNumericStyle('.panel-2-content', 'height', 240, 1);
212214
});
215+
216+
test('it excludes hidden panels from participating in layout', async function (this: MyTestContext, assert) {
217+
this.renderController.isPanel2Hidden = true;
218+
this.renderController.containerStyle =
219+
'max-height: 100%; width: 200px; height: 218px;';
220+
let { renderController } = this;
221+
222+
await render(<template>
223+
{{! template-lint-disable no-inline-styles }}
224+
<div id='test-container' style={{renderController.containerStyle}}>
225+
<ResizablePanelGroup
226+
@orientation='vertical'
227+
@reverseCollapse={{true}}
228+
as |ResizablePanel|
229+
>
230+
<ResizablePanel
231+
@defaultLengthFraction={{0.6}}
232+
@lengthPx={{renderController.panel1LengthPx}}
233+
>
234+
<div class='panel-1-content' style='height: 100%; overflow-y:auto'>
235+
<div style={{renderController.panel1InnerContentStyle}}>
236+
Panel 1
237+
</div>
238+
</div>
239+
</ResizablePanel>
240+
<ResizablePanel
241+
@defaultLengthFraction={{0.4}}
242+
@minLengthPx={{50}}
243+
@lengthPx={{renderController.panel2LengthPx}}
244+
@isHidden={{renderController.isPanel2Hidden}}
245+
>
246+
<div class='panel-2-content' style='height: 100%;'>
247+
{{#unless renderController.isPanel2Hidden}}
248+
Panel 2
249+
{{/unless}}
250+
</div>
251+
</ResizablePanel>
252+
</ResizablePanelGroup>
253+
</div>
254+
</template>);
255+
await sleep(100); // let didResizeModifier run
256+
assert.hasNumericStyle('.panel-1-content', 'height', 218, 1);
257+
assert.hasNumericStyle('.panel-2-content', 'height', 0, 0);
258+
this.renderController.isPanel2Hidden = false;
259+
await sleep(100); // let didResizeModifier run
260+
assert.hasNumericStyle('.panel-1-content', 'height', 156, 1);
261+
assert.hasNumericStyle('.panel-2-content', 'height', 62, 1);
262+
this.renderController.isPanel2Hidden = true;
263+
await sleep(100); // let didResizeModifier run
264+
assert.hasNumericStyle('.panel-1-content', 'height', 218, 1);
265+
assert.hasNumericStyle('.panel-2-content', 'height', 0, 0);
266+
});
213267
});
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Loading
Loading

packages/host/app/components/ai-assistant/button.gts

+27-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import Component from '@glimmer/component';
22

3+
import { cn } from '@cardstack/boxel-ui/helpers';
4+
35
interface Signature {
46
Element: HTMLButtonElement;
7+
Args: {
8+
isActive: boolean;
9+
};
510
}
611

712
export default class AiAssistantButton extends Component<Signature> {
813
<template>
914
<button
10-
class='ai-assistant-button'
15+
class={{cn 'ai-assistant-button' is-active=@isActive}}
1116
data-test-open-ai-assistant
1217
...attributes
1318
/>
@@ -20,8 +25,8 @@ export default class AiAssistantButton extends Component<Signature> {
2025
bottom: var(--boxel-sp);
2126
right: var(--boxel-sp);
2227
border-radius: var(--boxel-border-radius);
23-
background-color: var(--boxel-ai-purple);
24-
border: none;
28+
background-color: var(--boxel-dark);
29+
border: 1px solid rgba(255, 255, 255, 0.35);
2530
2631
background-image: image-set(
2732
url('./ai-assist-icon.webp') 1x,
@@ -35,6 +40,25 @@ export default class AiAssistantButton extends Component<Signature> {
3540
.ai-assistant-button:hover {
3641
cursor: pointer;
3742
}
43+
44+
.ai-assistant-button.is-active {
45+
background-image: image-set(
46+
url('./ai-assist-icon-bw.png') 1x,
47+
url('./ai-assist-icon-bw@2x.png') 2x,
48+
url('./ai-assist-icon-bw@3x.png')
49+
),
50+
image-set(
51+
url('./ai-assist-button-active-bg.webp') 1x,
52+
url('./ai-assist-button-active-bg@2x.webp') 2x,
53+
url('./ai-assist-button-active-bg@3x.webp')
54+
);
55+
background-size:
56+
26px 26px,
57+
40px 40px;
58+
background-position: center, center;
59+
background-repeat: no-repeat, no-repeat;
60+
border: 1px solid rgba(0, 0, 0, 0.35);
61+
}
3862
</style>
3963
</template>
4064
}

packages/host/app/components/ai-assistant/panel.gts

+1
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ export default class AiAssistantPanel extends Component<Signature> {
169169
grid-template-rows: auto 1fr;
170170
background-color: var(--boxel-ai-purple);
171171
border: none;
172+
border-radius: 0;
172173
color: var(--boxel-light);
173174
height: 100%;
174175
position: relative;

packages/host/app/components/operator-mode/stack-item.gts

+13-4
Original file line numberDiff line numberDiff line change
@@ -319,10 +319,19 @@ export default class OperatorModeStackItem extends Component<Signature> {
319319
});
320320

321321
private calculateLastSavedMsg() {
322-
this.lastSavedMsg =
323-
this.lastSaved != null
324-
? `Saved ${formatDistanceToNow(this.lastSaved, { addSuffix: true })}`
325-
: undefined;
322+
// runs frequently, so only change a tracked property if the value has changed
323+
if (this.lastSaved == null) {
324+
if (this.lastSavedMsg) {
325+
this.lastSavedMsg = undefined;
326+
}
327+
} else {
328+
let savedMessage = `Saved ${formatDistanceToNow(this.lastSaved, {
329+
addSuffix: true,
330+
})}`;
331+
if (this.lastSavedMsg != savedMessage) {
332+
this.lastSavedMsg = savedMessage;
333+
}
334+
}
326335
}
327336

328337
private doWithStableScroll = restartableTask(

0 commit comments

Comments
 (0)