Compare commits

..

1 Commits

Author SHA1 Message Date
Matt Hill
9eb26db050 ensure correct locale on 035 update 2026-03-26 21:13:10 -06:00
10 changed files with 15 additions and 269 deletions

View File

@@ -494,7 +494,7 @@ export class SystemForEmbassy implements System {
const host = new MultiHost({ effects, id })
const internalPorts = new Set(
Object.values(interfaceValue["tor-config"]?.["port-mapping"] ?? {})
.map((v) => parseInt(v))
.map(Number.parseInt)
.concat(
...Object.values(interfaceValue["lan-config"] ?? {}).map(
(c) => c.internal,

View File

@@ -344,17 +344,12 @@ pub async fn mount_fs<P: AsRef<Path>>(
.arg(&blockdev_path)
.invoke(ErrorKind::DiskManagement)
.await?;
// Delete ext2_saved subvolume and defragment after conversion
// Defragment after conversion for optimal performance
let tmp_mount = datadir.as_ref().join(format!("{name}.convert-tmp"));
tokio::fs::create_dir_all(&tmp_mount).await?;
BlockDev::new(&blockdev_path)
.mount(&tmp_mount, ReadWrite)
.await?;
Command::new("btrfs")
.args(["subvolume", "delete"])
.arg(tmp_mount.join("ext2_saved"))
.invoke(ErrorKind::DiskManagement)
.await?;
Command::new("btrfs")
.args(["filesystem", "defragment", "-r"])
.arg(&tmp_mount)

View File

@@ -134,7 +134,8 @@ pub async fn list_service_interfaces(
.expect("valid json pointer");
let mut watch = context.seed.ctx.db.watch(ptr).await;
let res = from_value(watch.peek_and_mark_seen()?)?;
let res = imbl_value::from_value(watch.peek_and_mark_seen()?)
.unwrap_or_default();
if let Some(callback) = callback {
let callback = callback.register(&context.seed.persistent_container);
@@ -173,7 +174,9 @@ pub async fn clear_service_interfaces(
.as_idx_mut(&package_id)
.or_not_found(&package_id)?
.as_service_interfaces_mut()
.mutate(|s| Ok(s.retain(|id, _| except.contains(id))))
.mutate(|s| {
Ok(s.retain(|id, _| except.contains(id)))
})
})
.await
.result?;

View File

@@ -1,10 +1,7 @@
/**
* Performs a deep structural equality check across all provided arguments.
* Returns true only if every argument is deeply equal to every other argument.
* Handles primitives, arrays, and plain objects (JSON-like) recursively.
*
* Non-plain objects (Set, Map, Date, etc.) are compared by reference only,
* since Object.keys() does not enumerate their contents.
* Handles primitives, arrays, and plain objects recursively.
*
* @param args - Two or more values to compare for deep equality
* @returns True if all arguments are deeply equal
@@ -26,18 +23,6 @@ export function deepEqual(...args: unknown[]) {
}
if (objects.length !== args.length) return false
if (objects.some(Array.isArray) && !objects.every(Array.isArray)) return false
if (
objects.some(
(x) => !Array.isArray(x) && Object.getPrototypeOf(x) !== Object.prototype,
)
) {
return (
objects.reduce<object | null>(
(a, b) => (a === b ? a : null),
objects[0],
) !== null
)
}
const allKeys = new Set(objects.flatMap((x) => Object.keys(x)))
for (const key of allKeys) {
for (const x of objects) {

View File

@@ -37,7 +37,7 @@ import {
VERSION,
WorkspaceConfig,
} from '@start9labs/shared'
import { TUI_WINDOW_SIZE, tuiObfuscateOptionsProvider } from '@taiga-ui/cdk'
import { tuiObfuscateOptionsProvider } from '@taiga-ui/cdk'
import {
provideTaiga,
TUI_DIALOGS_CLOSE,
@@ -67,12 +67,11 @@ import {
PATCH_CACHE,
PatchDbSource,
} from 'src/app/services/patch-db/patch-db-source'
import { PluginsService } from 'src/app/services/plugins.service'
import { StateService } from 'src/app/services/state.service'
import { StorageService } from 'src/app/services/storage.service'
import {
DatetimeTransformer,
DateTransformer,
DatetimeTransformer,
} from 'src/app/utils/value-transformers'
import { environment } from 'src/environments/environment'
@@ -186,9 +185,5 @@ export const APP_CONFIG: ApplicationConfig = {
desktopLarge: Infinity,
},
},
{
provide: TUI_WINDOW_SIZE,
useExisting: PluginsService,
},
],
}

View File

@@ -16,7 +16,6 @@ import { HeaderStatusComponent } from './status.component'
template: `
<header-navigation />
<div class="item item_center">
<ng-content />
<div class="mobile"><ng-container #vcr /></div>
</div>
<header-status class="item item_connection" />

View File

@@ -1,100 +0,0 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { TuiAnimated } from '@taiga-ui/cdk'
import { TuiButton, TuiPopup } from '@taiga-ui/core'
import { ResizerComponent } from 'src/app/routes/portal/components/resizer.component'
import { PluginsService } from 'src/app/services/plugins.service'
@Component({
selector: 'app-plugins',
template: `
<button
tuiIconButton
iconStart="@tui.bot-message-square"
[appearance]="service.enabled() ? 'positive' : 'icon'"
(click)="service.enabled.set(!service.enabled())"
>
AI assistant
</button>
<aside
*tuiPopup="service.enabled()"
tuiAnimated
[class._mobile]="mobile"
[style.--plugins]="service.size() / 100"
(click.self)="onClick($any($event).layerX)"
>
Plugin placeholder
</aside>
<input
*tuiPopup="service.enabled()"
appResizer
type="range"
step="0.1"
[(ngModel)]="service.size"
/>
`,
styles: `
:host {
float: inline-end;
margin-inline-end: 0.5rem;
}
[tuiIconButton] {
background: transparent;
}
aside {
position: fixed;
inset: 0 0 0 calc(320px + (100% - 640px) * var(--plugins));
display: flex;
place-content: center;
place-items: center;
margin: var(--bumper) var(--bumper) var(--bumper) 0;
background: color-mix(in hsl, var(--start9-base-2) 75%, transparent);
background-image: linear-gradient(
transparent,
var(--tui-background-base)
);
backdrop-filter: blur(1rem);
border-radius: var(--bumper);
--tui-from: translateX(100%);
&._mobile {
inset-inline-start: 20%;
box-shadow:
inset 0 1px rgba(255, 255, 255, 0.25),
0 0 0 100vh rgb(0 0 0 / 50%);
&::before {
content: '';
position: fixed;
inset: -100vh;
}
&.tui-enter,
&.tui-leave {
animation-name: tuiSlide, tuiFade;
}
}
&.tui-enter,
&.tui-leave {
animation-name: tuiSlide;
}
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [FormsModule, TuiButton, ResizerComponent, TuiPopup, TuiAnimated],
})
export class PluginsComponent {
protected readonly mobile = inject(WA_IS_MOBILE)
protected readonly service = inject(PluginsService)
protected onClick(layerX: number) {
if (layerX < 0 && this.mobile) {
this.service.enabled.set(false)
}
}
}

View File

@@ -1,56 +0,0 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
@Component({
selector: 'input[type="range"][appResizer]',
template: '',
styles: `
@use '@taiga-ui/styles/utils' as taiga;
:host {
@include taiga.transition(color);
position: fixed;
inset: 0 calc(320px - var(--bumper)) 0 calc(320px - 2 * var(--bumper));
appearance: none;
pointer-events: none;
background: none;
color: transparent;
outline: none;
cursor: ew-resize;
&:hover {
color: var(--tui-background-neutral-1-hover);
}
&::-webkit-slider-runnable-track {
block-size: 100%;
}
&::-webkit-slider-thumb {
block-size: 100%;
inline-size: calc(var(--bumper) * 3);
padding: var(--bumper);
appearance: none;
background: currentColor;
background-clip: content-box;
border: none;
pointer-events: auto;
border-radius: var(--tui-radius-l);
}
&::-moz-range-thumb {
block-size: 100%;
inline-size: calc(var(--bumper) * 3);
padding: var(--bumper);
appearance: none;
background: currentColor;
background-clip: content-box;
border: none;
pointer-events: auto;
border-radius: var(--tui-radius-l);
}
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ResizerComponent {}

View File

@@ -6,7 +6,6 @@ import {
} from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { RouterOutlet } from '@angular/router'
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { ErrorService } from '@start9labs/shared'
import {
TuiButton,
@@ -22,17 +21,15 @@ import {
TuiProgress,
} from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { PluginsComponent } from 'src/app/routes/portal/components/plugins.component'
import { TabsComponent } from 'src/app/routes/portal/components/tabs.component'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { OSService } from 'src/app/services/os.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { PluginsService } from 'src/app/services/plugins.service'
import { HeaderComponent } from './components/header/header.component'
@Component({
template: `
<header appHeader><app-plugins /></header>
<header appHeader>{{ name() }}</header>
<main>
<tui-scrollbar [style.max-height.%]="100">
<router-outlet />
@@ -70,47 +67,19 @@ import { HeaderComponent } from './components/header/header.component'
styles: `
@use '@taiga-ui/styles/utils' as taiga;
@keyframes open {
from {
inline-size: 100%;
}
to {
inline-size: calc(320px + (100% - 640px) * var(--plugins));
}
}
:host {
@include taiga.transition(inline-size);
block-size: 100%;
inline-size: 100%;
height: 100%;
display: flex;
flex-direction: column;
// @TODO Theme
background: url(/assets/img/background_dark.jpeg) fixed center/cover;
&._plugins {
inline-size: calc(320px + (100% - 640px) * var(--plugins));
animation: open var(--tui-duration) ease-in-out;
transition: none;
app-tabs {
inline-size: calc(100% - var(--bumper));
}
}
&::before,
&::after {
&::before {
content: '';
position: fixed;
inset: 0;
backdrop-filter: blur(0.5rem);
}
&::after {
z-index: -1;
// @TODO Theme
background: url(/assets/img/background_dark.jpeg) fixed center/cover;
}
}
main {
@@ -132,10 +101,6 @@ import { HeaderComponent } from './components/header/header.component'
text-wrap: balance;
}
`,
host: {
'[class._plugins]': '!mobile && plugins.enabled()',
'[style.--plugins]': 'plugins.size() / 100',
},
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
RouterOutlet,
@@ -149,7 +114,6 @@ import { HeaderComponent } from './components/header/header.component'
TuiButton,
TuiPopup,
TuiCell,
PluginsComponent,
],
})
export class PortalComponent {
@@ -158,8 +122,6 @@ export class PortalComponent {
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
private readonly api = inject(ApiService)
readonly mobile = inject(WA_IS_MOBILE)
readonly plugins = inject(PluginsService)
readonly name = toSignal(this.patch.watch$('serverInfo', 'name'))
readonly update = toSignal(inject(OSService).updating$)
readonly bar = signal(true)

View File

@@ -1,37 +0,0 @@
import { inject, Injectable, INJECTOR, Injector, signal } from '@angular/core'
import { toObservable } from '@angular/core/rxjs-interop'
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { TUI_WINDOW_SIZE } from '@taiga-ui/cdk'
import { TUI_MEDIA } from '@taiga-ui/core'
import { combineLatest, map, Observable } from 'rxjs'
@Injectable({ providedIn: 'root' })
export class PluginsService extends Observable<DOMRect> {
public readonly enabled = signal(false)
public readonly size = signal(100)
// @ts-expect-error triggering TUI_WINDOW_SIZE default factory
private readonly window = Injector.create({
parent: inject(INJECTOR),
providers: [{ provide: TUI_WINDOW_SIZE }],
}).get(TUI_WINDOW_SIZE)
private readonly media = inject(TUI_MEDIA)
private readonly stream = inject(WA_IS_MOBILE)
? this.window
: combineLatest([
this.window,
toObservable(this.size),
toObservable(this.enabled),
]).pipe(
map(([window, size, enabled]) =>
window.width < this.media.mobile || !enabled
? window
: { ...window, width: 320 + (window.width - 640) * (size / 100) },
),
)
constructor() {
super(subscriber => this.stream.subscribe(subscriber))
}
}