orcas-angular 1.0.4 → 1.0.6

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 (37) hide show
  1. package/fesm2022/orcas-angular.mjs +1608 -0
  2. package/fesm2022/orcas-angular.mjs.map +1 -0
  3. package/package.json +39 -36
  4. package/types/orcas-angular.d.ts +460 -0
  5. package/.claude/settings.local.json +0 -8
  6. package/async/async.ts +0 -16
  7. package/async/cancellation-token.ts +0 -90
  8. package/dev/console-hook.ts +0 -25
  9. package/dev/debug.service.ts.example +0 -29
  10. package/framework/services-init.ts +0 -25
  11. package/index.ts +0 -25
  12. package/localization/localization.interface.ts +0 -18
  13. package/localization/localization.service.ts +0 -131
  14. package/localization/localize.pipe.ts +0 -30
  15. package/log/echo-provider.ts +0 -27
  16. package/log/echo.ts +0 -635
  17. package/log/index.ts +0 -6
  18. package/log/log-systems.ts +0 -20
  19. package/navigation/back-on-click.directive.ts +0 -19
  20. package/navigation/index.ts +0 -3
  21. package/navigation/navigation-stack.service.ts +0 -33
  22. package/ng-package.json +0 -7
  23. package/storage/capacitor-files.service.ts +0 -38
  24. package/storage/file-box.service.ts +0 -112
  25. package/storage/files.ts +0 -42
  26. package/storage/key-signals.ts +0 -49
  27. package/storage/local-storage-files.service.ts +0 -49
  28. package/storage/settings-signals.service.ts +0 -24
  29. package/storage/settings.service.ts +0 -24
  30. package/storage/tauri-files.service.ts +0 -69
  31. package/theme/theme.service.ts +0 -33
  32. package/tsconfig.lib.json +0 -11
  33. package/ui/context-menu/context-button.component.ts +0 -55
  34. package/ui/context-menu/context-header.component.ts +0 -15
  35. package/ui/context-menu/context-menu-trigger.directive.ts +0 -26
  36. package/ui/context-menu/context-menu.component.ts +0 -95
  37. package/ui/context-menu/index.ts +0 -4
