Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions apps/control/src/app/main-view.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { NextMeetingComponent } from './ui/next-meeting.component';
name="splash"
class="absolute inset-0 flex flex-col items-center justify-center text-white"
(click)="powerOn()"
(touchend)="powerOn()"
>
<h2 class="mb-4 text-4xl font-light">
{{ 'APP.CONTROL.TOUCH_TO_START' | translate }}
Expand All @@ -43,7 +42,9 @@ import { NextMeetingComponent } from './ui/next-meeting.component';
<button
class="m-0 border-none bg-none p-0 text-xs underline"
[disabled]="!changelog_available()"
(click)="viewChangelog()"
(click)="
$event.stopPropagation(); viewChangelog()
"
>
{{ version.hash }}
</button>
Expand Down
6 changes: 1 addition & 5 deletions apps/control/src/app/tabbed-view/tab-outlet.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { AsyncHandler, log } from '@placeos/common';
import {
BindingDirective,
IconComponent,
SafePipe,
TranslatePipe,
} from '@placeos/components';
import { map } from 'rxjs/operators';
Expand Down Expand Up @@ -157,9 +156,7 @@ import { TVControlsComponent } from './tv-controls.component';
<div
class="p-8"
content
[innerHTML]="
help().content | markdown | safe
"
[innerHTML]="help().content | markdown"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Help videos disappear

Removing safe also removes video and iframe embeds from help in both this tab and the help modal. The mock help includes a <video> tag. Help authors who use embedded media will lose those instructions. Keep sanitization, but decide which safe media embeds help needs and test that they render.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Known and intended for now: the PR description lists this under "Needs a decision". Help content is admin-editable and the panel holds a long-lived token, so it now goes through Angular's sanitizer. If help pages need embedded <video>, we will add a narrow allowlist in a follow-up rather than bypass sanitizing.

