@duplicati/ngclient 0.0.1 → 0.0.5

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.
Files changed (197) hide show
  1. package/chunk-2DCEAXBG.js +1 -0
  2. package/chunk-2WYEYW3N.js +1 -0
  3. package/chunk-43IMNGQL.js +1 -0
  4. package/chunk-63RKHEAK.js +1 -0
  5. package/chunk-6HCEXB2Z.js +1 -0
  6. package/chunk-7POJIDMZ.js +1 -0
  7. package/chunk-7PZ2UDVE.js +1 -0
  8. package/chunk-AYLGFKRR.js +1 -0
  9. package/chunk-BFEHLVAY.js +1 -0
  10. package/chunk-BHHWIMLQ.js +1 -0
  11. package/chunk-BOT7P6RM.js +1 -0
  12. package/chunk-BRNQESTL.js +1 -0
  13. package/chunk-EIZWLO2R.js +2 -0
  14. package/chunk-EJZT4TGZ.js +1 -0
  15. package/chunk-F3A4UEZK.js +1 -0
  16. package/chunk-GAT3R5MA.js +2 -0
  17. package/chunk-GBVLF3BS.js +1 -0
  18. package/chunk-GY2VPOAT.js +1 -0
  19. package/chunk-HHIFLECY.js +1 -0
  20. package/chunk-IMLDBH2T.js +1 -0
  21. package/chunk-IOM7KF2W.js +1 -0
  22. package/chunk-J6KTHSHX.js +1 -0
  23. package/chunk-JRXXG6O7.js +1 -0
  24. package/chunk-LD5EWOQM.js +1 -0
  25. package/chunk-LT6J7X37.js +1 -0
  26. package/chunk-OP5BV4PG.js +1 -0
  27. package/chunk-OX435KV6.js +1 -0
  28. package/chunk-PJNE2N54.js +1 -0
  29. package/chunk-QTQWYR6H.js +1 -0
  30. package/chunk-RCES7NCR.js +1 -0
  31. package/chunk-RL2VWNJG.js +8 -0
  32. package/chunk-RZUDMW24.js +1 -0
  33. package/chunk-S2HNK7TK.js +1 -0
  34. package/chunk-S2YPSAFD.js +1 -0
  35. package/chunk-TGC6GY4N.js +1 -0
  36. package/chunk-UJYJZACJ.js +1 -0
  37. package/chunk-VAAR5GXC.js +1 -0
  38. package/chunk-VF6HT3GY.js +1 -0
  39. package/chunk-XBN37RRV.js +1 -0
  40. package/chunk-YC5LTBFM.js +1 -0
  41. package/index.html +15 -0
  42. package/main-TLCKWB2W.js +1 -0
  43. package/package.json +3 -40
  44. package/sparkle-ui-assets/fonts/InterTight-VariableFont_wght.ttf +0 -0
  45. package/sparkle-ui-assets/fonts/InterTight-VariableFont_wght.woff2 +0 -0
  46. package/styles-6W7ZCKNM.css +1 -0
  47. package/.dockerignore +0 -13
  48. package/.github/workflows/npm-publish.yml +0 -59
  49. package/.prettierignore +0 -13
  50. package/.prettierrc +0 -12
  51. package/.vscode/extensions.json +0 -4
  52. package/.vscode/launch.json +0 -13
  53. package/.vscode/settings.json +0 -8
  54. package/.vscode/tasks.json +0 -24
  55. package/Dockerfile +0 -38
  56. package/LICENSE +0 -21
  57. package/README.md +0 -27
  58. package/angular.json +0 -98
  59. package/bun.lockb +0 -0
  60. package/openapi-ts.config.ts +0 -13
  61. package/scripts/generate-package-json.ts +0 -15
  62. package/src/app/about/about.component.html +0 -15
  63. package/src/app/about/about.component.scss +0 -19
  64. package/src/app/about/about.component.ts +0 -13
  65. package/src/app/about/changelog/changelog.component.html +0 -3
  66. package/src/app/about/changelog/changelog.component.scss +0 -7
  67. package/src/app/about/changelog/changelog.component.ts +0 -18
  68. package/src/app/about/general/general.component.html +0 -9
  69. package/src/app/about/general/general.component.scss +0 -23
  70. package/src/app/about/general/general.component.ts +0 -30
  71. package/src/app/about/libraries/libraries.component.html +0 -3
  72. package/src/app/about/libraries/libraries.component.scss +0 -7
  73. package/src/app/about/libraries/libraries.component.ts +0 -18
  74. package/src/app/about/logs/logs.component.html +0 -5
  75. package/src/app/about/logs/logs.component.scss +0 -7
  76. package/src/app/about/logs/logs.component.ts +0 -18
  77. package/src/app/about/system-info/system-info.component.html +0 -3
  78. package/src/app/about/system-info/system-info.component.scss +0 -7
  79. package/src/app/about/system-info/system-info.component.ts +0 -17
  80. package/src/app/add-backup/add-backup.component.html +0 -29
  81. package/src/app/add-backup/add-backup.component.scss +0 -38
  82. package/src/app/add-backup/add-backup.component.ts +0 -14
  83. package/src/app/app.component.ts +0 -35
  84. package/src/app/app.config.ts +0 -23
  85. package/src/app/app.routes.ts +0 -121
  86. package/src/app/core/components/file-tree/file-tree.component.html +0 -87
  87. package/src/app/core/components/file-tree/file-tree.component.scss +0 -80
  88. package/src/app/core/components/file-tree/file-tree.component.ts +0 -485
  89. package/src/app/core/components/logo/logo.component.html +0 -22
  90. package/src/app/core/components/logo/logo.component.scss +0 -12
  91. package/src/app/core/components/logo/logo.component.ts +0 -11
  92. package/src/app/core/components/status-bar/status-bar.component.html +0 -38
  93. package/src/app/core/components/status-bar/status-bar.component.scss +0 -27
  94. package/src/app/core/components/status-bar/status-bar.component.ts +0 -33
  95. package/src/app/core/components/status-bar/status-bar.state.ts +0 -182
  96. package/src/app/core/components/toggle-card/toggle-card.component.html +0 -11
  97. package/src/app/core/components/toggle-card/toggle-card.component.scss +0 -49
  98. package/src/app/core/components/toggle-card/toggle-card.component.ts +0 -28
  99. package/src/app/core/interceptors/http.interceptor.ts +0 -69
  100. package/src/app/core/interceptors/websocket.interceptor.ts +0 -66
  101. package/src/app/core/openapi/core/ApiError.ts +0 -21
  102. package/src/app/core/openapi/core/ApiRequestOptions.ts +0 -21
  103. package/src/app/core/openapi/core/ApiResult.ts +0 -7
  104. package/src/app/core/openapi/core/OpenAPI.ts +0 -55
  105. package/src/app/core/openapi/core/request.ts +0 -337
  106. package/src/app/core/openapi/index.ts +0 -6
  107. package/src/app/core/openapi/schemas.gen.ts +0 -3611
  108. package/src/app/core/openapi/services.gen.ts +0 -1460
  109. package/src/app/core/openapi/types.gen.ts +0 -1510
  110. package/src/app/core/pipes/duration.pipe.ts +0 -27
  111. package/src/app/core/pipes/relative-time.pipe.ts +0 -14
  112. package/src/app/core/providers/dayjs.ts +0 -16
  113. package/src/app/core/services/dict.en.ts +0 -0
  114. package/src/app/core/services/dictionary.service.ts +0 -12
  115. package/src/app/core/services/localstorage.token.ts +0 -82
  116. package/src/app/core/services/password-generator.service.ts +0 -39
  117. package/src/app/core/services/relay-websocket.service.ts +0 -268
  118. package/src/app/core/services/timespan-literals.service.ts +0 -25
  119. package/src/app/core/states/app-auth.state.ts +0 -87
  120. package/src/app/core/states/backups.state.ts +0 -71
  121. package/src/app/core/states/relayconfig.state.ts +0 -23
  122. package/src/app/core/states/sysinfo.state.ts +0 -31
  123. package/src/app/core/states/tasks.state.ts +0 -34
  124. package/src/app/core/types/subscribed.ts +0 -7
  125. package/src/app/core/validators/custom.validators.ts +0 -120
  126. package/src/app/create-edit-backup/create-edit-backup.component.html +0 -28
  127. package/src/app/create-edit-backup/create-edit-backup.component.scss +0 -15
  128. package/src/app/create-edit-backup/create-edit-backup.component.ts +0 -33
  129. package/src/app/create-edit-backup/create-edit-backup.state.ts +0 -582
  130. package/src/app/create-edit-backup/destination/destination.component.html +0 -367
  131. package/src/app/create-edit-backup/destination/destination.component.scss +0 -85
  132. package/src/app/create-edit-backup/destination/destination.component.ts +0 -249
  133. package/src/app/create-edit-backup/destination/destination.config.ts +0 -103
  134. package/src/app/create-edit-backup/destination/destination.mapper.ts +0 -177
  135. package/src/app/create-edit-backup/general/general.component.html +0 -108
  136. package/src/app/create-edit-backup/general/general.component.scss +0 -34
  137. package/src/app/create-edit-backup/general/general.component.ts +0 -136
  138. package/src/app/create-edit-backup/options/options.component.html +0 -95
  139. package/src/app/create-edit-backup/options/options.component.scss +0 -42
  140. package/src/app/create-edit-backup/options/options.component.ts +0 -113
  141. package/src/app/create-edit-backup/schedule/schedule.component.html +0 -102
  142. package/src/app/create-edit-backup/schedule/schedule.component.scss +0 -38
  143. package/src/app/create-edit-backup/schedule/schedule.component.ts +0 -137
  144. package/src/app/create-edit-backup/source-data/source-data.component.html +0 -93
  145. package/src/app/create-edit-backup/source-data/source-data.component.scss +0 -41
  146. package/src/app/create-edit-backup/source-data/source-data.component.ts +0 -114
  147. package/src/app/home/backup.const.ts +0 -167
  148. package/src/app/home/home.component.html +0 -98
  149. package/src/app/home/home.component.scss +0 -88
  150. package/src/app/home/home.component.ts +0 -54
  151. package/src/app/layout/layout.component.html +0 -64
  152. package/src/app/layout/layout.component.scss +0 -24
  153. package/src/app/layout/layout.component.ts +0 -51
  154. package/src/app/layout/layout.state.ts +0 -70
  155. package/src/app/login/login.component.html +0 -12
  156. package/src/app/login/login.component.scss +0 -25
  157. package/src/app/login/login.component.ts +0 -34
  158. package/src/app/logout/logout.component.html +0 -1
  159. package/src/app/logout/logout.component.scss +0 -3
  160. package/src/app/logout/logout.component.ts +0 -18
  161. package/src/app/restore/restore.component.html +0 -56
  162. package/src/app/restore/restore.component.scss +0 -37
  163. package/src/app/restore/restore.component.ts +0 -55
  164. package/src/app/restore-flow/options/options.component.html +0 -57
  165. package/src/app/restore-flow/options/options.component.scss +0 -16
  166. package/src/app/restore-flow/options/options.component.ts +0 -58
  167. package/src/app/restore-flow/restore-flow.component.html +0 -14
  168. package/src/app/restore-flow/restore-flow.component.scss +0 -15
  169. package/src/app/restore-flow/restore-flow.component.ts +0 -23
  170. package/src/app/restore-flow/restore-flow.state.ts +0 -120
  171. package/src/app/restore-flow/select-files/select-files.component.html +0 -42
  172. package/src/app/restore-flow/select-files/select-files.component.scss +0 -16
  173. package/src/app/restore-flow/select-files/select-files.component.ts +0 -129
  174. package/src/app/settings/settings.component.html +0 -256
  175. package/src/app/settings/settings.component.scss +0 -106
  176. package/src/app/settings/settings.component.ts +0 -475
  177. package/src/environments/environment-token.ts +0 -4
  178. package/src/environments/environment.prod.ts +0 -6
  179. package/src/environments/environment.ts +0 -6
  180. package/src/index.html +0 -15
  181. package/src/main.ts +0 -6
  182. package/src/proxy.conf.mjs +0 -6
  183. package/src/styles.scss +0 -41
  184. package/tsconfig.app.json +0 -15
  185. package/tsconfig.json +0 -30
  186. /package/{src/assets → assets}/duplicati-logo.png +0 -0
  187. /package/{src/assets → assets}/duplicati-logo.svg +0 -0
  188. /package/{src/assets → assets}/favicon.ico +0 -0
  189. /package/{src/assets → assets}/images/backup.png +0 -0
  190. /package/{src/assets → assets}/images/dark-theme.png +0 -0
  191. /package/{src/assets → assets}/images/folder.png +0 -0
  192. /package/{src/assets → assets}/images/light-theme.png +0 -0
  193. /package/{src/assets → assets}/images/new-backup.png +0 -0
  194. /package/{src/assets → assets}/images/restore.png +0 -0
  195. /package/{src/assets → assets}/images/system-theme.png +0 -0
  196. /package/{src/assets → assets}/spk.css +0 -0
  197. /package/{src/assets → assets}/spk.woff2 +0 -0