@@ -1,33 +0,0 @@
1
- import {Injectable} from '@angular/core';
2
- import {Location} from '@angular/common';
3
- import {Router, NavigationEnd} from '@angular/router';
4
-
5
- @Injectable({
6
- providedIn: 'root'
7
- })
8
- export class NavigationStackService {
9
- private history: string[] = [];
10
-
11
- constructor(private router: Router, private location: Location) {
12
- this.router.events.subscribe((event: any) => {
13
- if (event instanceof NavigationEnd)
14
- this.history.push(event.urlAfterRedirects);
15
- });
16
- }
17
-
18
- public goBack(): void {
19
- this.history.pop();
20
- if (this.history.length > 0)
21
- this.location.back();
22
- else
23
- this.router.navigateByUrl("/").then();
24
- this.history.pop();
25
- }
26
-
27
- public getBack(): string {
28
- if (this.history.length > 1)
29
- return this.history[this.history.length - 2];
30
-
31
- return '';
32
- }
33
- }
package/ng-package.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "$schema": "./node_modules/ng-packagr/ng-package.schema.json",
3
- "lib": {
4
- "entryFile": "index.ts"
5
- },
6
- "dest": "dist"
7
- }
@@ -1,38 +0,0 @@
1
- import { Injectable } from '@angular/core';
2
- import { FilesService } from './files';
3
-
4
- @Injectable()
5
- export class CapacitorFilesService extends FilesService {
6
- static isSupported(): boolean {
7
- // Capacitor is not yet implemented, so returning false
8
- return false;
9
- }
10
-
11
- async init() {
12
- throw new Error('CapacitorFilesService not implemented');
13
- }
14
-
15
- async joinStoragePath(filePath: string): Promise<string | null> {
16
- return null;
17
- }
18
-
19
- async hasInStorage(filePath: string): Promise<boolean> {
20
- throw new Error('Method not implemented.');
21
- }
22
-
23
- async readFromStorage(filePath: string): Promise<string> {
24
- throw new Error('Method not implemented.');
25
- }
26
-
27
- async writeToStorage(filePath: string, data: string): Promise<void> {
28
- throw new Error('Method not implemented.');
29
- }
30
-
31
- async hasInProject(filePath: string): Promise<boolean> {
32
- throw new Error('Method not implemented.');
33
- }
34
-
35
- async readFromProject(filePath: string): Promise<string> {
36
- throw new Error('Method not implemented.');
37
- }
38
- }
@@ -1,112 +0,0 @@
1
- import { computed, inject, Injectable, signal } from '@angular/core';
2
- import { FilesService } from './files';
3
- import { Async } from "../async/async";
4
-
5
- enum Status {
6
- NotInitialized,
7
- Loading,
8
- Idle,
9
- Saving
10
- }
11
-
12
- interface BoxData {
13
- [key: string]: any;
14
- }
15
-
16
- @Injectable({
17
- providedIn: 'root'
18
- })
19
- export class FileBoxService {
20
- private files: FilesService = inject(FilesService);
21
- private status: Status = Status.NotInitialized;
22
- private path: string = '';
23
-
24
- private saveEnqueued: boolean = false;
25
-
26
- private $dataWritable = signal<BoxData>({});
27
- public $data = computed(() => {
28
- if (this.status === Status.NotInitialized)
29
- console.error('Service is not initialized.');
30
- else if (this.status === Status.Loading)
31
- console.error('Service is loading.');
32
-
33
- return this.$dataWritable();
34
- });
35
-
36
- async init(path: string) {
37
- if (this.status !== Status.NotInitialized) {
38
- console.error('Service is already initialized.');
39
- return;
40
- }
41
-
42
- this.path = path;
43
-
44
- try {
45
- this.status = Status.Loading;
46
- if (await this.files.hasInStorage(this.path)) {
47
- const fileContent = await this.files.readFromStorage(this.path);
48
- this.$dataWritable.set(JSON.parse(fileContent));
49
- }
50
- }
51
- catch (error) {
52
- console.error('Failed to load file:', error);
53
- this.$dataWritable.set({});
54
- }
55
- finally {
56
- this.status = Status.Idle;
57
- }
58
- }
59
-
60
- has(key: string): boolean {
61
- return key in this.$data();
62
- }
63
-
64
- set(key: string, value: any): void {
65
- this.checkType(value);
66
- const newData = { ...this.$dataWritable() };
67
- newData[key] = value;
68
- this.$dataWritable.set(newData);
69
- }
70
-
71
- setAll(data: BoxData): void {
72
- this.$dataWritable.set(data);
73
- }
74
-
75
- remove(key: string): void {
76
- const newData = { ...this.$dataWritable() };
77
- delete newData[key];
78
- this.$dataWritable.set(newData);
79
- }
80
-
81
- private checkType(value: any) {
82
- if (value instanceof Function)
83
- throw new Error('Cannot save functions.');
84
-
85
- if (value instanceof Promise)
86
- throw new Error('Cannot save promises.');
87
- }
88
-
89
- async save() {
90
- if (this.saveEnqueued)
91
- return;
92
-
93
- this.saveEnqueued = true;
94
-
95
- while (this.status === Status.Saving)
96
- await Async.delay(100);
97
-
98
- this.saveEnqueued = false;
99
-
100
- try {
101
- this.status = Status.Saving;
102
- const dataString = JSON.stringify(this.$data());
103
- await this.files.writeToStorage(this.path, dataString);
104
- }
105
- catch (error) {
106
- console.error('Failed to save file:', error);
107
- }
108
- finally {
109
- this.status = Status.Idle;
110
- }
111
- }
112
- }
package/storage/files.ts DELETED
@@ -1,42 +0,0 @@
1
- import { Injectable } from '@angular/core';
2
-
3
- @Injectable()
4
- export abstract class FilesService {
5
- /**
6
- * Initializes the service.
7
- * This should handle any setup required by the specific implementation.
8
- */
9
- abstract init(...args: any[]): Promise<void>;
10
-
11
- /**
12
- * Joins a filename with the storage base directory to get a full path.
13
- * Returns a string or a Promise of a string depending on implementation.
14
- * In some platforms, this may return null if paths are not supported.
15
- */
16
- abstract joinStoragePath(filePath: string): Promise<string | null>;
17
-
18
- /**
19
- * Checks if a file exists in the platform-specific storage.
20
- */
21
- abstract hasInStorage(filePath: string): Promise<boolean>;
22
-
23
- /**
24
- * Reads a file from the platform-specific storage.
25
- */
26
- abstract readFromStorage(filePath: string): Promise<string>;
27
-
28
- /**
29
- * Writes data to a file in the platform-specific storage.
30
- */
31
- abstract writeToStorage(filePath: string, data: string): Promise<void>;
32
-
33
- /**
34
- * Checks if a file exists in the project's assets/resources.
35
- */
36
- abstract hasInProject(filePath: string): Promise<boolean>;
37
-
38
- /**
39
- * Reads a file from the project's assets/resources.
40
- */
41
- abstract readFromProject(filePath: string): Promise<string>;
42
- }
@@ -1,49 +0,0 @@
1
- import { computed, Signal } from '@angular/core';
2
-
3
- export abstract class KeySignals {
4
- protected abstract $data(): Record<string, any>;
5
- protected abstract setRawValue(key: string, value: any): Promise<void>;
6
- protected abstract setMultipleRawValues(values: Record<string, any>): Promise<void>;
7
-
8
- protected readonly SEPARATOR = '|';
9
-
10
- public getCanonicalKey(path: string[]): string {
11
- return path.join(this.SEPARATOR);
12
- }
13
-
14
- public getNewSignal<T>(defaultValue: T, ...path: string[]): Signal<T> {
15
- return computed(() => this.getValue(defaultValue, ...path));
16
- }
17
-
18
- public getValue<T>(defaultValue: T, ...path: string[]): T {
19
- const key = this.getCanonicalKey(path);
20
- const data = this.$data();
21
- return key in data ? data[key] : defaultValue;
22
- }
23
-
24
- public async set(value: any, ...path: string[]): Promise<void> {
25
- const key = this.getCanonicalKey(path);
26
- await this.setRawValue(key, value);
27
- }
28
-
29
- /**
30
- * Clears all keys that start with the given prefix.
31
- */
32
- public async clearByPrefix(...pathPrefix: string[]): Promise<void> {
33
- const prefix = this.getCanonicalKey(pathPrefix) + this.SEPARATOR;
34
- const data = this.$data();
35
- const updatedData = { ...data };
36
- let changed = false;
37
-
38
- for (const key in updatedData) {
39
- if (key.startsWith(prefix)) {
40
- delete updatedData[key];
41
- changed = true;
42
- }
43
- }
44
-
45
- if (changed) {
46
- await this.setMultipleRawValues(updatedData);
47
- }
48
- }
49
- }
@@ -1,49 +0,0 @@
1
- import { Injectable, inject } from '@angular/core';
2
- import { HttpClient } from '@angular/common/http';
3
- import { lastValueFrom } from 'rxjs';
4
- import { FilesService } from './files';
5
-
6
- @Injectable()
7
- export class LocalStorageFilesService extends FilesService {
8
- private http = inject(HttpClient);
9
-
10
- static isSupported(): boolean {
11
- return typeof window !== 'undefined' && !!window.localStorage;
12
- }
13
-
14
- async init() {
15
- // LocalStorage is ready immediately
16
- }
17
-
18
- async joinStoragePath(filePath: string): Promise<string | null> {
19
- return null;
20
- }
21
-
22
- async hasInStorage(filePath: string): Promise<boolean> {
23
- return localStorage.getItem(filePath) !== null;
24
- }
25
-
26
- async readFromStorage(filePath: string): Promise<string> {
27
- const data = localStorage.getItem(filePath);
28
- if (data === null) throw new Error(`File not found in localStorage: ${filePath}`);
29
- return data;
30
- }
31
-
32
- async writeToStorage(filePath: string, data: string): Promise<void> {
33
- localStorage.setItem(filePath, data);
34
- }
35
-
36
- async hasInProject(filePath: string): Promise<boolean> {
37
- try {
38
- // In a browser, we check if we can fetch it via HTTP
39
- await lastValueFrom(this.http.head(filePath));
40
- return true;
41
- } catch {
42
- return false;
43
- }
44
- }
45
-
46
- async readFromProject(filePath: string): Promise<string> {
47
- return await lastValueFrom(this.http.get(filePath, { responseType: 'text' }));
48
- }
49
- }
@@ -1,24 +0,0 @@
1
- import { inject, Injectable } from '@angular/core';
2
- import { KeySignals } from './key-signals';
3
- import { FileBoxService } from './file-box.service';
4
-
5
- @Injectable({
6
- providedIn: 'root'
7
- })
8
- export class SettingsSignalsService extends KeySignals {
9
- private filebox = inject(FileBoxService);
10
-
11
- protected override $data(): Record<string, any> {
12
- return this.filebox.$data();
13
- }
14
-
15
- protected override async setRawValue(key: string, value: any): Promise<void> {
16
- this.filebox.set(key, value);
17
- await this.filebox.save();
18
- }
19
-
20
- protected override async setMultipleRawValues(values: Record<string, any>): Promise<void> {
21
- this.filebox.setAll(values);
22
- await this.filebox.save();
23
- }
24
- }
@@ -1,24 +0,0 @@
1
- import { inject, Injectable, Signal } from "@angular/core";
2
- import { FileBoxService } from "./file-box.service";
3
- import { SettingsSignalsService } from "./settings-signals.service";
4
-
5
- @Injectable({
6
- providedIn: "root"
7
- })
8
- export class SettingsService {
9
- private fileboxService = inject(FileBoxService);
10
- private sss = inject(SettingsSignalsService);
11
- private readonly SETTINGS_KEY = "app-settings";
12
-
13
- public getNewSignal<T>(defaultValue: T, ...path: string[]): Signal<T> {
14
- return this.sss.getNewSignal(defaultValue, this.SETTINGS_KEY, ...path);
15
- }
16
-
17
- public async set(value: any, ...path: string[]): Promise<void> {
18
- await this.sss.set(value, this.SETTINGS_KEY, ...path);
19
- }
20
-
21
- public async save(): Promise<void> {
22
- await this.fileboxService.save();
23
- }
24
- }
@@ -1,69 +0,0 @@
1
- import { Injectable } from '@angular/core';
2
- import { FilesService } from './files';
3
-
4
- @Injectable()
5
- export class TauriFilesService extends FilesService {
6
- private static _isTauri: boolean | null = null;
7
-
8
- static isSupported(): boolean {
9
- if (this._isTauri !== null) return this._isTauri;
10
-
11
- try {
12
- // Check for window.__TAURI_INTERNALS__ which is usually present in v2
13
- this._isTauri = !!((window as any).__TAURI_INTERNALS__);
14
- } catch {
15
- this._isTauri = false;
16
- }
17
- return this._isTauri;
18
- }
19
-
20
- async init() {
21
- // No special initialization needed for Tauri FS plugin beyond what is handled lazily
22
- }
23
-
24
- async joinStoragePath(filePath: string): Promise<string | null> {
25
- const { appLocalDataDir, join } = await import('@tauri-apps/api/path');
26
- const dataDir = await appLocalDataDir();
27
- return await join(dataDir, filePath);
28
- }
29
-
30
- async hasInStorage(filePath: string): Promise<boolean> {
31
- try {
32
- const { exists } = await import('@tauri-apps/plugin-fs');
33
- const { BaseDirectory } = await import('@tauri-apps/api/path');
34
- return await exists(filePath, { baseDir: BaseDirectory.AppLocalData });
35
- } catch (error) {
36
- console.error('TauriFilesService.hasInStorage error:', error);
37
- return false;
38
- }
39
- }
40
-
41
- async readFromStorage(filePath: string): Promise<string> {
42
- const { readTextFile } = await import('@tauri-apps/plugin-fs');
43
- const { BaseDirectory } = await import('@tauri-apps/api/path');
44
- return await readTextFile(filePath, { baseDir: BaseDirectory.AppLocalData });
45
- }
46
-
47
- async writeToStorage(filePath: string, data: string): Promise<void> {
48
- const { writeTextFile } = await import('@tauri-apps/plugin-fs');
49
- const { BaseDirectory } = await import('@tauri-apps/api/path');
50
- await writeTextFile(filePath, data, { baseDir: BaseDirectory.AppLocalData });
51
- }
52
-
53
- async hasInProject(filePath: string): Promise<boolean> {
54
- try {
55
- const response = await fetch(filePath, { method: 'HEAD' });
56
- return response.ok;
57
- } catch {
58
- return false;
59
- }
60
- }
61
-
62
- async readFromProject(filePath: string): Promise<string> {
63
- const response = await fetch(filePath);
64
- if (!response.ok) {
65
- throw new Error(`Failed to read project file: ${filePath} (${response.status})`);
66
- }
67
- return await response.text();
68
- }
69
- }
@@ -1,33 +0,0 @@
1
- import { computed, effect, inject, Injectable } from '@angular/core';
2
- import { SettingsService } from "../storage/settings.service";
3
-
4
- export enum ThemeType {
5
- Unset = '',
6
- Light = 'light',
7
- Dark = 'dark',
8
- }
9
-
10
- @Injectable({
11
- providedIn: 'root'
12
- })
13
- export class ThemeService {
14
- private settings = inject(SettingsService);
15
- public $theme = this.settings.getNewSignal<ThemeType>(ThemeType.Unset, 'theme');
16
-
17
- public $darkMode = computed(() => {
18
- const theme = this.$theme();
19
- let isDarkMode: boolean = theme === ThemeType.Unset
20
- ? window.matchMedia('(prefers-color-scheme: dark)').matches
21
- : theme === ThemeType.Dark;
22
-
23
- return isDarkMode;
24
- });
25
-
26
- private effectSetDarkMode = effect(async () => {
27
- document.documentElement.classList.toggle('dark', this.$darkMode());
28
- });
29
-
30
- public async setTheme(theme: ThemeType) {
31
- await this.settings.set(theme, 'theme');
32
- }
33
- }
package/tsconfig.lib.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "inlineSourceMap": true,
5
- "inlineSources": true,
6
- "declarationMap": true
7
- },
8
- "angularCompilerOptions": {
9
- "compilationMode": "partial"
10
- }
11
- }
@@ -1,55 +0,0 @@
1
- import { Component, input, signal, booleanAttribute } from '@angular/core';
2
- import { CommonModule } from '@angular/common';
3
-
4
- @Component({
5
- selector: 'context-button',
6
- standalone: true,
7
- imports: [CommonModule],
8
- template: `
9
- <div class="relative" (mouseenter)="onMouseEnter()" (mouseleave)="onMouseLeave()">
10
- <button
11
- class="w-full text-left px-3 py-2 text-sm transition-colors duration-100 flex items-center justify-between gap-2"
12
- [class.hover:bg-light-bg-secondary]="!disabled()"
13
- [class.dark:hover:bg-[#383838]]="!disabled()"
14
- [class.text-red-500]="danger() && !disabled()"
15
- [class.hover:bg-red-500]="danger() && !disabled()"
16
- [class.hover:text-white]="danger() && !disabled()"
17
- [class.opacity-50]="disabled()"
18
- [class.cursor-not-allowed]="disabled()"
19
- [disabled]="disabled()">
20
- <div class="flex items-center gap-2">
21
- <ng-content select="[icon]"></ng-content>
22
- <ng-content></ng-content>
23
- </div>
24
- @if (hasSubmenu()) {
25
- <span class="text-[10px] text-light-text-secondary opacity-50">▶</span>
26
- }
27
- </button>
28
-
29
- @if (hasSubmenu() && $showSubmenu() && !disabled()) {
30
- <div class="absolute left-full top-0 ml-[-2px] z-[60]">
31
- <ng-content select="context-menu"></ng-content>
32
- </div>
33
- }
34
- </div>
35
- `
36
- })
37
- export class ContextButtonComponent {
38
- danger = input(false, { transform: booleanAttribute });
39
- disabled = input(false, { transform: booleanAttribute });
40
- hasSubmenu = input(false, { transform: booleanAttribute });
41
-
42
- $showSubmenu = signal(false);
43
-
44
- onMouseEnter() {
45
- if (this.hasSubmenu() && !this.disabled()) {
46
- this.$showSubmenu.set(true);
47
- }
48
- }
49
-
50
- onMouseLeave() {
51
- if (this.hasSubmenu() && !this.disabled()) {
52
- this.$showSubmenu.set(false);
53
- }
54
- }
55
- }
@@ -1,15 +0,0 @@
1
- import { Component } from '@angular/core';
2
- import { CommonModule } from '@angular/common';
3
-
4
- @Component({
5
- selector: 'context-header',
6
- standalone: true,
7
- imports: [CommonModule],
8
- template: `
9
- <div
10
- class="px-3 py-1.5 text-xs font-semibold text-light-text-secondary dark:text-dark-text-secondary truncate border-b border-light-border dark:border-dark-border mb-1">
11
- <ng-content></ng-content>
12
- </div>
13
- `
14
- })
15
- export class ContextHeaderComponent { }
@@ -1,26 +0,0 @@
1
- import { Directive, input, output, HostListener, ElementRef } from '@angular/core';
2
- import { ContextMenuComponent } from './context-menu.component';
3
-
4
- @Directive({
5
- selector: '[appContextMenu]',
6
- standalone: true
7
- })
8
- export class ContextMenuTriggerDirective {
9
- appContextMenu = input.required<ContextMenuComponent>();
10
-
11
- beforeOpen = output<void>();
12
-
13
- constructor(private elementRef: ElementRef) { }
14
-
15
- @HostListener('contextmenu', ['$event'])
16
- onContextMenu(event: MouseEvent) {
17
- event.preventDefault();
18
- event.stopPropagation();
19
-
20
- // Notify parent to prepare data
21
- this.beforeOpen.emit();
22
-
23
- // Show menu at cursor position
24
- this.appContextMenu().show(event.clientX, event.clientY);
25
- }
26
- }