1
0
forked from boos/BoosAPI

feat(desktop): embed BoosAPI Desktop client as a subproject

Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
This commit is contained in:
boos
2026-06-22 11:32:57 +08:00
parent d9c78ecc73
commit e0a5f57793
2305 changed files with 449355 additions and 0 deletions
@@ -0,0 +1,156 @@
import type { AfterViewInit, ElementRef, OnChanges, OnDestroy } from '@angular/core'
import type { AngularRenderableNode, AngularRenderContext } from '../shared/node-helpers'
import { CommonModule } from '@angular/common'
import {
ChangeDetectionStrategy,
Component,
forwardRef,
Input,
ViewChild,
} from '@angular/core'
import { hideTooltip, showTooltipForAnchor } from '../../tooltip/singletonTooltip'
import { NestedRendererComponent } from '../NestedRenderer/NestedRenderer.component'
import { getNodeList, getString } from '../shared/node-helpers'
import { TextNodeComponent } from '../TextNode/TextNode.component'
@Component({
selector: 'markstream-angular-link-node',
standalone: true,
imports: [CommonModule, forwardRef(() => NestedRendererComponent), forwardRef(() => TextNodeComponent)],
template: `
<a
*ngIf="!loading; else loadingTpl"
#anchorEl
class="link-node"
[attr.href]="href || null"
[attr.title]="resolvedShowTooltip ? '' : title"
[attr.aria-label]="'Link: ' + title"
target="_blank"
rel="noopener noreferrer"
[ngStyle]="cssVars"
(mouseenter)="onAnchorEnter()"
(mouseleave)="onAnchorLeave()"
>
<markstream-angular-nested-renderer
*ngIf="hasChildren; else fallbackText"
[nodes]="children"
[context]="context"
[indexPrefix]="nestedPrefix"
/>
<ng-template #fallbackText>
<markstream-angular-text-node
[node]="{ type: 'text', content: fallbackLabel }"
[context]="context"
[indexKey]="nestedPrefix + '-fallback'"
/>
</ng-template>
</a>
<ng-template #loadingTpl>
<span class="link-loading" [ngStyle]="cssVars" aria-hidden="false">
<span class="link-text-wrapper">
<span class="link-text">
<markstream-angular-text-node
[node]="{ type: 'text', content: fallbackLabel }"
[context]="context"
[indexKey]="nestedPrefix + '-loading'"
/>
</span>
<span class="link-loading-indicator" aria-hidden="true"></span>
</span>
</span>
</ng-template>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LinkNodeComponent implements AfterViewInit, OnChanges, OnDestroy {
@ViewChild('anchorEl') private anchorRef?: ElementRef<HTMLElement>
@Input({ required: true }) node!: AngularRenderableNode
@Input() context?: AngularRenderContext
@Input() indexKey?: string
private hovering = false
get href() {
return getString((this.node as any)?.href)
}
get title() {
const rawTitle = getString((this.node as any)?.title)
return rawTitle || this.href
}
get loading() {
return (this.node as any)?.loading === true
}
get resolvedShowTooltip() {
return this.context?.showTooltips !== false
}
get children() {
return getNodeList((this.node as any)?.children)
}
get hasChildren() {
return this.children.length > 0
}
get fallbackLabel() {
return getString((this.node as any)?.text || this.href)
}
get nestedPrefix() {
return `${this.indexKey || 'link'}-inline`
}
get cssVars() {
return {
'--link-color': '#0366d6',
'--underline-height': '2px',
'--underline-bottom': '-3px',
'--underline-opacity': '0.35',
'--underline-rest-opacity': '0.175',
'--underline-duration': '1.6s',
'--underline-timing': 'ease-in-out',
'--underline-iteration': 'infinite',
} as Record<string, string>
}
ngAfterViewInit() {
this.syncTooltip()
}
ngOnChanges() {
this.syncTooltip()
}
ngOnDestroy() {
if (this.hovering)
hideTooltip(true)
}
onAnchorEnter() {
if (!this.resolvedShowTooltip)
return
this.hovering = true
showTooltipForAnchor(this.anchorRef?.nativeElement || null, this.title, 'top', false, this.context?.isDark)
}
onAnchorLeave() {
if (!this.resolvedShowTooltip)
return
this.hovering = false
hideTooltip()
}
private syncTooltip() {
if (!this.hovering || !this.resolvedShowTooltip)
return
showTooltipForAnchor(this.anchorRef?.nativeElement || null, this.title, 'top', true, this.context?.isDark)
}
}