@@ -1,87 +0,0 @@
1
- @if (!multiSelect()) {
2
- <spk-button-group>
3
- <button
4
- type="button"
5
- [class.active]="pathDiscoveryMethod() === 'browse'"
6
- (click)="pathDiscoveryMethod.set('browse')">
7
- Browser path
8
- </button>
9
-
10
- <button type="button" [class.active]="pathDiscoveryMethod() === 'path'" (click)="pathDiscoveryMethod.set('path')">
11
- Manually type path
12
- </button>
13
- </spk-button-group>
14
- }
15
-
16
- <!-- <spk-form-field>
17
- <label for="search">Search</label>
18
- <input type="text" id="search" [(ngModel)]="treeSearchQuery" />
19
- </spk-form-field> -->
20
-
21
- <div class="section" [class.hidden]="pathDiscoveryMethod() !== 'browse'">
22
- <ng-content select="label" ngProjectAs="label"></ng-content>
23
-
24
- <div class="browse-files" #treeContainer>
25
- <ng-template #recursiveTree let-data>
26
- <spk-list [style.padding-left.rem]="data.level * 0.5">
27
- @for (node of data.children; track node.id) {
28
- @if ((node.hidden && showHiddenFiles()) || !node.hidden) {
29
- <div
30
- spk-action-item
31
- #treeNode
32
- [class.active]="node.isSelected"
33
- [class.open]="node.children.length"
34
- [class.usually-hidden]="node.hidden"
35
- (click)="node.id !== '/' && toggleNode($event, node.id, node)">
36
- <div class="icon">
37
- @if (node.id !== '/') {
38
- @if (node.children.length) {
39
- <spk-icon class="spk-primary">folder-open</spk-icon>
40
- } @else if (node.cls === 'file') {
41
- <spk-icon>file</spk-icon>
42
- } @else {
43
- <spk-icon class="spk-primary">folder</spk-icon>
44
- }
45
- } @else {
46
- <spk-icon class="spk-primary">desktop-tower</spk-icon>
47
- }
48
- </div>
49
-
50
- <div class="text" (click)="node.id !== '/' && toggleSelectedNode($event, node)">
51
- {{ node.text }}
52
- </div>
53
- </div>
54
-
55
- @if (node.children.length > 0) {
56
- <ng-container
57
- *ngTemplateOutlet="
58
- recursiveTree;
59
- context: { $implicit: { children: node.children, level: data.level + 1 } }
60
- "></ng-container>
61
- }
62
- }
63
- }
64
- </spk-list>
65
- </ng-template>
66
-
67
- <ng-container
68
- *ngTemplateOutlet="recursiveTree; context: { $implicit: { children: treeStructure(), level: 0 } }"></ng-container>
69
- </div>
70
-
71
- <spk-toggle
72
- [class.active]="showHiddenFiles()"
73
- class="primary raised browse-files-toggle"
74
- (click)="showHiddenFiles.set(!showHiddenFiles())">
75
- Show hidden {{ showFiles() ? 'files' : 'folders' }}
76
- </spk-toggle>
77
- </div>
78
-
79
- <div #formRef>
80
- <spk-form-field [class.hidden]="pathDiscoveryMethod() !== 'path'">
81
- <ng-content select="label" ngProjectAs="label"></ng-content>
82
- <ng-content select="input" ngProjectAs="input"></ng-content>
83
- </spk-form-field>
84
-
85
- <!-- <pre>{{ selectedPaths() | json }}</pre> -->
86
- <!-- <pre>{{ currentPath() }}</pre> -->
87
- </div>
@@ -1,80 +0,0 @@
1
- @use '@sparkle-ui/core/styles/helpers' as *;
2
-
3
- :host {
4
- display: flex;
5
- flex-direction: column;
6
- gap: p2r(16);
7
-
8
- &.disabled {
9
- opacity: 0.5;
10
- user-select: none;
11
- pointer-events: none;
12
- position: relative;
13
-
14
- &:before {
15
- content: '';
16
- position: absolute;
17
- inset: 0;
18
- background: transparent;
19
- }
20
- }
21
- }
22
-
23
- .section {
24
- display: flex;
25
- flex-direction: column;
26
- gap: p2r(8);
27
- }
28
-
29
- .browse-files {
30
- display: flex;
31
- gap: p2r(8);
32
- padding: p2r(8 16);
33
- height: p2r(200);
34
- border: 1px solid var(--base-level-40);
35
- border-radius: var(--shape-3);
36
- overflow: auto;
37
- resize: vertical;
38
-
39
- spk-list {
40
- --list-active-bg: var(--base-level-30);
41
- gap: 0;
42
- padding: 0;
43
- height: min-content;
44
- min-width: min-content;
45
-
46
- [spk-action-item] {
47
- padding: 0;
48
- gap: p2r(4);
49
-
50
- &.usually-hidden {
51
- opacity: 0.6;
52
- }
53
-
54
- .text {
55
- flex: 1 0;
56
- height: 100%;
57
- width: max-content;
58
- display: flex;
59
- align-items: center;
60
- }
61
-
62
- .icon {
63
- width: p2r(24);
64
- height: p2r(24);
65
- display: flex;
66
- align-items: center;
67
- justify-content: center;
68
- cursor: pointer;
69
- }
70
- }
71
- }
72
- }
73
-
74
- .browse-files-toggle {
75
- color: var(--base-level-60R);
76
- }
77
-
78
- .hidden {
79
- display: none;
80
- }
@@ -1,485 +0,0 @@
1
- import { JsonPipe, NgTemplateOutlet } from '@angular/common';
2
- import {
3
- ChangeDetectionStrategy,
4
- Component,
5
- computed,
6
- effect,
7
- ElementRef,
8
- inject,
9
- input,
10
- output,
11
- signal,
12
- viewChild,
13
- } from '@angular/core';
14
- import { FormsModule } from '@angular/forms';
15
- import {
16
- SparkleButtonGroupComponent,
17
- SparkleCheckboxComponent,
18
- SparkleFormFieldComponent,
19
- SparkleIconComponent,
20
- SparkleListComponent,
21
- SparkleToggleComponent,
22
- } from '@sparkle-ui/core';
23
- import { finalize, forkJoin, Observable } from 'rxjs';
24
- import { DuplicatiServerService, GetApiV1BackupByIdFilesData, TreeNodeDto } from '../../openapi';
25
-
26
- type TreeNode = TreeNodeDto & {
27
- isSelected: boolean;
28
- parentPath: string;
29
- };
30
-
31
- type FileTreeNode = TreeNode & {
32
- children: FileTreeNode[];
33
- };
34
-
35
- type InputValueChangedEvent = CustomEvent<{ value: string }>;
36
-
37
- interface CustomEventMap {
38
- inputValueChanged: InputValueChangedEvent;
39
- }
40
-
41
- declare global {
42
- interface HTMLInputElement {
43
- // Extend HTMLElement to cover input elements
44
- addEventListener<K extends keyof CustomEventMap>(
45
- type: K,
46
- listener: (this: HTMLElement, ev: CustomEventMap[K]) => any,
47
- options?: boolean | AddEventListenerOptions
48
- ): void;
49
- }
50
- }
51
-
52
- type BackupSettings = {
53
- id: string;
54
- time: string;
55
- };
56
-
57
- // TODO
58
- // - Scroll to newly selected node by path
59
- // - When rootPath get all the levels and batch them at the root level for better usability
60
-
61
- @Component({
62
- selector: 'app-file-tree',
63
- standalone: true,
64
- imports: [
65
- SparkleButtonGroupComponent,
66
- SparkleToggleComponent,
67
- SparkleIconComponent,
68
- SparkleFormFieldComponent,
69
- SparkleListComponent,
70
- SparkleCheckboxComponent,
71
- NgTemplateOutlet,
72
- JsonPipe,
73
- FormsModule,
74
- ],
75
- templateUrl: './file-tree.component.html',
76
- styleUrl: './file-tree.component.scss',
77
- changeDetection: ChangeDetectionStrategy.OnPush,
78
- host: {
79
- '[class.disabled]': 'disabled()',
80
- },
81
- })
82
- export default class FileTreeComponent {
83
- #dupServer = inject(DuplicatiServerService);
84
-
85
- multiSelect = input(false);
86
- disabled = input(false);
87
- showFiles = input(false);
88
- startingPath = input<string | null>(null);
89
- rootPath = input<string | undefined>(undefined);
90
- backupSettings = input<BackupSettings | null>(null);
91
- includes = output<string[]>();
92
- excludes = output<string[]>();
93
-
94
- treeContainerRef = viewChild<ElementRef<HTMLDivElement>>('treeContainer');
95
- formRef = viewChild<ElementRef<HTMLFormElement>>('formRef');
96
- pathDiscoveryMethod = signal<'browse' | 'path'>('browse');
97
- isLoading = signal(false);
98
- showHiddenFiles = signal(false);
99
- currentPath = signal<string>('/');
100
- #inputRef = signal<HTMLInputElement | null>(null);
101
- treeSearchQuery = signal<string>('');
102
- treeNodes = signal<TreeNode[]>([]);
103
-
104
- isByBackupSettings = computed(() => this.rootPath() && this.backupSettings()?.id && this.backupSettings()?.time);
105
- searchableTreeNodes = computed<TreeNode[]>(() => {
106
- const query = this.treeSearchQuery();
107
- return query
108
- ? this.treeNodes().filter((x) => x.text?.toLowerCase().includes(query.toLowerCase()))
109
- : this.treeNodes();
110
- });
111
- treeStructure = computed<FileTreeNode[]>(() => {
112
- const nodes = this.searchableTreeNodes();
113
- const nodeMap = new Map<string, FileTreeNode>();
114
- const rootPath = this.rootPath();
115
-
116
- const root: FileTreeNode = rootPath
117
- ? {
118
- id: rootPath,
119
- resolvedpath: rootPath,
120
- text: rootPath?.replaceAll('/', ''),
121
- parentPath: '',
122
- children: [],
123
- isSelected: false,
124
- cls: 'folder',
125
- }
126
- : {
127
- id: '/',
128
- resolvedpath: '/',
129
- text: 'Computer',
130
- parentPath: '',
131
- children: [],
132
- isSelected: false,
133
- };
134
-
135
- nodeMap.set('/', root);
136
-
137
- for (const node of nodes) {
138
- const itemPath = (node.id as string).replace(rootPath ?? '', '');
139
- const pathParts = itemPath.split('/').filter(Boolean);
140
-
141
- let currentPath = '';
142
- let parentNode = root;
143
-
144
- for (const part of pathParts) {
145
- currentPath += '/' + part;
146
-
147
- if (!nodeMap.has(currentPath)) {
148
- const newNode: FileTreeNode = {
149
- id: node.id,
150
- isSelected: node.isSelected,
151
- parentPath: currentPath,
152
- resolvedpath: node.id?.startsWith('%') ? node.resolvedpath + '/' : currentPath,
153
- hidden: node.hidden,
154
- text: node.text,
155
- cls: node.cls,
156
- children: [],
157
- };
158
- nodeMap.set(currentPath, newNode);
159
- parentNode.children.push(newNode);
160
- }
161
-
162
- parentNode = nodeMap.get(currentPath)!;
163
- }
164
- }
165
-
166
- return [root];
167
- });
168
-
169
- pathDiscoveryMethodEffect = effect(
170
- () => {
171
- const disvoeryMethod = this.pathDiscoveryMethod();
172
- const input = this.#inputRef();
173
-
174
- if (disvoeryMethod === 'browse' && input) {
175
- if (this.multiSelect()) {
176
- const arr = input.value.split('\0');
177
- this.treeNodes.update((y) =>
178
- y.map((z) => ({ ...z, isSelected: typeof z.id === 'string' && arr.includes(z.id) }))
179
- );
180
- } else {
181
- this.treeNodes.update((y) => y.map((z) => ({ ...z, isSelected: z.id === input.value })));
182
- }
183
- }
184
- },
185
- {
186
- allowSignalWrites: true,
187
- }
188
- );
189
-
190
- #scrollToFirstSelectedNode() {
191
- const selectedNodes = this.treeContainerRef()?.nativeElement.querySelectorAll('.active');
192
-
193
- if (selectedNodes && selectedNodes.length > 0) {
194
- selectedNodes[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
195
- }
196
- }
197
-
198
- currentPathEffect = effect(
199
- () => {
200
- const input = this.#inputRef();
201
-
202
- if (input) {
203
- input.value = this.currentPath();
204
- input.dispatchEvent(new Event('input'));
205
- }
206
- },
207
- {
208
- allowSignalWrites: true,
209
- }
210
- );
211
-
212
- selectedPathsEffect = effect(
213
- () => {
214
- if (this.multiSelect()) {
215
- const selectedNodes = this.treeNodes().filter((node) => node.isSelected);
216
- const excludedNodes = this.treeNodes().filter(
217
- (node) => !node.isSelected && selectedNodes.some((selected) => node.id?.startsWith(selected.id as string))
218
- );
219
-
220
- const selectedMapped = selectedNodes
221
- .filter(
222
- (node) =>
223
- !selectedNodes.some(
224
- (otherNode) => node.id !== otherNode.id && node.id?.startsWith(otherNode.id as string)
225
- )
226
- )
227
- .map((node) => node.id as string);
228
-
229
- const excludedMapped = excludedNodes
230
- .filter(
231
- (node) =>
232
- !excludedNodes.some(
233
- (otherNode) => node.id !== otherNode.id && node.id?.startsWith(otherNode.id as string)
234
- )
235
- )
236
- .map((node) => `-${node.id as string}`);
237
-
238
- this.excludes.emit(excludedMapped);
239
-
240
- const newPath = [...selectedMapped, ...excludedMapped] as string[];
241
-
242
- this.currentPath.set(newPath.join('\0'));
243
- } else {
244
- const selectedNode = this.treeNodes().find((node) => node.isSelected);
245
-
246
- const resolvedPath = selectedNode?.resolvedpath as string;
247
-
248
- this.currentPath.set(selectedNode?.id ?? '');
249
- }
250
- },
251
- {
252
- allowSignalWrites: true,
253
- }
254
- );
255
-
256
- inputValueChangeAbortController: AbortController | null = null;
257
- inputRefEffect = effect(
258
- () => {
259
- const input = this.formRef()?.nativeElement?.querySelector('input');
260
-
261
- if (!input) return;
262
-
263
- this.#createCustomInputEventListener(input);
264
-
265
- if (this.inputValueChangeAbortController) {
266
- this.inputValueChangeAbortController.abort();
267
- }
268
-
269
- this.inputValueChangeAbortController = new AbortController();
270
-
271
- input.addEventListener(
272
- 'inputValueChanged',
273
- (event: InputValueChangedEvent) => {
274
- this.currentPath.set(event.detail.value);
275
- },
276
- { signal: this.inputValueChangeAbortController.signal }
277
- );
278
-
279
- this.#inputRef.set(input);
280
- input.autocomplete = 'off';
281
- },
282
- { allowSignalWrites: true }
283
- );
284
-
285
- ngOnInit() {
286
- const startingPath = this.startingPath();
287
-
288
- if (startingPath) {
289
- this.currentPath.set(startingPath);
290
- this.#fetchPathSegmentsRecursively(startingPath);
291
- } else {
292
- this.#getPath(null, this.rootPath());
293
- }
294
- }
295
-
296
- toggleSelectedNode($event: Event, node: FileTreeNode) {
297
- $event.stopPropagation();
298
-
299
- if (this.multiSelect()) {
300
- this.treeNodes.update((y) =>
301
- y.map((z) => {
302
- if (z.id?.startsWith(node.id as string)) {
303
- return { ...z, isSelected: !node.isSelected };
304
- }
305
- return z;
306
- })
307
- );
308
- } else {
309
- this.treeNodes.update((y) =>
310
- y.map((z) => {
311
- if (z.id === (node.id as string)) {
312
- return { ...z, isSelected: !node.isSelected };
313
- } else {
314
- return { ...z, isSelected: false };
315
- }
316
- })
317
- );
318
- }
319
- }
320
-
321
- toggleNode($event: Event, path: string, node: FileTreeNode | null = null) {
322
- $event.stopPropagation();
323
-
324
- if (node?.children.length) {
325
- this.treeNodes.update((y) => y.filter((z) => !z.id?.startsWith(node.id as string) || z.id === node.id));
326
- } else {
327
- this.#getPath(node, path);
328
- }
329
- }
330
-
331
- #createCustomInputEventListener(input: HTMLInputElement) {
332
- Object.defineProperty(input, 'value', {
333
- configurable: true,
334
- get() {
335
- return Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')!.get!.call(this);
336
- },
337
- set(newVal) {
338
- Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')!.set!.call(this, newVal);
339
-
340
- this.dispatchEvent(
341
- new CustomEvent('inputValueChanged', {
342
- bubbles: true,
343
- cancelable: true,
344
- detail: {
345
- value: newVal,
346
- },
347
- })
348
- );
349
-
350
- return newVal;
351
- },
352
- });
353
-
354
- return input;
355
- }
356
-
357
- #getFilesystemPath(path: string) {
358
- return this.#dupServer.postApiV1Filesystem({
359
- showHidden: true,
360
- requestBody: {
361
- path,
362
- },
363
- onlyFolders: !this.showFiles(),
364
- });
365
- }
366
-
367
- #getBackupFiles(path: string | null) {
368
- const backupSettings = this.backupSettings()!;
369
- const params: GetApiV1BackupByIdFilesData = {
370
- id: backupSettings.id,
371
- time: backupSettings.time,
372
- prefixOnly: false,
373
- folderContents: true,
374
- };
375
-
376
- if (path) {
377
- params.filter = '@' + path;
378
- }
379
-
380
- return this.#dupServer.getApiV1BackupByIdFiles(params);
381
- }
382
-
383
- #getFilePath(path: string) {
384
- return this.isByBackupSettings() ? this.#getBackupFiles(path) : this.#getFilesystemPath(path);
385
- }
386
-
387
- #fetchPathSegmentsRecursively(path: string) {
388
- const deselectedPaths = path
389
- .split('\0')
390
- .filter((x) => x.startsWith('-'))
391
- .map((x) => x.slice(1, -1));
392
-
393
- const pathArr = path
394
- .split('\0')
395
- .filter((x) => !x.startsWith('-'))
396
- .filter(Boolean);
397
- const segmentArr = pathArr.map((x) => x.split('/').filter(Boolean));
398
-
399
- let urlPieces: string[] = [this.rootPath() ?? '/'];
400
-
401
- segmentArr.forEach((segments) => {
402
- segments.forEach((_, index) => {
403
- const urlPiece = '/' + segments.slice(0, index + 1).join('/');
404
- if (!urlPieces.includes(urlPiece)) {
405
- urlPieces.push(urlPiece);
406
- }
407
- });
408
- });
409
-
410
- this.isLoading.set(true);
411
-
412
- forkJoin([...urlPieces.map((urlPiece) => this.#getFilePath(urlPiece === '/' ? urlPiece : urlPiece))])
413
- .pipe(finalize(() => this.isLoading.set(false)))
414
- .subscribe({
415
- next: (results) => {
416
- this.treeNodes.update((y) => {
417
- const allNewNodes = results.flatMap((x) => {
418
- return (x as any).map((z: any) => {
419
- const path = z.id;
420
- const parentPath = path && path.split('/').filter(Boolean).slice(0, -1).join('/');
421
- const _pathWithoutTrailingSlash = path && path.split('/').filter(Boolean).join('/');
422
- const pathWithoutTrailingSlash = '/' + _pathWithoutTrailingSlash;
423
- const found =
424
- pathArr.findIndex((x) => {
425
- return this.multiSelect() ? pathWithoutTrailingSlash.startsWith(x) : pathWithoutTrailingSlash === x;
426
- }) > -1;
427
-
428
- const hi = pathArr.findIndex((x) => x === path) > -1;
429
- const isDeselected = deselectedPaths.findIndex((x) => x === pathWithoutTrailingSlash) > -1;
430
-
431
- return {
432
- ...z,
433
- parentPath: parentPath.startsWith('%') ? parentPath : '/' + parentPath,
434
- isSelected: (found && !isDeselected) || hi, // TODO - Use deselected paths to disable
435
- };
436
- });
437
- });
438
-
439
- const arrayUniqueByKey = [
440
- ...new Map([...y, ...allNewNodes].map((item) => [item.resolvedpath ?? item.id, item])).values(),
441
- ];
442
-
443
- return arrayUniqueByKey as TreeNode[];
444
- });
445
- },
446
- });
447
- }
448
-
449
- #getPath(node: FileTreeNode | null = null, newPath = '/') {
450
- this.isLoading.set(true);
451
-
452
- (this.#getFilePath(newPath) as Observable<any>).pipe(finalize(() => this.isLoading.set(false))).subscribe({
453
- next: (x) => {
454
- const alignDataArray = this.isByBackupSettings()
455
- ? x['Files'].map((y: { Path: string; Size: any }) => {
456
- return {
457
- text: y.Path.split('/')
458
- .filter((part) => part !== '')
459
- .pop(),
460
- id: y.Path,
461
- cls: y.Path.endsWith('/') ? 'folder' : 'file',
462
- leaf: node !== null,
463
- resolvedpath: y.Path,
464
- hidden: false,
465
- };
466
- })
467
- : x;
468
-
469
- this.treeNodes.update((y) => {
470
- const newArray = alignDataArray.map((z: any) => ({
471
- ...z,
472
- parentPath: newPath,
473
- isSelected: (this.multiSelect() && node?.isSelected) ?? false,
474
- }));
475
-
476
- const arrayUniqueByKey = [
477
- ...new Map([...y, ...newArray].map((item) => [item.resolvedpath ?? item.id, item])).values(),
478
- ];
479
-
480
- return arrayUniqueByKey as TreeNode[];
481
- });
482
- },
483
- });
484
- }
485
- }