@aiao/rxdb-angular 0.0.3 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/eslint.config.mjs +58 -0
- package/ng-package.json +7 -0
- package/package.json +13 -23
- package/project.json +48 -0
- package/src/InfiniteScrollingList.ts +123 -0
- package/src/devtools/devtools.html +99 -0
- package/src/devtools/devtools.interface.ts +3 -0
- package/src/devtools/devtools.scss +49 -0
- package/src/devtools/devtools.spec.ts +30 -0
- package/src/devtools/devtools.ts +207 -0
- package/src/devtools/entity-table-item.ts +47 -0
- package/src/devtools/entity-table-tools.html +56 -0
- package/src/devtools/entity-table-tools.scss +8 -0
- package/src/devtools/entity-table-tools.ts +153 -0
- package/src/devtools/event-tools.html +15 -0
- package/src/devtools/event-tools.scss +18 -0
- package/src/devtools/event-tools.ts +45 -0
- package/src/devtools/scroll-advanced-calc.service.ts +41 -0
- package/src/devtools/settings.html +46 -0
- package/src/devtools/settings.scss +19 -0
- package/src/devtools/settings.ts +122 -0
- package/src/hooks.ts +307 -0
- package/src/index.ts +7 -0
- package/src/rxdb-change-detector.directive.spec.ts +94 -0
- package/src/rxdb-change-detector.directive.ts +35 -0
- package/src/rxdb.provider.ts +13 -0
- package/src/rxdb.service.spec.ts +31 -0
- package/src/rxdb.service.ts +35 -0
- package/src/test-setup.ts +14 -0
- package/src/use-action.spec.ts +88 -0
- package/src/use-action.ts +20 -0
- package/src/use-state.spec.ts +105 -0
- package/src/use-state.ts +28 -0
- package/tsconfig.json +33 -0
- package/tsconfig.lib.json +42 -0
- package/tsconfig.lib.prod.json +10 -0
- package/tsconfig.spec.json +23 -0
- package/vite.config.mts +55 -0
- package/fesm2022/aiao-rxdb-angular.mjs +0 -47
- package/fesm2022/aiao-rxdb-angular.mjs.map +0 -1
- package/index.d.ts +0 -15
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { JsonPipe } from '@angular/common';
|
|
2
|
+
import { afterNextRender, Component, ElementRef, inject, input } from '@angular/core';
|
|
3
|
+
import { RxDBEntityChangeDirective } from '../rxdb-change-detector.directive';
|
|
4
|
+
import { ScrollAdvancedCalcService } from './scroll-advanced-calc.service';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'rxdb-entity-item',
|
|
8
|
+
template: `
|
|
9
|
+
@let item = entity();
|
|
10
|
+
@let i = index();
|
|
11
|
+
|
|
12
|
+
<pre class="text-xs" [class.bg-base-100]="i % 2 === 0" [rxdbChangeDetector]="item">
|
|
13
|
+
{{ item | json }}
|
|
14
|
+
</pre
|
|
15
|
+
>
|
|
16
|
+
`,
|
|
17
|
+
imports: [JsonPipe, RxDBEntityChangeDirective],
|
|
18
|
+
styles: [
|
|
19
|
+
`
|
|
20
|
+
:host {
|
|
21
|
+
display: block;
|
|
22
|
+
min-height: 150px;
|
|
23
|
+
}
|
|
24
|
+
`
|
|
25
|
+
]
|
|
26
|
+
})
|
|
27
|
+
export class RxDBEntityTableItem {
|
|
28
|
+
readonly #calcService = inject(ScrollAdvancedCalcService);
|
|
29
|
+
readonly #el = inject(ElementRef);
|
|
30
|
+
readonly entity = input.required<any>();
|
|
31
|
+
readonly index = input.required<number>();
|
|
32
|
+
|
|
33
|
+
constructor() {
|
|
34
|
+
afterNextRender(() => {
|
|
35
|
+
const item = this.entity();
|
|
36
|
+
this.#calcService.cacheCalcDynamic.update(cache => {
|
|
37
|
+
if (!cache.some(c => c.trackId === item.id)) {
|
|
38
|
+
cache.push({
|
|
39
|
+
trackId: item.id,
|
|
40
|
+
itemSize: this.#el.nativeElement.getBoundingClientRect().height
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return cache;
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
@let entity_meta_tree = $entity_meta_tree();
|
|
2
|
+
@let current_entity_meta = $current_entity_meta();
|
|
3
|
+
|
|
4
|
+
<div class="bg-base-200 overflow-y-auto">
|
|
5
|
+
<ul class="menu menu-xs rounded-box w-30 p-0">
|
|
6
|
+
@for (ns of entity_meta_tree; track $index) {
|
|
7
|
+
<li>
|
|
8
|
+
<details open>
|
|
9
|
+
<summary>{{ ns.namespace }}</summary>
|
|
10
|
+
<ul>
|
|
11
|
+
@for (entity of ns.entities; track entity.name) {
|
|
12
|
+
<li>
|
|
13
|
+
<a
|
|
14
|
+
[class.menu-active]="current_entity_meta === entity"
|
|
15
|
+
(click)="$current_entity_meta.set(entity)"
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
>
|
|
18
|
+
{{ entity.name }}
|
|
19
|
+
</a>
|
|
20
|
+
</li>
|
|
21
|
+
}
|
|
22
|
+
</ul>
|
|
23
|
+
</details>
|
|
24
|
+
</li>
|
|
25
|
+
}
|
|
26
|
+
</ul>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="relative flex-1 overflow-hidden">
|
|
30
|
+
<div class="tabs tabs-border tabs-xs bg-base-300 h-full">
|
|
31
|
+
<input class="tab" aria-label="Data" checked="checked" name="my_tabs_2" type="radio" />
|
|
32
|
+
<div class="tab-content h-full overflow-hidden">
|
|
33
|
+
<cdk-virtual-scroll-viewport
|
|
34
|
+
class="tab-content h-full"
|
|
35
|
+
[itemDynamicSizes]="dynamicSize()"
|
|
36
|
+
maxBufferPx="1350"
|
|
37
|
+
minBufferPx="900"
|
|
38
|
+
rdlaboFixVirtualScrollElement
|
|
39
|
+
>
|
|
40
|
+
@defer (on viewport) {
|
|
41
|
+
<ng-container *cdkVirtualFor="let item of $entity_all_data(); let i = index; trackBy: trackByFn">
|
|
42
|
+
<rxdb-entity-item [entity]="item" [index]="i"></rxdb-entity-item>
|
|
43
|
+
</ng-container>
|
|
44
|
+
} @placeholder {
|
|
45
|
+
<div class="h-40"></div>
|
|
46
|
+
}
|
|
47
|
+
</cdk-virtual-scroll-viewport>
|
|
48
|
+
</div>
|
|
49
|
+
<input class="tab" aria-label="Metadata" name="my_tabs_2" type="radio" />
|
|
50
|
+
<div class="tab-content h-full overflow-y-auto">
|
|
51
|
+
@if (current_entity_meta) {
|
|
52
|
+
<ao-code-editor [theme]="theme()" [value]="current_entity_meta | json" language="json"></ao-code-editor>
|
|
53
|
+
}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { CodeEditor } from '@aiao/code-editor-angular';
|
|
2
|
+
import { EntityMetadata, EntityType, getEntityMetadata, RxDB } from '@aiao/rxdb';
|
|
3
|
+
import { nextMacroTask } from '@aiao/utils';
|
|
4
|
+
import { ObserversModule } from '@angular/cdk/observers';
|
|
5
|
+
import { CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
|
6
|
+
import { CommonModule } from '@angular/common';
|
|
7
|
+
import {
|
|
8
|
+
ChangeDetectionStrategy,
|
|
9
|
+
ChangeDetectorRef,
|
|
10
|
+
Component,
|
|
11
|
+
computed,
|
|
12
|
+
effect,
|
|
13
|
+
inject,
|
|
14
|
+
input,
|
|
15
|
+
OnInit,
|
|
16
|
+
signal,
|
|
17
|
+
viewChild
|
|
18
|
+
} from '@angular/core';
|
|
19
|
+
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
|
|
20
|
+
import {
|
|
21
|
+
CdkDynamicSizeVirtualScroll,
|
|
22
|
+
DynamicSizeVirtualScrollService,
|
|
23
|
+
itemDynamicSize
|
|
24
|
+
} from '@rdlabo/ngx-cdk-scroll-strategies';
|
|
25
|
+
import { LucideAngularModule } from 'lucide-angular';
|
|
26
|
+
import { of, switchMap } from 'rxjs';
|
|
27
|
+
import { RxDBEntityTableItem } from './entity-table-item';
|
|
28
|
+
import { ScrollAdvancedCalcService } from './scroll-advanced-calc.service';
|
|
29
|
+
|
|
30
|
+
interface EntityTreeItem {
|
|
31
|
+
namespace: string;
|
|
32
|
+
entities: EntityMetadata[];
|
|
33
|
+
}
|
|
34
|
+
(BigInt.prototype as any)['toJSON'] = function () {
|
|
35
|
+
return Number(this);
|
|
36
|
+
};
|
|
37
|
+
@Component({
|
|
38
|
+
selector: 'rxdb-entity-table-tools',
|
|
39
|
+
imports: [
|
|
40
|
+
LucideAngularModule,
|
|
41
|
+
CommonModule,
|
|
42
|
+
RxDBEntityTableItem,
|
|
43
|
+
CodeEditor,
|
|
44
|
+
ObserversModule,
|
|
45
|
+
CdkVirtualForOf,
|
|
46
|
+
CdkVirtualScrollViewport,
|
|
47
|
+
CdkDynamicSizeVirtualScroll
|
|
48
|
+
],
|
|
49
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
50
|
+
styleUrls: ['./entity-table-tools.scss'],
|
|
51
|
+
templateUrl: './entity-table-tools.html'
|
|
52
|
+
})
|
|
53
|
+
export class RxDBEntityTableTools implements OnInit {
|
|
54
|
+
#rxdb = inject(RxDB);
|
|
55
|
+
#entity_metadata_map = new Map<EntityMetadata, EntityType>();
|
|
56
|
+
#scroll = inject(DynamicSizeVirtualScrollService);
|
|
57
|
+
#calcService = inject(ScrollAdvancedCalcService);
|
|
58
|
+
#latestScrollOffset = 0;
|
|
59
|
+
|
|
60
|
+
virtualScroll = viewChild(CdkVirtualScrollViewport);
|
|
61
|
+
|
|
62
|
+
dynamicSize = computed<itemDynamicSize[]>(() => {
|
|
63
|
+
const size = this.#calcService.changeItemsToDynamicItemSize(
|
|
64
|
+
this.$entity_all_data() as any,
|
|
65
|
+
this.#calcService.cacheCalcDynamic(),
|
|
66
|
+
this.virtualScroll()
|
|
67
|
+
);
|
|
68
|
+
return size;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
cdr = inject(ChangeDetectorRef);
|
|
72
|
+
theme = input<'light' | 'dark'>('light');
|
|
73
|
+
|
|
74
|
+
$entity_meta_tree = signal<EntityTreeItem[]>([]);
|
|
75
|
+
$current_entity_meta = signal<EntityMetadata | null>(null);
|
|
76
|
+
|
|
77
|
+
current_entity_meta$ = toObservable(this.$current_entity_meta);
|
|
78
|
+
|
|
79
|
+
entity_all_data$ = this.current_entity_meta$.pipe(
|
|
80
|
+
switchMap(meta => {
|
|
81
|
+
if (meta) {
|
|
82
|
+
const entity = this.#entity_metadata_map.get(meta)!;
|
|
83
|
+
return this.#rxdb.entityManager.getRepository(entity).findAll({
|
|
84
|
+
where: {
|
|
85
|
+
combinator: 'and',
|
|
86
|
+
rules: []
|
|
87
|
+
},
|
|
88
|
+
orderBy: [
|
|
89
|
+
{
|
|
90
|
+
field: 'id',
|
|
91
|
+
sort: 'desc'
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
return of([]);
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
$entity_all_data = toSignal(this.entity_all_data$);
|
|
102
|
+
constructor() {
|
|
103
|
+
effect(() => {
|
|
104
|
+
this.$current_entity_meta();
|
|
105
|
+
nextMacroTask(() => {
|
|
106
|
+
this.#calcService.beforeCacheCalcDynamicSize.set(0);
|
|
107
|
+
this.#calcService.cacheCalcDynamic.set([]);
|
|
108
|
+
this.virtualScroll()?.scrollTo({
|
|
109
|
+
top: 0
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
trackByFn = (_: number, item: any) => {
|
|
115
|
+
let track_id = item.id;
|
|
116
|
+
if (item.updatedAt) {
|
|
117
|
+
track_id += (item.updatedAt as Date).getTime();
|
|
118
|
+
}
|
|
119
|
+
return track_id;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
ngOnInit(): void {
|
|
123
|
+
this.#scroll.onInit(this.virtualScroll()!, this.#latestScrollOffset);
|
|
124
|
+
this.virtualScroll()?.scrolledIndexChange.subscribe(() => {
|
|
125
|
+
if (this.#calcService.beforeCacheCalcDynamicSize() !== this.#calcService.cacheCalcDynamic().length) {
|
|
126
|
+
this.#calcService.cacheCalcDynamic.update(cache => [...cache]);
|
|
127
|
+
this.#calcService.beforeCacheCalcDynamicSize.set(this.#calcService.cacheCalcDynamic().length);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const entity_meta_tree: EntityTreeItem[] = [];
|
|
132
|
+
let rxdb_change_meta: any;
|
|
133
|
+
this.#rxdb.config.entities.forEach(entity => {
|
|
134
|
+
const meta = getEntityMetadata(entity);
|
|
135
|
+
this.#entity_metadata_map.set(meta, entity);
|
|
136
|
+
if (meta.name === 'RxDBChange') {
|
|
137
|
+
rxdb_change_meta = meta;
|
|
138
|
+
}
|
|
139
|
+
const namespace = meta.namespace;
|
|
140
|
+
const existing = entity_meta_tree.find(item => item.namespace === namespace);
|
|
141
|
+
if (existing) {
|
|
142
|
+
existing.entities.push(meta);
|
|
143
|
+
} else {
|
|
144
|
+
entity_meta_tree.push({
|
|
145
|
+
namespace,
|
|
146
|
+
entities: [meta]
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
this.$entity_meta_tree.set(entity_meta_tree);
|
|
151
|
+
this.$current_entity_meta.set(rxdb_change_meta);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
@let events = $events();
|
|
2
|
+
|
|
3
|
+
<div class="bg-base-300 w-full text-xs">
|
|
4
|
+
<button class="btn btn-xs" (click)="clear()">clear</button>
|
|
5
|
+
<span>{{ events.length }}</span>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="bg-base-300 relative flex-1 overflow-y-auto">
|
|
8
|
+
@for (item of events; track item) {
|
|
9
|
+
@defer (on viewport) {
|
|
10
|
+
<pre class="text-xs" [class.bg-base-100]="$index % 2 === 0" animate.enter="fade-in">{{ item | json }}</pre>
|
|
11
|
+
} @placeholder {
|
|
12
|
+
<div class="h-40">loading...</div>
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
@apply flex h-full w-full flex-col overflow-hidden;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.fade-in {
|
|
6
|
+
animation: fadeIn 0.3s ease-out;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
@keyframes fadeIn {
|
|
10
|
+
from {
|
|
11
|
+
opacity: 0;
|
|
12
|
+
transform: translateX(100px);
|
|
13
|
+
}
|
|
14
|
+
to {
|
|
15
|
+
opacity: 1;
|
|
16
|
+
transform: translateX(0);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ENTITY_LOCAL_CREATE_EVENT,
|
|
3
|
+
ENTITY_LOCAL_NEW_EVENT,
|
|
4
|
+
ENTITY_LOCAL_REMOVE_EVENT,
|
|
5
|
+
ENTITY_LOCAL_UPDATE_EVENT,
|
|
6
|
+
RxDB,
|
|
7
|
+
RxDBEvent,
|
|
8
|
+
TRANSACTION_BEGIN,
|
|
9
|
+
TRANSACTION_COMMIT,
|
|
10
|
+
TRANSACTION_ROLLBACK
|
|
11
|
+
} from '@aiao/rxdb';
|
|
12
|
+
import { JsonPipe } from '@angular/common';
|
|
13
|
+
import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';
|
|
14
|
+
import { LucideAngularModule } from 'lucide-angular';
|
|
15
|
+
|
|
16
|
+
@Component({
|
|
17
|
+
selector: 'rxdb-event-tools',
|
|
18
|
+
imports: [LucideAngularModule, JsonPipe],
|
|
19
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
20
|
+
styleUrls: ['./event-tools.scss'],
|
|
21
|
+
templateUrl: './event-tools.html'
|
|
22
|
+
})
|
|
23
|
+
export class RxDBEventTools implements OnInit {
|
|
24
|
+
#rxdb = inject(RxDB);
|
|
25
|
+
|
|
26
|
+
$events = signal<RxDBEvent[]>([]);
|
|
27
|
+
|
|
28
|
+
ngOnInit(): void {
|
|
29
|
+
this.#rxdb.addEventListener(ENTITY_LOCAL_NEW_EVENT, this.handler);
|
|
30
|
+
this.#rxdb.addEventListener(ENTITY_LOCAL_CREATE_EVENT, this.handler);
|
|
31
|
+
this.#rxdb.addEventListener(ENTITY_LOCAL_UPDATE_EVENT, this.handler);
|
|
32
|
+
this.#rxdb.addEventListener(ENTITY_LOCAL_REMOVE_EVENT, this.handler);
|
|
33
|
+
this.#rxdb.addEventListener(TRANSACTION_BEGIN, this.handler);
|
|
34
|
+
this.#rxdb.addEventListener(TRANSACTION_COMMIT, this.handler);
|
|
35
|
+
this.#rxdb.addEventListener(TRANSACTION_ROLLBACK, this.handler);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
handler = (event: RxDBEvent) => {
|
|
39
|
+
this.$events.update(events => [event, ...events]);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
clear() {
|
|
43
|
+
this.$events.set([]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
|
2
|
+
import { Injectable, signal } from '@angular/core';
|
|
3
|
+
import { itemDynamicSize } from '@rdlabo/ngx-cdk-scroll-strategies';
|
|
4
|
+
|
|
5
|
+
export interface ScrollAdvancedItem {
|
|
6
|
+
id: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface DynamicSizeCache {
|
|
10
|
+
trackId: number | string;
|
|
11
|
+
itemSize: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Injectable({
|
|
15
|
+
providedIn: 'root'
|
|
16
|
+
})
|
|
17
|
+
export class ScrollAdvancedCalcService {
|
|
18
|
+
readonly cacheCalcDynamic = signal<DynamicSizeCache[]>([]);
|
|
19
|
+
readonly beforeCacheCalcDynamicSize = signal<number>(0);
|
|
20
|
+
|
|
21
|
+
changeItemsToDynamicItemSize(
|
|
22
|
+
items: ScrollAdvancedItem[],
|
|
23
|
+
dynamicSizeCache: DynamicSizeCache[],
|
|
24
|
+
virtualScroll: CdkVirtualScrollViewport | undefined
|
|
25
|
+
): itemDynamicSize[] {
|
|
26
|
+
if (virtualScroll === undefined) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
return (
|
|
30
|
+
items?.map(item => {
|
|
31
|
+
const cacheSize = dynamicSizeCache.find(cache => cache.trackId === item.id)?.itemSize;
|
|
32
|
+
const itemSize = cacheSize || 150;
|
|
33
|
+
return {
|
|
34
|
+
itemSize: Math.ceil(itemSize),
|
|
35
|
+
trackId: item.id,
|
|
36
|
+
source: cacheSize !== undefined ? 'cache' : 'temporary'
|
|
37
|
+
};
|
|
38
|
+
}) || []
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<div class="p-2">
|
|
2
|
+
<fieldset class="fieldset">
|
|
3
|
+
<legend class="fieldset-legend">Side</legend>
|
|
4
|
+
<div class="join" id="side">
|
|
5
|
+
<button
|
|
6
|
+
class="btn join-item btn-xs btn-ghost"
|
|
7
|
+
id="panel-left"
|
|
8
|
+
(click)="sideChange.emit('left')"
|
|
9
|
+
aria-hidden="true"
|
|
10
|
+
>
|
|
11
|
+
<lucide-icon [img]="PanelLeft" size="16"></lucide-icon>
|
|
12
|
+
</button>
|
|
13
|
+
<button
|
|
14
|
+
class="btn join-item btn-xs btn-ghost"
|
|
15
|
+
id="panel-bottom"
|
|
16
|
+
(click)="sideChange.emit('bottom')"
|
|
17
|
+
aria-hidden="true"
|
|
18
|
+
>
|
|
19
|
+
<lucide-icon [img]="PanelBottom" size="16"></lucide-icon>
|
|
20
|
+
</button>
|
|
21
|
+
<button
|
|
22
|
+
class="btn join-item btn-xs btn-ghost"
|
|
23
|
+
id="panel-right"
|
|
24
|
+
(click)="sideChange.emit('right')"
|
|
25
|
+
aria-hidden="true"
|
|
26
|
+
>
|
|
27
|
+
<lucide-icon [img]="PanelRight" size="16"></lucide-icon>
|
|
28
|
+
</button>
|
|
29
|
+
</div>
|
|
30
|
+
</fieldset>
|
|
31
|
+
|
|
32
|
+
<fieldset class="fieldset">
|
|
33
|
+
<legend class="fieldset-legend">tools</legend>
|
|
34
|
+
<div class="flex gap-2 p-2">
|
|
35
|
+
<button class="btn btn-xs" (click)="delete_db()">delete_db</button>
|
|
36
|
+
</div>
|
|
37
|
+
</fieldset>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<ng-template #message let-message="message">
|
|
41
|
+
<div class="toast toast-top toast-center">
|
|
42
|
+
<div class="alert alert-error">
|
|
43
|
+
<span>{{ message }}</span>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</ng-template>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
@apply flex h-full w-full flex-col overflow-hidden;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
:has(rxdb-devtools.right) :host {
|
|
6
|
+
#panel-right {
|
|
7
|
+
color: var(--color-primary);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
:has(rxdb-devtools.left) :host {
|
|
11
|
+
#panel-left {
|
|
12
|
+
color: var(--color-primary);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
:has(rxdb-devtools.bottom) :host {
|
|
16
|
+
#panel-bottom {
|
|
17
|
+
color: var(--color-primary);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { RxDB, RxDBEvent } from '@aiao/rxdb';
|
|
2
|
+
import {
|
|
3
|
+
ChangeDetectionStrategy,
|
|
4
|
+
Component,
|
|
5
|
+
inject,
|
|
6
|
+
output,
|
|
7
|
+
signal,
|
|
8
|
+
TemplateRef,
|
|
9
|
+
viewChild,
|
|
10
|
+
ViewContainerRef
|
|
11
|
+
} from '@angular/core';
|
|
12
|
+
import { LucideAngularModule, PanelBottom, PanelLeft, PanelRight } from 'lucide-angular';
|
|
13
|
+
|
|
14
|
+
interface FileEntry {
|
|
15
|
+
name: string;
|
|
16
|
+
path: string;
|
|
17
|
+
type: 'file' | 'directory';
|
|
18
|
+
handle: FileSystemDirectoryHandle | FileSystemFileHandle;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@Component({
|
|
22
|
+
selector: 'rxdb-settings',
|
|
23
|
+
imports: [LucideAngularModule],
|
|
24
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
25
|
+
styleUrls: ['./settings.scss'],
|
|
26
|
+
templateUrl: './settings.html'
|
|
27
|
+
})
|
|
28
|
+
export class RxDBSettings {
|
|
29
|
+
#viewContainerRef = inject(ViewContainerRef);
|
|
30
|
+
#rxdb = inject(RxDB);
|
|
31
|
+
|
|
32
|
+
protected readonly PanelLeft = PanelLeft;
|
|
33
|
+
protected readonly PanelBottom = PanelBottom;
|
|
34
|
+
protected readonly PanelRight = PanelRight;
|
|
35
|
+
|
|
36
|
+
messageTmp = viewChild<TemplateRef<any>>('message');
|
|
37
|
+
|
|
38
|
+
$events = signal<RxDBEvent[]>([]);
|
|
39
|
+
|
|
40
|
+
sideChange = output<'right' | 'left' | 'bottom'>();
|
|
41
|
+
|
|
42
|
+
async delete_db() {
|
|
43
|
+
try {
|
|
44
|
+
await this.#rxdb.disconnectAll();
|
|
45
|
+
} catch {
|
|
46
|
+
//
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const root = await navigator.storage.getDirectory();
|
|
51
|
+
const files = await this.listAllFiles(root, 1);
|
|
52
|
+
for (const entry of files) {
|
|
53
|
+
if (entry.type === 'file') {
|
|
54
|
+
await root.removeEntry(entry.name, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
await this.clean_indexedDB();
|
|
58
|
+
localStorage.clear();
|
|
59
|
+
// 使用 baseURI 跳转回首页,避免在子路由刷新时因服务器配置问题导致 404 fallback 到主站
|
|
60
|
+
window.location.href = document.baseURI;
|
|
61
|
+
} catch (error: Error | any) {
|
|
62
|
+
if (error && error.name === 'NoModificationAllowedError') {
|
|
63
|
+
const ref = this.#viewContainerRef.createEmbeddedView<{ message: string }>(this.messageTmp()!, {
|
|
64
|
+
message: '关闭数据库失败,请检查是否在多个浏览器 tab 中打开页面'
|
|
65
|
+
});
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
ref.destroy();
|
|
68
|
+
}, 3000);
|
|
69
|
+
}
|
|
70
|
+
console.error(error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async clean_indexedDB() {
|
|
75
|
+
const dbList = indexedDB.databases ? await indexedDB.databases() : [];
|
|
76
|
+
if (dbList.length) {
|
|
77
|
+
await Promise.all(
|
|
78
|
+
dbList.map(({ name }) => {
|
|
79
|
+
return new Promise<void>((resolve, reject) => {
|
|
80
|
+
const request = indexedDB.deleteDatabase(name!);
|
|
81
|
+
request.onsuccess = () => resolve();
|
|
82
|
+
request.onerror = () => reject(request.error);
|
|
83
|
+
request.onblocked = () => resolve();
|
|
84
|
+
});
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async listAllFiles(
|
|
91
|
+
directoryHandle: FileSystemDirectoryHandle | any,
|
|
92
|
+
maxDeep = Number.MAX_SAFE_INTEGER,
|
|
93
|
+
path = '',
|
|
94
|
+
currentDeep = 1
|
|
95
|
+
) {
|
|
96
|
+
const entries: FileEntry[] = [];
|
|
97
|
+
for await (const [name, handle] of directoryHandle) {
|
|
98
|
+
const entryPath = path ? `${path}/${name}` : name;
|
|
99
|
+
if (handle.kind === 'file') {
|
|
100
|
+
entries.push({
|
|
101
|
+
name: name,
|
|
102
|
+
path: entryPath,
|
|
103
|
+
type: 'file',
|
|
104
|
+
handle: handle
|
|
105
|
+
});
|
|
106
|
+
} else if (handle.kind === 'directory') {
|
|
107
|
+
entries.push({
|
|
108
|
+
name: name,
|
|
109
|
+
path: entryPath,
|
|
110
|
+
type: 'directory',
|
|
111
|
+
handle: handle
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (currentDeep < maxDeep) {
|
|
115
|
+
const subEntries = await this.listAllFiles(handle, maxDeep, entryPath, currentDeep + 1);
|
|
116
|
+
entries.push(...subEntries);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return entries;
|
|
121
|
+
}
|
|
122
|
+
}
|