Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,352 changes: 31 additions & 1,321 deletions projects/kit/README.md

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions projects/kit/docs/auth-http.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## Access capability

`provideKitAuth()` configures functional route guards for `user`, `confirm`, `required`, `anonymous`, and `unavailable` authentication states. Redirect routes and application side effects remain in the app.

`KitAuthAccessService` publishes what the current session may do:

| Mode | Local replica and outbox | Authenticated HTTP, realtime, and sync |
| -------- | ------------------------ | -------------------------------------- |
| `none` | Blocked | Blocked |
| `local` | Allowed | Blocked |
| `remote` | Allowed | Allowed |

An authoritative `required` result is signed out and must not become offline access. Only an `unavailable` transport result may activate a previously verified local session.

```ts
provideKitAuth(() => ({
authState: () => inject(AuthService).state$,
redirects: {
whenAuthorized: '/home',
whenConfirming: '/auth/confirm',
whenNotConfirming: '/auth/signin',
whenUnauthorized: '/auth',
},
isUnavailableError: (error) => isOfflineFallbackError(error),
}));
```

Use `kitRequiredUnauthorizedGuard`, `kitRequireConfirmingGuard`, and `kitRequireAuthorizedGuard` in route definitions. A protected asynchronous decision suspends previously published remote capability until the current authorization lease succeeds.

## HTTP policy

`provideKitHttp()` configures `kitAuthInterceptor` for credential injection, bypass rules, transient failure handling, and application error hooks.

Automatic retry is limited to `GET`, `HEAD`, `OPTIONS`, or requests carrying an `Idempotency-Key`. Ordinary writes are never retried automatically. Retries cover transient statuses `0`, `408`, `429`, `502`, `503`, and `504`, and honor `Retry-After`.

When offline support is enabled, register `offlineInterceptor` before `kitAuthInterceptor`. Local mode then prevents credential generation and network transport while allowing a matched read policy to serve the scoped replica.
36 changes: 36 additions & 0 deletions projects/kit/docs/offline-realtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## Scoped offline runtime

The `/offline` entry point provides a user- and partition-scoped local replica, durable outbox, cursor-based delta pull, aggregate-ordered replay, optimistic mutation policies, and request-policy interception.

Use `mode: 'readCacheOnly'` for external-source or HTTP caches. Synchronized mode uses encrypted `@capacitor-community/sqlite` on iOS and Android; it fails fast on the web because the current runtime has no cross-tab synchronization lock.

Cold-start offline access restores only a manifest bound to a non-null authentication-provider subject. Remote work follows this order:

1. Prepare the verified remote session.
2. Publish `remote` access.
3. Resume pull, outbox replay, and realtime work.

`createOfflineAuthBridge()` connects this ordering to `provideKitAuth()` while leaving consent, error UI, and credential exchange in the app.

```ts
import { createOfflineAuthBridge, isOfflineFallbackError } from '@rdlabo/ionic-angular-kit/offline';

provideKitAuth(() => ({
authState: () => auth.state$,
...createOfflineAuthBridge({
exchange: async (context) => exchangeCredential(context),
currentAuthSubject: () => auth.currentSubject(),
isUnavailableError,
availability: () => auth.authorityAvailable$,
}),
redirects,
}));
```

On explicit sign-out, clear `KitAuthAccessService` first, then await offline session cleanup so in-flight leases are invalidated before persisted user data is removed.

## Realtime connection

Subclass `KitRealtimeConnection` to supply connection intent and `{ url, protocols }` targets. The kit owns foreground and network suspension, target-scoped reconnect, exponential backoff, ping/pong detection, self-echo annotation, and `reconnected$` resync signaling.

Use `kitRealtimeProtocols()` to carry authentication and the stable `KIT_REALTIME_CLIENT_ID` in WebSocket subprotocols instead of URL parameters. Offline-capable authenticated clients set `requireRemoteAccess: true`; sockets then remain closed in `none` and `local` modes.
31 changes: 31 additions & 0 deletions projects/kit/docs/optional-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Optional features use secondary entry points so their native plugins and SDKs do not enter applications that do not use them.

## Theme and review

`provideKitTheme()` and `KitThemeController` persist a user preference, follow `prefers-color-scheme` until overridden, toggle app-provided palette classes, and synchronize the Android status bar.

