mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
feat(portal): implement drag and drop add/remove (#2383)
This commit is contained in:
@@ -20,7 +20,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<ng-template #content>
|
<ng-template #content>
|
||||||
<app-actions
|
<app-actions
|
||||||
[actions]="(actions | toDesktopActions : id | async) || {}"
|
[actions]="actions"
|
||||||
(click)="dropdown.openChange.next(false)"
|
(click)="dropdown.openChange.next(false)"
|
||||||
>
|
>
|
||||||
{{ title }}
|
{{ title }}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
width: 2.5rem;
|
width: 2.5rem;
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
box-shadow: 0.25rem 0.25rem 0.25rem rgb(0 0 0 / 25%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import {
|
|||||||
} from '@taiga-ui/core'
|
} from '@taiga-ui/core'
|
||||||
import { NavigationService } from '../navigation/navigation.service'
|
import { NavigationService } from '../navigation/navigation.service'
|
||||||
import { Action, ActionsComponent } from '../actions/actions.component'
|
import { Action, ActionsComponent } from '../actions/actions.component'
|
||||||
import { ToDesktopActionsPipe } from '../../pipes/to-desktop-actions'
|
|
||||||
import { toRouterLink } from '../../utils/to-router-link'
|
import { toRouterLink } from '../../utils/to-router-link'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -34,7 +33,6 @@ import { toRouterLink } from '../../utils/to-router-link'
|
|||||||
TuiSvgModule,
|
TuiSvgModule,
|
||||||
TickerModule,
|
TickerModule,
|
||||||
ActionsComponent,
|
ActionsComponent,
|
||||||
ToDesktopActionsPipe,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CardComponent {
|
export class CardComponent {
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import {
|
||||||
|
Directive,
|
||||||
|
ElementRef,
|
||||||
|
HostListener,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { tuiGetActualTarget, tuiIsElement, tuiPx } from '@taiga-ui/cdk'
|
||||||
|
import { DrawerComponent } from './drawer.component'
|
||||||
|
import { DesktopService } from '../../services/desktop.service'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This directive is responsible for drag and drop of the drawer item.
|
||||||
|
* It saves item to desktop when dropped.
|
||||||
|
*/
|
||||||
|
@Directive({
|
||||||
|
selector: '[drawerItem]',
|
||||||
|
standalone: true,
|
||||||
|
host: {
|
||||||
|
'[style.touchAction]': '"none"',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export class DrawerItemDirective {
|
||||||
|
private readonly desktop = inject(DesktopService)
|
||||||
|
private readonly drawer = inject(DrawerComponent)
|
||||||
|
private readonly element: HTMLElement = inject(ElementRef).nativeElement
|
||||||
|
|
||||||
|
private x = NaN
|
||||||
|
private y = NaN
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
drawerItem = ''
|
||||||
|
|
||||||
|
@HostListener('pointerdown.prevent.silent', ['$event'])
|
||||||
|
onStart(event: PointerEvent): void {
|
||||||
|
// This element is already on the desktop
|
||||||
|
if (this.desktop.items.includes(this.drawerItem)) return
|
||||||
|
|
||||||
|
const target = tuiGetActualTarget(event)
|
||||||
|
const { x, y, pointerId } = event
|
||||||
|
const { left, top } = this.element.getBoundingClientRect()
|
||||||
|
|
||||||
|
if (tuiIsElement(target)) {
|
||||||
|
target.releasePointerCapture(pointerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.drawer.open = false
|
||||||
|
this.onPointer(x - left, y - top)
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:pointerup.silent')
|
||||||
|
onPointer(x = NaN, y = NaN): void {
|
||||||
|
// Some other element is dragged
|
||||||
|
if (Number.isNaN(this.x) && Number.isNaN(x)) return
|
||||||
|
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
this.process(NaN, NaN)
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:pointermove.silent', ['$event.x', '$event.y'])
|
||||||
|
onMove(x: number, y: number): void {
|
||||||
|
// This element is not dragged
|
||||||
|
if (Number.isNaN(this.x)) return
|
||||||
|
|
||||||
|
this.process(x, y)
|
||||||
|
this.desktop.add('')
|
||||||
|
}
|
||||||
|
|
||||||
|
private process(x: number, y: number) {
|
||||||
|
const { style } = this.element
|
||||||
|
const { items } = this.desktop
|
||||||
|
const dragged = !Number.isNaN(this.x + x)
|
||||||
|
|
||||||
|
style.pointerEvents = dragged ? 'none' : ''
|
||||||
|
style.position = dragged ? 'fixed' : ''
|
||||||
|
style.top = dragged ? tuiPx(y - this.y) : ''
|
||||||
|
style.left = dragged ? tuiPx(x - this.x) : ''
|
||||||
|
|
||||||
|
if (dragged || !items.includes('')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.desktop.items = items.map(item => item || this.drawerItem)
|
||||||
|
this.desktop.reorder(this.desktop.order)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
empty: empty
|
empty: empty
|
||||||
"
|
"
|
||||||
appCard
|
appCard
|
||||||
|
[drawerItem]="item.key"
|
||||||
[id]="item.key"
|
[id]="item.key"
|
||||||
[title]="item.value.title"
|
[title]="item.value.title"
|
||||||
[icon]="item.value.icon"
|
[icon]="item.value.icon"
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
empty: empty
|
empty: empty
|
||||||
"
|
"
|
||||||
appCard
|
appCard
|
||||||
|
[drawerItem]="item.manifest.id"
|
||||||
[id]="item.manifest.id"
|
[id]="item.manifest.id"
|
||||||
[icon]="item.icon"
|
[icon]="item.icon"
|
||||||
[title]="item.manifest.title"
|
[title]="item.manifest.title"
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { CardComponent } from '../card/card.component'
|
|||||||
import { ServicesService } from '../../services/services.service'
|
import { ServicesService } from '../../services/services.service'
|
||||||
import { SYSTEM_UTILITIES } from './drawer.const'
|
import { SYSTEM_UTILITIES } from './drawer.const'
|
||||||
import { toRouterLink } from '../../utils/to-router-link'
|
import { toRouterLink } from '../../utils/to-router-link'
|
||||||
|
import { DrawerItemDirective } from './drawer-item.directive'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-drawer',
|
selector: 'app-drawer',
|
||||||
@@ -41,6 +42,7 @@ import { toRouterLink } from '../../utils/to-router-link'
|
|||||||
TuiForModule,
|
TuiForModule,
|
||||||
TuiFilterPipeModule,
|
TuiFilterPipeModule,
|
||||||
CardComponent,
|
CardComponent,
|
||||||
|
DrawerItemDirective,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import { inject, Pipe, PipeTransform } from '@angular/core'
|
|
||||||
import { Action } from '../components/actions/actions.component'
|
|
||||||
import { filter, map, Observable } from 'rxjs'
|
|
||||||
import { DesktopService } from '../routes/desktop/desktop.service'
|
|
||||||
|
|
||||||
@Pipe({
|
|
||||||
name: 'toDesktopActions',
|
|
||||||
standalone: true,
|
|
||||||
})
|
|
||||||
export class ToDesktopActionsPipe implements PipeTransform {
|
|
||||||
private readonly desktop = inject(DesktopService)
|
|
||||||
|
|
||||||
transform(
|
|
||||||
value: Record<string, readonly Action[]>,
|
|
||||||
id: string,
|
|
||||||
): Observable<Record<string, readonly Action[]>> {
|
|
||||||
return this.desktop.desktop$.pipe(
|
|
||||||
filter(Boolean),
|
|
||||||
map(desktop => {
|
|
||||||
const action = desktop.includes(id)
|
|
||||||
? {
|
|
||||||
icon: 'tuiIconMinus',
|
|
||||||
label: 'Remove from Desktop',
|
|
||||||
action: () => this.desktop.remove(id),
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
icon: 'tuiIconPlus',
|
|
||||||
label: 'Add to Desktop',
|
|
||||||
action: () => this.desktop.add(id),
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
manage: [action],
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,9 @@ export class ToDesktopItemPipe implements PipeTransform {
|
|||||||
transform(
|
transform(
|
||||||
packages: Record<string, PackageDataEntry>,
|
packages: Record<string, PackageDataEntry>,
|
||||||
id: string,
|
id: string,
|
||||||
): NavigationItem {
|
): NavigationItem | null {
|
||||||
|
if (!id) return null
|
||||||
|
|
||||||
const item = SYSTEM_UTILITIES[id]
|
const item = SYSTEM_UTILITIES[id]
|
||||||
const routerLink = toRouterLink(id)
|
const routerLink = toRouterLink(id)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { inject, Injectable } from '@angular/core'
|
||||||
|
import { PatchDB } from 'patch-db-client'
|
||||||
|
import {
|
||||||
|
endWith,
|
||||||
|
ignoreElements,
|
||||||
|
Observable,
|
||||||
|
shareReplay,
|
||||||
|
startWith,
|
||||||
|
take,
|
||||||
|
tap,
|
||||||
|
} from 'rxjs'
|
||||||
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
|
import { DesktopService } from '../../services/desktop.service'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This service loads initial values for desktop items
|
||||||
|
* and is used to show loading indicator.
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class DektopLoadingService extends Observable<boolean> {
|
||||||
|
private readonly desktop = inject(DesktopService)
|
||||||
|
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||||
|
private readonly loading = this.patch.watch$('ui', 'desktop').pipe(
|
||||||
|
take(1),
|
||||||
|
tap(items => (this.desktop.items = items.filter(Boolean))),
|
||||||
|
ignoreElements(),
|
||||||
|
startWith(true),
|
||||||
|
endWith(false),
|
||||||
|
shareReplay(1),
|
||||||
|
)
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(subscriber => this.loading.subscribe(subscriber))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import {
|
||||||
|
Directive,
|
||||||
|
ElementRef,
|
||||||
|
HostBinding,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { TuiTilesComponent } from '@taiga-ui/kit'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This directive is responsible for creating empty placeholder
|
||||||
|
* on the desktop when item is dragged from the drawer
|
||||||
|
*/
|
||||||
|
@Directive({
|
||||||
|
selector: '[desktopItem]',
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
export class DesktopItemDirective implements OnInit, OnDestroy {
|
||||||
|
private readonly element: Element = inject(ElementRef).nativeElement
|
||||||
|
private readonly tiles = inject(TuiTilesComponent)
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
desktopItem = ''
|
||||||
|
|
||||||
|
@HostBinding('class._empty')
|
||||||
|
get empty(): boolean {
|
||||||
|
return !this.desktopItem
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
if (this.empty) this.tiles.element = this.element
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove after Taiga UI updated to 3.40.0
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.tiles.element === this.element) this.tiles.element = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,41 @@
|
|||||||
<ng-container *ngIf="desktop$ | async as desktop">
|
<tui-loader
|
||||||
|
*ngIf="loading$ | async; else data"
|
||||||
|
size="xl"
|
||||||
|
class="loader"
|
||||||
|
></tui-loader>
|
||||||
|
<ng-template #data>
|
||||||
|
<tui-svg
|
||||||
|
*ngIf="tile?.element"
|
||||||
|
class="remove"
|
||||||
|
src="tuiIconTrash2Large"
|
||||||
|
@tuiFadeIn
|
||||||
|
@tuiScaleIn
|
||||||
|
(pointerup)="onRemove()"
|
||||||
|
></tui-svg>
|
||||||
<tui-tiles
|
<tui-tiles
|
||||||
*ngIf="packages$ | async as packages"
|
*ngIf="packages$ | async as packages"
|
||||||
class="tiles"
|
class="tiles"
|
||||||
|
@tuiParentStop
|
||||||
[debounce]="500"
|
[debounce]="500"
|
||||||
[order]="order"
|
[order]="desktop.order"
|
||||||
(orderChange)="onReorder($event, desktop)"
|
(orderChange)="onReorder($event)"
|
||||||
>
|
>
|
||||||
<tui-tile
|
<tui-tile
|
||||||
*ngFor="let service of desktop; let index = index"
|
*ngFor="let item of desktop.items; let index = index"
|
||||||
class="item"
|
class="item"
|
||||||
[style.order]="order.get(index)"
|
[style.order]="desktop.order.get(index)"
|
||||||
|
[desktopItem]="item"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
*ngIf="packages | toDesktopItem : service as item"
|
*ngIf="packages | toDesktopItem : item as desktopItem"
|
||||||
tuiTileHandle
|
tuiTileHandle
|
||||||
appCard
|
appCard
|
||||||
[id]="service"
|
@tuiFadeIn
|
||||||
[title]="item.title"
|
[id]="item"
|
||||||
[icon]="item.icon"
|
[title]="desktopItem.title"
|
||||||
[routerLink]="item.routerLink"
|
[icon]="desktopItem.icon"
|
||||||
|
[routerLink]="desktopItem.routerLink"
|
||||||
></a>
|
></a>
|
||||||
</tui-tile>
|
</tui-tile>
|
||||||
</tui-tiles>
|
</tui-tiles>
|
||||||
</ng-container>
|
</ng-template>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@import '@taiga-ui/core/styles/taiga-ui-local';
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -9,10 +11,38 @@
|
|||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
height: 10rem;
|
||||||
|
width: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
.tiles {
|
.tiles {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-height: 5.5rem;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
grid-template-columns: repeat(auto-fit, 12.5rem);
|
grid-template-columns: repeat(auto-fit, 12.5rem);
|
||||||
grid-auto-rows: 5.5rem;
|
grid-auto-rows: 5.5rem;
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.remove {
|
||||||
|
@include transition(background);
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: calc(50% - 3rem);
|
||||||
|
width: 6rem;
|
||||||
|
height: 6rem;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: var(--tui-base-02);
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--tui-base-01);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item._dragged,
|
||||||
|
.item._empty {
|
||||||
|
border-radius: var(--tui-radius-l);
|
||||||
|
box-shadow: inset 0 0 0 0.5rem var(--tui-clear-active);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,35 +1,51 @@
|
|||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
import {
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
inject,
|
||||||
|
QueryList,
|
||||||
|
ViewChild,
|
||||||
|
ViewChildren,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { EMPTY_QUERY, TUI_PARENT_STOP } from '@taiga-ui/cdk'
|
||||||
|
import { tuiFadeIn, tuiScaleIn } from '@taiga-ui/core'
|
||||||
|
import { TuiTileComponent, TuiTilesComponent } from '@taiga-ui/kit'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { tap } from 'rxjs'
|
import {
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
DataModel,
|
||||||
import { DesktopService } from './desktop.service'
|
PackageDataEntry,
|
||||||
|
} from 'src/app/services/patch-db/data-model'
|
||||||
|
import { DesktopService } from '../../services/desktop.service'
|
||||||
|
import { Observable } from 'rxjs'
|
||||||
|
import { DektopLoadingService } from './dektop-loading.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: 'desktop.component.html',
|
templateUrl: 'desktop.component.html',
|
||||||
styleUrls: ['desktop.component.scss'],
|
styleUrls: ['desktop.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
animations: [TUI_PARENT_STOP, tuiScaleIn, tuiFadeIn],
|
||||||
})
|
})
|
||||||
export class DesktopComponent {
|
export class DesktopComponent {
|
||||||
private readonly desktop = inject(DesktopService)
|
@ViewChildren(TuiTileComponent, { read: ElementRef })
|
||||||
|
private readonly tiles: QueryList<ElementRef> = EMPTY_QUERY
|
||||||
|
|
||||||
readonly desktop$ = this.desktop.desktop$.pipe(
|
readonly desktop = inject(DesktopService)
|
||||||
tap(() => (this.order = new Map())),
|
readonly loading$ = inject(DektopLoadingService)
|
||||||
)
|
readonly packages$: Observable<Record<string, PackageDataEntry>> =
|
||||||
|
|
||||||
readonly packages$ =
|
|
||||||
inject<PatchDB<DataModel>>(PatchDB).watch$('package-data')
|
inject<PatchDB<DataModel>>(PatchDB).watch$('package-data')
|
||||||
|
|
||||||
order = new Map()
|
@ViewChild(TuiTilesComponent)
|
||||||
|
readonly tile?: TuiTilesComponent
|
||||||
|
|
||||||
onReorder(order: Map<number, number>, desktop: readonly string[]) {
|
onRemove() {
|
||||||
this.order = order
|
const element = this.tile?.element
|
||||||
|
const index = this.tiles
|
||||||
|
.toArray()
|
||||||
|
.map(({ nativeElement }) => nativeElement)
|
||||||
|
.indexOf(element)
|
||||||
|
|
||||||
const items: string[] = []
|
this.desktop.remove(this.desktop.items[index])
|
||||||
|
}
|
||||||
|
|
||||||
Array.from(this.order.entries()).forEach(([index, order]) => {
|
onReorder(order: Map<number, number>) {
|
||||||
items[order] = desktop[index]
|
this.desktop.reorder(order)
|
||||||
})
|
|
||||||
|
|
||||||
this.desktop.save(items)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
import { RouterModule, Routes } from '@angular/router'
|
||||||
|
import { TuiLoaderModule, TuiSvgModule } from '@taiga-ui/core'
|
||||||
import { TuiTilesModule } from '@taiga-ui/kit'
|
import { TuiTilesModule } from '@taiga-ui/kit'
|
||||||
import { DesktopComponent } from './desktop.component'
|
import { DesktopComponent } from './desktop.component'
|
||||||
import { CardComponent } from '../../components/card/card.component'
|
import { CardComponent } from '../../components/card/card.component'
|
||||||
import { ToDesktopActionsPipe } from '../../pipes/to-desktop-actions'
|
|
||||||
import { ToDesktopItemPipe } from '../../pipes/to-desktop-item'
|
import { ToDesktopItemPipe } from '../../pipes/to-desktop-item'
|
||||||
|
import { DesktopItemDirective } from './desktop-item.directive'
|
||||||
|
|
||||||
const ROUTES: Routes = [
|
const ROUTES: Routes = [
|
||||||
{
|
{
|
||||||
@@ -18,8 +19,10 @@ const ROUTES: Routes = [
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
CardComponent,
|
CardComponent,
|
||||||
|
DesktopItemDirective,
|
||||||
|
TuiSvgModule,
|
||||||
|
TuiLoaderModule,
|
||||||
TuiTilesModule,
|
TuiTilesModule,
|
||||||
ToDesktopActionsPipe,
|
|
||||||
ToDesktopItemPipe,
|
ToDesktopItemPipe,
|
||||||
RouterModule.forChild(ROUTES),
|
RouterModule.forChild(ROUTES),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
import { inject, Injectable } from '@angular/core'
|
|
||||||
import { TuiAlertService } from '@taiga-ui/core'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
|
||||||
import { BehaviorSubject, first } from 'rxjs'
|
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class DesktopService {
|
|
||||||
private readonly alerts = inject(TuiAlertService)
|
|
||||||
private readonly api = inject(ApiService)
|
|
||||||
|
|
||||||
readonly desktop$ = new BehaviorSubject<readonly string[] | undefined>(
|
|
||||||
undefined,
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
inject<PatchDB<DataModel>>(PatchDB)
|
|
||||||
.watch$('ui', 'desktop')
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe(desktop => {
|
|
||||||
if (!this.desktop$.value) {
|
|
||||||
this.desktop$.next(desktop)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
add(id: string) {
|
|
||||||
this.desktop$.next(this.desktop$.value?.concat(id))
|
|
||||||
this.save(this.desktop$.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
remove(id: string) {
|
|
||||||
this.desktop$.next(this.desktop$.value?.filter(x => x !== id))
|
|
||||||
this.save(this.desktop$.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
save(ids: readonly string[] = []) {
|
|
||||||
this.api
|
|
||||||
.setDbValue(['desktop'], ids)
|
|
||||||
.catch(() =>
|
|
||||||
this.alerts
|
|
||||||
.open(
|
|
||||||
'Desktop might be out of sync. Please refresh the page to fix it.',
|
|
||||||
{ status: 'warning' },
|
|
||||||
)
|
|
||||||
.subscribe(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { inject, Injectable } from '@angular/core'
|
||||||
|
import { TuiAlertService } from '@taiga-ui/core'
|
||||||
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class DesktopService {
|
||||||
|
private readonly alerts = inject(TuiAlertService)
|
||||||
|
private readonly api = inject(ApiService)
|
||||||
|
|
||||||
|
order = new Map()
|
||||||
|
items: readonly string[] = []
|
||||||
|
|
||||||
|
add(id: string) {
|
||||||
|
if (this.items.includes(id)) return
|
||||||
|
|
||||||
|
this.items = this.items.concat(id)
|
||||||
|
this.save(this.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(id: string) {
|
||||||
|
if (!this.items.includes(id)) return
|
||||||
|
|
||||||
|
this.items = this.items.filter(x => x !== id)
|
||||||
|
this.save(this.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
save(ids: readonly string[] = []) {
|
||||||
|
this.api
|
||||||
|
.setDbValue(['desktop'], Array.from(new Set(ids)))
|
||||||
|
.catch(() =>
|
||||||
|
this.alerts
|
||||||
|
.open(
|
||||||
|
'Desktop might be out of sync. Please refresh the page to fix it.',
|
||||||
|
{ status: 'warning' },
|
||||||
|
)
|
||||||
|
.subscribe(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
reorder(order: Map<number, number>) {
|
||||||
|
this.order = order
|
||||||
|
|
||||||
|
const items: string[] = [...this.items]
|
||||||
|
|
||||||
|
Array.from(this.order.entries()).forEach(([index, order]) => {
|
||||||
|
items[order] = this.items[index]
|
||||||
|
})
|
||||||
|
|
||||||
|
this.save(items)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user