></div>
}
@if (!help()) {
Expand Down Expand Up @@ -243,7 +240,6 @@ import { TVControlsComponent } from './tv-controls.component';
DeviceOutputListComponent,
TranslatePipe,
MarkdownPipe,
SafePipe,
TVControlsComponent,
VideoCallDialViewComponent,
CameraControlsComponent,
Expand Down
5 changes: 3 additions & 2 deletions apps/control/src/app/tabbed-view/tabbed-view.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import { TabOutletComponent } from './tab-outlet.component';
name="splash"
class="absolute inset-0 flex flex-col items-center justify-center text-white"
(click)="powerOn()"
(touchend)="powerOn()"
>
<h2 class="mb-4 text-4xl font-light">
{{ 'APP.CONTROL.TOUCH_TO_START' | translate }}
Expand All @@ -50,7 +49,9 @@ import { TabOutletComponent } from './tab-outlet.component';
<button
class="m-0 border-none bg-none p-0 text-xs underline"
[disabled]="!changelog_available()"
(click)="viewChangelog()"
(click)="
$event.stopPropagation(); viewChangelog()
"
>
{{ version.hash }}
</button>
Expand Down
45 changes: 27 additions & 18 deletions apps/control/src/app/ui/camera-controls.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export enum ZoomDirection {
}
</mat-select>
</mat-form-field>
<div class="p-4">
<div class="relative p-4">
<h3 class="mb-2 text-xl font-medium">
{{ 'APP.CONTROL.CONTROLS' | translate }}
</h3>
Expand All @@ -63,9 +63,11 @@ export enum ZoomDirection {
zoom-in
icon
matRipple
class="rounded-sm"
(mousedown)="startZoom('in', $event)"
(touchstart)="startZoom('in', $event)"
class="touch-none rounded-sm select-none"
(pointerdown)="startZoom('in', $event)"
(pointerup)="stopZoom()"
(pointercancel)="stopZoom()"
(lostpointercapture)="stopZoom()"
(contextmenu)="$event.preventDefault()"
>
<icon>add</icon>
Expand All @@ -79,25 +81,30 @@ export enum ZoomDirection {
zoom-out
icon
matRipple
class="rounded-sm"
(mousedown)="startZoom('out', $event)"
(touchstart)="startZoom('out', $event)"
class="touch-none rounded-sm select-none"
(pointerdown)="startZoom('out', $event)"
(pointerup)="stopZoom()"
(pointercancel)="stopZoom()"
(lostpointercapture)="stopZoom()"
(contextmenu)="$event.preventDefault()"
(window:mouseup)="stopZoom()"
(window:touchend)="stopZoom()"
>
<icon>remove</icon>
</button>
</div>
</div>
@if (!active_camera()) {
<div
no-camera
class="bg-base-100/75 absolute inset-0 flex items-center justify-center"
>
<p>
{{
'APP.CONTROL.CAMERA_SELECT_MSG' | translate
}}
</p>
</div>
}
</div>
@if (!active_camera()) {
<div
class="bg-base-100 bg-opacity-75 absolute inset-0 flex items-center justify-center"
>
<p>{{ 'APP.CONTROL.CAMERA_SELECT_MSG' | translate }}</p>
</div>
}
</div>
}
`,
Expand All @@ -114,7 +121,6 @@ export enum ZoomDirection {
})
export class CameraControlsComponent implements OnInit {
private _state = inject(ControlStateService);
private _destroyRef = inject(DestroyRef);

/** Currently active camera */
public readonly active_camera = signal<RoomInput | undefined>(undefined);
Expand All @@ -141,6 +147,7 @@ export class CameraControlsComponent implements OnInit {
}

constructor() {
inject(DestroyRef).onDestroy(() => this.stopZoom());
effect(() => {
const list = this.camera_list();
const cam = this._selected_camera();
Expand Down Expand Up @@ -205,7 +212,9 @@ export class CameraControlsComponent implements OnInit {
}, 50);
}

public async startZoom(dir: 'in' | 'out', e: MouseEvent | TouchEvent) {
/** Start zooming. Pointer capture makes sure the button receives the release. */
public async startZoom(dir: 'in' | 'out', e: PointerEvent) {
(e.currentTarget as Element | null)?.setPointerCapture?.(e.pointerId);
const cam = this.active_camera();
if (!cam) return;
const mod = getModule(this.id, cam.mod);
Expand Down
43 changes: 20 additions & 23 deletions apps/control/src/app/ui/camera-tooltip.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, effect, inject, signal } from '@angular/core';
import { Component, DestroyRef, effect, inject, signal } from '@angular/core';
import {
BindingDirective,
CustomTooltipData,
Expand Down Expand Up @@ -134,11 +134,7 @@ export enum ZoomDirection {
<h3 class="mb-2 text-xl font-medium">
{{ 'APP.CONTROL.CONTROLS' | translate }}
</h3>
<div
class="flex items-center space-x-2"
(window:mouseup)="stopZoom()"
(window:touchend)="stopZoom()"
>
<div class="flex items-center space-x-2">
<joystick
[(pan)]="pan"
[(tilt)]="tilt"
Expand All @@ -153,11 +149,12 @@ export enum ZoomDirection {
zoom-in
icon
matRipple
class="rounded-sm"
(mousedown)="startZoom('in', $event)"
(touchstart)="startZoom('in', $event)"
class="touch-none rounded-sm select-none"
(pointerdown)="startZoom('in', $event)"
(pointerup)="stopZoom()"
(pointercancel)="stopZoom()"
(lostpointercapture)="stopZoom()"
(contextmenu)="$event.preventDefault()"
(click)="stopZoom()"
>
<icon>add</icon>
</button>
Expand All @@ -170,11 +167,12 @@ export enum ZoomDirection {
zoom-out
icon
matRipple
class="rounded-sm"
(mousedown)="startZoom('out', $event)"
(touchstart)="startZoom('out', $event)"
class="touch-none rounded-sm select-none"
(pointerdown)="startZoom('out', $event)"
(pointerup)="stopZoom()"
(pointercancel)="stopZoom()"
(lostpointercapture)="stopZoom()"
(contextmenu)="$event.preventDefault()"
(click)="stopZoom()"
>
<icon>remove</icon>
</button>
Expand All @@ -183,7 +181,8 @@ export enum ZoomDirection {
</div>
@if (!active_camera()) {
<div
class="bg-base-100 bg-opacity-75 absolute inset-0 flex items-center justify-center"
no-camera
class="bg-base-100/75 absolute inset-0 flex items-center justify-center"
>
<p>
{{
Expand Down Expand Up @@ -267,6 +266,7 @@ export class CameraTooltipComponent {
}

constructor() {
inject(DestroyRef).onDestroy(() => this.stopZoom());
effect(() => {
const l = this.camera_list();
const cam = this._selected_camera();
Expand Down Expand Up @@ -312,7 +312,8 @@ export class CameraTooltipComponent {
this._move_timeout = setTimeout(async () => {
const { index } = camera;
const mod = getModule(this.id, camera.mod);
if (!mod) return;
// Stop first so an axis that returned to Stop does not keep moving
await mod.execute('stop', index ? [index] : []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Camera move throws without module

If the selected camera's module is unavailable, moveCamera() calls mod.execute('stop') without checking mod. The timer throws instead of quietly returning, as it did before this change. Check mod before sending stop.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a bug. getModule() never returns null: in ts-client it is Qr(t).module(e, n), which always builds a module binding (typed PlaceModuleBinding, not nullable). A missing module makes execute reject; it does not throw on mod. The old if (!mod) return was dead code.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You’re right — getModule() returns a PlaceModuleBinding created by system.module(...) and does not return null. The previous if (!mod) return was dead code, so removing it does not introduce the reported null dereference. A missing or unavailable engine module is handled through the promise returned by execute(), not by mod being absent. I’m withdrawing this finding.

if (this.tilt !== JoystickTilt.Stop) {
await mod.execute(
'tilt',
Expand All @@ -325,16 +326,12 @@ export class CameraTooltipComponent {
index ? [this.pan, index] : [this.pan],
);
}
if (
this.tilt === JoystickTilt.Stop &&
this.pan === JoystickPan.Stop
) {
await mod.execute('stop', index ? [index] : []);
}
}, 50);
}

public async startZoom(dir: 'in' | 'out', e: MouseEvent | TouchEvent) {
/** Start zooming. Pointer capture makes sure the button receives the release. */
public async startZoom(dir: 'in' | 'out', e: PointerEvent) {
(e.currentTarget as Element | null)?.setPointerCapture?.(e.pointerId);
const camera = this.active_camera();
if (!camera?.mod) return;
const mod = getModule(this.id, camera.mod);
Expand Down
4 changes: 1 addition & 3 deletions apps/control/src/app/ui/help-modal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { OrganisationService, SettingsService } from '@placeos/common';
import {
AuthenticatedImageDirective,
IconComponent,
SafePipe,
} from '@placeos/components';

import { marked } from 'marked';
Expand Down Expand Up @@ -74,7 +73,7 @@ import { marked } from 'marked';
<div
content
class="bg-base-100 h-1/2 w-full flex-1 overflow-auto p-4 sm:h-full sm:w-1/2 sm:p-8"
[innerHTML]="content() | safe"
[innerHTML]="content()"
></div>
<button
icon
Expand All @@ -97,7 +96,6 @@ import { marked } from 'marked';
],
imports: [
MatDialogModule,
SafePipe,
MatFormFieldModule,
MatSelectModule,
FormsModule,
Expand Down
Loading
Loading