```ts
provideKitTheme({
storageKey: 'theme',
darkClasses: ['ion-palette-dark'],
lightClasses: ['ion-palette-light'],
});
```

Import `kitRequestReview()` from `/review` to request the native review dialog at most once per application-defined window. It is a no-op on the web.

## Printer

The `/printer` entry point contains pure helpers for DOM-to-PNG rendering, image rotation, Brother print settings, multi-page label layout, and PDF generation. The consuming app owns paper-selection UI, loading overlays, storage, transport, and copy policy.

## Firebase authentication

The `/auth-firebase` entry point initializes `firebase/auth` through `provideKitFirebase()` and exposes `KIT_FIREBASE_AUTH` plus flow helpers such as `kitSignIn`, `kitSignUp`, `kitSignOut`, `kitResolveAuthStatus`, and `kitReauthWithRetry`.

The kit performs no UI. Hooks carry loading, navigation, and error presentation back to the application. Social providers are isolated further under `/auth-firebase/social`.

## Live Update

`provideLiveUpdateReadiness()` from `/live-update` waits for Angular stability, the first completed route, and one animation frame before calling Capawesome `LiveUpdate.ready()`. It is a no-op on the web.

A Live Update replaces only the web layer of an existing native binary. Native code, Capacitor configuration, or plugin version changes require a store build and a new build-number-specific channel.
52 changes: 52 additions & 0 deletions projects/kit/docs/storage-overlays.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## Typed storage

Provide Ionic Storage once. `KitStorageService` initializes it lazily and every public operation waits for that initialization, so writes made immediately after service creation are not dropped.

```ts
import { importProvidersFrom } from '@angular/core';
import { IonicStorageModule } from '@ionic/storage-angular';

export const appConfig: ApplicationConfig = {
providers: [importProvidersFrom(IonicStorageModule.withConfig({ name: '__mydb' }))],
};
```

```ts
const storage = inject(KitStorageService);

await storage.set('token', token);
const saved = await storage.get<string>('token');
await storage.remove('token');
```

`get<T>()` returns `null` for a missing key. `kitClearStoragePreservingKeys()` clears application data while restoring selected values such as the last authentication email or theme.

## Typed overlays

Configure application-owned labels with `provideKitOverlay()` and inject `KitOverlayController`. The kit does not hard-code localized copy.

```ts
provideKitOverlay({
labels: {
close: $localize`Close`,
cancel: $localize`Cancel`,
},
});
```

```ts
export class DetailPage {
declare static readonly modalReturn: DetailResult;
readonly item = input.required<Item>();
}

export const launchDetailPage = (
overlay: KitOverlayController,
props: { item: Item },
): Promise<DetailResult | undefined> =>
overlay.presentModal(DetailPage, props, { backdropDismiss: false });
```

Component props are inferred from Angular `input()` fields and dismiss data from the component's static `modalReturn` declaration. Keep a typed launcher beside each modal or popover instead of calling an Ionic controller inline.

The same controller provides `presentPopover()`, `presentToast()`, `alertClose()`, and `alertConfirm()`. Modal option `watchKeyboard: true` expands a bottom sheet while the native keyboard is visible.
5 changes: 4 additions & 1 deletion projects/kit/ng-package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
"dest": "../../dist/kit",
"lib": {
"entryFile": "src/public-api.ts"
}
},
"assets": [
"docs"
]
}
183 changes: 34 additions & 149 deletions projects/photo-editor/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
# @rdlabo/ionic-angular-photo-editor

## Overview

This is a photo editor and viewer for modal page of Ionic Angular project using Capacitor.

