fix: make control ui chat scroll page
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
### Fixes
|
### Fixes
|
||||||
- Android: tapping the foreground service notification brings the app to the front. (#179) — thanks @Syhids
|
- Android: tapping the foreground service notification brings the app to the front. (#179) — thanks @Syhids
|
||||||
- Cron tool passes `id` to the gateway for update/remove/run/runs (keeps `jobId` input). (#180) — thanks @adamgall
|
- Cron tool passes `id` to the gateway for update/remove/run/runs (keeps `jobId` input). (#180) — thanks @adamgall
|
||||||
|
- Control UI: chat view uses page scroll with sticky header/sidebar and fixed composer (no inner scroll frame).
|
||||||
- macOS: treat location permission as always-only to avoid iOS-only enums. (#165) — thanks @Nachx639
|
- macOS: treat location permission as always-only to avoid iOS-only enums. (#165) — thanks @Nachx639
|
||||||
- macOS: make generated gateway protocol models `Sendable` for Swift 6 strict concurrency. (#195) — thanks @andranik-sahakyan
|
- macOS: make generated gateway protocol models `Sendable` for Swift 6 strict concurrency. (#195) — thanks @andranik-sahakyan
|
||||||
- WhatsApp: suppress typing indicator during heartbeat background tasks. (#190) — thanks @mcinteerj
|
- WhatsApp: suppress typing indicator during heartbeat background tasks. (#190) — thanks @mcinteerj
|
||||||
|
|||||||
@@ -429,6 +429,16 @@
|
|||||||
background: rgba(0, 0, 0, 0.2);
|
background: rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell--chat .chat {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-header {
|
.chat-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -460,8 +470,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
max-height: 60vh;
|
flex: 1;
|
||||||
overflow: auto;
|
max-height: none;
|
||||||
|
overflow: visible;
|
||||||
padding: 14px 12px;
|
padding: 14px 12px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
@@ -731,6 +742,16 @@
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shell--chat .chat-compose {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 5;
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 12px;
|
||||||
|
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, var(--panel) 35%);
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
.chat-compose__field {
|
.chat-compose__field {
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
.shell {
|
.shell {
|
||||||
|
--shell-pad: 18px;
|
||||||
|
--shell-gap: 18px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
|
grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
|
||||||
@@ -6,13 +8,16 @@
|
|||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"topbar topbar"
|
"topbar topbar"
|
||||||
"nav content";
|
"nav content";
|
||||||
gap: 18px;
|
gap: var(--shell-gap);
|
||||||
padding: 18px;
|
padding: var(--shell-pad);
|
||||||
animation: dashboard-enter 0.6s ease-out;
|
animation: dashboard-enter 0.6s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topbar {
|
.topbar {
|
||||||
grid-area: topbar;
|
grid-area: topbar;
|
||||||
|
position: sticky;
|
||||||
|
top: var(--shell-pad);
|
||||||
|
z-index: 20;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -51,6 +56,16 @@
|
|||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
grid-area: nav;
|
grid-area: nav;
|
||||||
|
position: sticky;
|
||||||
|
top: calc(
|
||||||
|
var(--shell-pad) + var(--topbar-height, 0px) + var(--shell-gap)
|
||||||
|
);
|
||||||
|
align-self: start;
|
||||||
|
max-height: calc(
|
||||||
|
100vh - var(--topbar-height, 0px) - var(--shell-gap) -
|
||||||
|
var(--shell-pad) - var(--shell-pad)
|
||||||
|
);
|
||||||
|
overflow: auto;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
@@ -132,6 +147,14 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell--chat .content {
|
||||||
|
min-height: calc(
|
||||||
|
100vh - var(--topbar-height, 0px) - var(--shell-gap) -
|
||||||
|
var(--shell-pad) - var(--shell-pad)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-header {
|
.content-header {
|
||||||
@@ -204,16 +227,19 @@
|
|||||||
|
|
||||||
@media (max-width: 1100px) {
|
@media (max-width: 1100px) {
|
||||||
.shell {
|
.shell {
|
||||||
|
--shell-pad: 12px;
|
||||||
|
--shell-gap: 12px;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-template-rows: auto auto 1fr;
|
grid-template-rows: auto auto 1fr;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"topbar"
|
"topbar"
|
||||||
"nav"
|
"nav"
|
||||||
"content";
|
"content";
|
||||||
padding: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
|
position: static;
|
||||||
|
max-height: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
@@ -234,6 +260,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.topbar {
|
.topbar {
|
||||||
|
position: static;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|||||||
@@ -184,9 +184,10 @@ export function renderApp(state: AppViewState) {
|
|||||||
const sessionsCount = state.sessionsResult?.count ?? null;
|
const sessionsCount = state.sessionsResult?.count ?? null;
|
||||||
const cronNext = state.cronStatus?.nextWakeAtMs ?? null;
|
const cronNext = state.cronStatus?.nextWakeAtMs ?? null;
|
||||||
const chatDisabledReason = state.connected ? null : "Disconnected from gateway.";
|
const chatDisabledReason = state.connected ? null : "Disconnected from gateway.";
|
||||||
|
const isChat = state.tab === "chat";
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="shell">
|
<div class="shell ${isChat ? "shell--chat" : ""}">
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
<div class="brand-title">Clawdbot Control</div>
|
<div class="brand-title">Clawdbot Control</div>
|
||||||
@@ -211,7 +212,7 @@ export function renderApp(state: AppViewState) {
|
|||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
</aside>
|
</aside>
|
||||||
<main class="content">
|
<main class="content ${isChat ? "content--chat" : ""}">
|
||||||
<section class="content-header">
|
<section class="content-header">
|
||||||
<div>
|
<div>
|
||||||
<div class="page-title">${titleForTab(state.tab)}</div>
|
<div class="page-title">${titleForTab(state.tab)}</div>
|
||||||
|
|||||||
@@ -348,6 +348,7 @@ export class ClawdbotApp extends LitElement {
|
|||||||
private popStateHandler = () => this.onPopState();
|
private popStateHandler = () => this.onPopState();
|
||||||
private themeMedia: MediaQueryList | null = null;
|
private themeMedia: MediaQueryList | null = null;
|
||||||
private themeMediaHandler: ((event: MediaQueryListEvent) => void) | null = null;
|
private themeMediaHandler: ((event: MediaQueryListEvent) => void) | null = null;
|
||||||
|
private topbarObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
createRenderRoot() {
|
createRenderRoot() {
|
||||||
return this;
|
return this;
|
||||||
@@ -365,10 +366,16 @@ export class ClawdbotApp extends LitElement {
|
|||||||
this.startNodesPolling();
|
this.startNodesPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected firstUpdated() {
|
||||||
|
this.observeTopbar();
|
||||||
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
window.removeEventListener("popstate", this.popStateHandler);
|
window.removeEventListener("popstate", this.popStateHandler);
|
||||||
this.stopNodesPolling();
|
this.stopNodesPolling();
|
||||||
this.detachThemeListener();
|
this.detachThemeListener();
|
||||||
|
this.topbarObserver?.disconnect();
|
||||||
|
this.topbarObserver = null;
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -437,6 +444,19 @@ export class ClawdbotApp extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private observeTopbar() {
|
||||||
|
if (typeof ResizeObserver === "undefined") return;
|
||||||
|
const topbar = this.querySelector(".topbar");
|
||||||
|
if (!topbar) return;
|
||||||
|
const update = () => {
|
||||||
|
const { height } = topbar.getBoundingClientRect();
|
||||||
|
this.style.setProperty("--topbar-height", `${height}px`);
|
||||||
|
};
|
||||||
|
update();
|
||||||
|
this.topbarObserver = new ResizeObserver(() => update());
|
||||||
|
this.topbarObserver.observe(topbar);
|
||||||
|
}
|
||||||
|
|
||||||
private startNodesPolling() {
|
private startNodesPolling() {
|
||||||
if (this.nodesPollInterval != null) return;
|
if (this.nodesPollInterval != null) return;
|
||||||
this.nodesPollInterval = window.setInterval(
|
this.nodesPollInterval = window.setInterval(
|
||||||
|
|||||||
Reference in New Issue
Block a user