**Documentation:** [Read the full documentation](https://docs.rdlabo.dev/projects/ionic-angular-photo-editor)
## Features

### Choose by editing goal

| Goal | Guide |
| --- | --- |
| Load a photo from camera or album | [PhotoFileService](./docs/photo-file.md) |
| Crop and edit in a modal | [Photo Editor](./docs/editor.md) |
| Browse images in a modal | [Photo Viewer](./docs/viewer.md) |
| Override editor colors | [Theme](./docs/theme.md) |

## Quick start

After [Installation](#installation), load a photo:

```typescript
import { PhotoFileService } from '@rdlabo/ionic-angular-photo-editor';

const files = await this.photoFileService.loadPhoto(1);
```

Then present the editor or viewer. Details: [PhotoFileService](./docs/photo-file.md), [Photo Editor](./docs/editor.md), [Photo Viewer](./docs/viewer.md).

## Installation

```bash
npm install @rdlabo/ionic-angular-photo-editor
npm install @rdlabo/ionic-angular-photo-editor
```

If you use capacitor, you need to install plugin:
Expand All @@ -26,154 +49,16 @@ If you public your project to the web, you need to add the following input tag t
</div>
```

## Theme

Default color is set, but user can overwrite it: https://github.com/rdlabo-dev/ionic-angular-library/blob/main/projects/photo-editor/src/lib/pages/core.scss

### How to overwrite

```scss
:root {
--ion-photo-editor-background: #2a2a2a;
--ion-photo-editor-background-tint: #414141;

--ion-photo-editor-color: #f0f0f0;
--ion-photo-editor-color-tint: #dbdbdb;

--ion-photo-editor-primary: #4d8dff;
--ion-photo-editor-danger: #f24c58;
--ion-photo-editor-success: #2dd55b;
}
```

## Usage

### PhotoFileService

```typescript
import { PhotoFileService } from '@rdlabo/ionic-angular-photo-editor';

export class AppComponent {
private photoFileService = inject(PhotoFileService);

constructor() {
this.photoFileService.photoMaxSize = 1000;
this.photoFileService.labels = {
camera: 'Camera',
album: 'Album',
cancel: 'Cancel',
};
}

async upload() {
const file = await this.photoFileService.loadPhoto();
if (file) {
// upload file
}
}
}
````

#### Options
##### photoMaxSize

The maximum size of the photo. Default is 1000.

##### labels

If set, the label is overwritten.


### PhotoEditorPage

```typescript
import { PhotoEditorPage, IPhotoEditorDismiss } from '@rdlabo/ionic-angular-photo-editor';

(async () => {
const modal = await this.modalCtrl.create({
component: PhotoEditorPage,
componentProps: {
requireSquare: false,
value: 'https://picsum.photos/200/300',
label: {
save: '送信', // change '保存' to '送信'
},
},
});
await modal.present();
const { data } = await modal.onWillDismiss<IPhotoEditorDismiss>();
if (data?.value) {
console.log(data.value);
}
})();
```

### Options

#### requireSquare: boolean

If true, the image must be cropped to a square at first.

#### value: string

The image url or base64 string.

#### labels: IDictionaryForEditor

If set, the label is overwritten.

List is [here](https://github.com/rdlabo-dev/ionic-angular-library/blob/main/projects/photo-editor/src/lib/dictionaries.ts).


### PhotoViewerPage

```typescript
import { PhotoViewerPage, IPhotoViewerDismiss } from '@rdlabo/ionic-angular-photo-editor';

(async () => {
const modal = await this.modalCtrl.create({
component: PhotoViewerPage,
componentProps: {
imageUrls: [
'https://picsum.photos/200/300',
'https://picsum.photos/200/300',
],
index: 0,
isCircle: false,
},
});
await modal.present();
const { data } = await modal.onWillDismiss<IPhotoViewerDismiss>();
if (data?.delete) {
// User delete image
}
})();
```

### Options

#### imageUrls: string[]

The image url or base64 string[].

#### index: number

The index of imageUrls.

#### isCircle: boolean

If set, the image is displayed in a circle.

#### enableDelete: boolean

If true, the delete button is displayed.

#### enableFooterSafeArea: boolean

If true, enable footer safe area for iOS.
## Documentation

#### labels: IDictionaryForViewer
Start with [Installation](#installation), then pick a guide.

If set, the label is overwritten.
- [PhotoFileService](./docs/photo-file.md) — camera and album.
- [Photo Editor](./docs/editor.md) — crop and edit in a modal.
- [Photo Viewer](./docs/viewer.md) — browse images in a modal.
- [Theme](./docs/theme.md) — CSS variables.

List is [here](https://github.com/rdlabo-dev/ionic-angular-library/blob/main/projects/photo-editor/src/lib/dictionaries.ts).
<!-- rdlabo-docs-omit -->
**Full documentation:** [https://docs.rdlabo.dev/projects/ionic-angular-photo-editor](https://docs.rdlabo.dev/projects/ionic-angular-photo-editor)
<!-- /rdlabo-docs-omit -->
Loading