Skip to content

Commit

Permalink
feat: attachments example
Browse files Browse the repository at this point in the history
  • Loading branch information
voznik committed Feb 10, 2024
1 parent 6940b2e commit cefa3d8
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 18 deletions.
9 changes: 8 additions & 1 deletion examples/demo/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule, Routes } from '@angular/router';
import { NgxRxdbModule } from '@ngx-odm/rxdb';
import { getRxDatabaseCreator } from '@ngx-odm/rxdb/config';
import { RxDBAttachmentsPlugin } from 'rxdb/plugins/attachments';
import { RxDBLeaderElectionPlugin } from 'rxdb/plugins/leader-election';
import { AppComponent } from './app.component';

const routes: Routes = [
Expand All @@ -27,10 +29,15 @@ const routes: Routes = [
NgxRxdbModule.forRoot(
getRxDatabaseCreator({
name: 'demo',
localDocuments: true,
localDocuments: false,
multiInstance: true,
ignoreDuplicate: false,
options: {
plugins: [
// will be loaded by together with core plugins
RxDBAttachmentsPlugin,
RxDBLeaderElectionPlugin,
],
storageType: localStorage['_ngx_rxdb_storage'] ?? 'dexie',
dumpPath: 'assets/data/db.dump.json',
},
Expand Down
35 changes: 35 additions & 0 deletions examples/demo/src/app/todos/todos.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ <h1>todos</h1>
<li
*ngFor="let todo of $.todos | byStatus: $.filter; trackBy: trackByFn"
[class.completed]="todo.completed"
(contextmenu)="showContextMenu($event, todo)"
>
<div class="view">
<input
Expand Down Expand Up @@ -106,3 +107,37 @@ <h1>todos</h1>
</button>
</footer>
</section>

<dialog [open]="isDialogOpen">
Attachments:
<ul *ngIf="selectedTodo?._attachments">
<li *ngFor="let attachment of selectedTodo._attachments | keyvalue">
<a
href="javascript:void(0);"
(click)="todosService.downloadAttachment(selectedTodo.id, attachment.key)"
>
{{ attachment.key }} - {{ attachment.value.type }}
</a>
&nbsp;
<button
type="button"
(click)="todosService.removeAttachment(selectedTodo.id, attachment.key)"
>
</button>
</li>
</ul>
<form method="dialog" (ngSubmit)="selectedTodo = undefined; isDialogOpen = false">
<button type="button" (click)="fileInput.click()">Upload txt attachment</button>
<input
type="file"
accept=".txt"
#fileInput
style="display: none"
(change)="
todosService.uploadAttachment(selectedTodo.id, $any($event.target).files[0])
"
/>
<button>OK</button>
</form>
</dialog>
10 changes: 10 additions & 0 deletions examples/demo/src/app/todos/todos.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,17 @@ export class TodosComponent {
);
count$ = this.todosService.count$;

isDialogOpen = false;
selectedTodo: Todo = undefined;

trackByFn = (index: number, item: Todo) => {
return item.last_modified;
};

showContextMenu(event: Event, todo: Todo) {
event.preventDefault();
this.selectedTodo = todo;
this.isDialogOpen = true;
console.log('showContextMenu', todo);
}
}
1 change: 1 addition & 0 deletions examples/demo/src/app/todos/todos.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface Todo {
completed: boolean;
createdAt: string;
last_modified: number;
_attachments?: any[];

Check failure on line 7 in examples/demo/src/app/todos/todos.model.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
}

export type TodosFilter = 'ALL' | 'COMPLETED' | 'ACTIVE';
Expand Down
30 changes: 28 additions & 2 deletions examples/demo/src/app/todos/todos.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import { Injectable, inject } from '@angular/core';
import { NgxRxdbCollection, NgxRxdbCollectionService } from '@ngx-odm/rxdb/collection';
import { DEFAULT_LOCAL_DOCUMENT_ID } from '@ngx-odm/rxdb/config';
import type { RxDatabaseCreator } from 'rxdb';
import { RxAttachment, type RxDatabaseCreator } from 'rxdb';

Check failure on line 5 in examples/demo/src/app/todos/todos.service.ts

View workflow job for this annotation

GitHub Actions / build

'RxAttachment' is defined but never used
import { Observable, distinctUntilChanged, startWith } from 'rxjs';
import { v4 as uuid } from 'uuid';
import { Todo, TodosFilter, TodosLocalState } from './todos.model';

const withAttachments = true;

@Injectable()
export class TodosService {
private collectionService: NgxRxdbCollection<Todo> = inject<NgxRxdbCollection<Todo>>(
Expand All @@ -22,7 +24,8 @@ export class TodosService {
count$ = this.collectionService.count();

todos$: Observable<Todo[]> = this.collectionService.docs(
this.collectionService.queryParams$
this.collectionService.queryParams$,
withAttachments
);

get dbOptions(): Readonly<RxDatabaseCreator> {
Expand Down Expand Up @@ -107,4 +110,27 @@ export class TodosService {
filter
);
}

async uploadAttachment(id: string, file: File) {
await this.collectionService.putAttachment(id, {
id: file.name, // (string) name of the attachment
data: file, // createBlob('meowmeow', 'text/plain'), // (string|Blob) data of the attachment
type: 'text/plain', // (string) type of the attachment-data like 'image/jpeg'
});
}

async downloadAttachment(id: string, a_id: string) {
const data = await this.collectionService.getAttachmentById(id, a_id);
const url = URL.createObjectURL(data);
const a = document.createElement('a');
a.href = url;
a.download = a_id;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}

async removeAttachment(id: string, a_id: string) {
await this.collectionService.removeAttachment(id, a_id);
}
}
5 changes: 4 additions & 1 deletion examples/demo/src/assets/data/todo.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,8 @@
}
},
"__indexes": ["createdAt"],
"primaryKey": "id"
"primaryKey": "id",
"attachments": {
"encrypted": false
}
}
7 changes: 7 additions & 0 deletions examples/standalone/src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { provideAnimationsAsync } from '@angular/platform-browser/animations/asy
import { provideRouter, withRouterConfig } from '@angular/router';
import { provideRxDatabase } from '@ngx-odm/rxdb';
import { getRxDatabaseCreator } from '@ngx-odm/rxdb/config';
import { RxDBAttachmentsPlugin } from 'rxdb/plugins/attachments';
import { RxDBLeaderElectionPlugin } from 'rxdb/plugins/leader-election';
import { appRoutes } from './app.routes';

export const appConfig: ApplicationConfig = {
Expand All @@ -21,6 +23,11 @@ export const appConfig: ApplicationConfig = {
ignoreDuplicate: false,
// storage: getRxStorageDexie(), // INFO: can be ommited, will be provide by `storageType` string
options: {
plugins: [
// will be loaded by together with core plugins
RxDBAttachmentsPlugin,
RxDBLeaderElectionPlugin,
],
storageType: localStorage['_ngx_rxdb_storage'] ?? 'dexie',
dumpPath: 'assets/data/db.dump.json',
},
Expand Down
47 changes: 47 additions & 0 deletions packages/rxdb/collection/src/lib/rxdb-collection.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
type RxLocalDocument,
type RxStorageWriteError,
type RxCollection as _RxCollection,
RxAttachment,

Check failure on line 28 in packages/rxdb/collection/src/lib/rxdb-collection.service.ts

View workflow job for this annotation

GitHub Actions / build

'RxAttachment' is defined but never used
RxAttachmentCreator,
} from 'rxdb';
import { RxReplicationState } from 'rxdb/plugins/replication';
import {
Expand Down Expand Up @@ -367,6 +369,51 @@ export class NgxRxdbCollection<T extends Entity = { id: EntityId }> {
return this.collection.remove();
}

async getAttachments(docId: string): Promise<Blob[] | null> {
await this.ensureCollection();
const doc = await this.collection.findOne(docId).exec();
if (!doc) {
return null;
}
const attachmentsData = doc.allAttachments().map(a => a.getData());
return Promise.all(attachmentsData);
}

async getAttachmentById(docId: string, attachmentId: string): Promise<Blob | null> {
await this.ensureCollection();
const doc = await this.collection.findOne(docId).exec();
if (!doc) {
return null;
}
const attachment = doc.getAttachment(attachmentId);
if (!attachment) {
return null;
}
return attachment.getData();
}

async putAttachment(docId: string, attachment: RxAttachmentCreator): Promise<void> {
await this.ensureCollection();
const doc = await this.collection.findOne(docId).exec();
if (!doc) {
throw new Error(`Document with id "${docId}" not found.`);
}
await doc.putAttachment(attachment);
}

async removeAttachment(docId: string, attachmentId: string): Promise<void> {
await this.ensureCollection();
const doc = await this.collection.findOne(docId).exec();
if (!doc) {
throw new Error(`Document with id "${docId}" not found.`);
}
const attachment = doc.getAttachment(attachmentId);
if (!attachment) {
throw new Error(`Attachment with id "${attachmentId}" not found.`);
}
await attachment.remove();
}

/**
* Add one of RxDB-supported middleware-hooks to current collection, e.g run smth on document postSave.
* By default runs in series
Expand Down
3 changes: 3 additions & 0 deletions packages/rxdb/config/src/lib/rxdb.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
RxCollectionCreator,
RxDatabaseCreator,
RxJsonSchema,
RxPlugin,
} from 'rxdb';
import { RxReplicationState } from 'rxdb/plugins/replication';
import { getRxStorageDexie } from 'rxdb/plugins/storage-dexie';
Expand Down Expand Up @@ -102,6 +103,7 @@ interface NgxRxdbConfigOptions {
storageOptions?: {};
dumpPath?: string;
useQueryParams?: boolean;
plugins?: RxPlugin[];
}

type RxDatabaseCreatorPartialStorage = SetOptional<RxDatabaseCreator, 'storage'>;
Expand Down Expand Up @@ -134,6 +136,7 @@ export function getRxDatabaseCreator(config: NgxRxdbConfig): RxDatabaseCreator {
const dbConfig: RxDatabaseCreator = {
name,
storage,
options,
...rest,
};
return dbConfig;
Expand Down
18 changes: 11 additions & 7 deletions packages/rxdb/core/src/lib/rxdb-plugin.loader.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
import { isDevMode } from '@angular/core';
import { RxDBPUseQueryParamsPlugin } from '@ngx-odm/rxdb/query-params';
import { NgxRxdbUtils } from '@ngx-odm/rxdb/utils';
import { addRxPlugin } from 'rxdb';
import { RxPlugin, addRxPlugin } from 'rxdb';
import { RxDBCleanupPlugin } from 'rxdb/plugins/cleanup';
import { RxDBJsonDumpPlugin } from 'rxdb/plugins/json-dump';
import { RxDBLeaderElectionPlugin } from 'rxdb/plugins/leader-election';
import { RxDBLocalDocumentsPlugin } from 'rxdb/plugins/local-documents';
import { RxDBMigrationPlugin } from 'rxdb/plugins/migration-schema';
import { RxDBUpdatePlugin } from 'rxdb/plugins/update';
import { RxDBPreparePlugin } from './rxdb-prepare.plugin';

/**
* Loads all the necessary RxDB plugins for the application to work.
* Loads all the necessary and additional RxDB plugins for the application to work.
* @param plugins
* @returns A Promise that resolves when all the plugins have been loaded.
* @throws If there was an error loading the plugins.
*/
export async function loadRxDBPlugins(): Promise<void> {
export async function loadRxDBPlugins(plugins: RxPlugin[] = []): Promise<void> {
try {
// plugins
// vendor
addRxPlugin(RxDBLocalDocumentsPlugin);
addRxPlugin(RxDBLeaderElectionPlugin);
addRxPlugin(RxDBJsonDumpPlugin);
addRxPlugin(RxDBMigrationPlugin);
addRxPlugin(RxDBUpdatePlugin);
addRxPlugin(RxDBCleanupPlugin);
// custom
addRxPlugin(RxDBPreparePlugin);
addRxPlugin(RxDBPUseQueryParamsPlugin);
addRxPlugin(RxDBCleanupPlugin);
// additional plugins
for (const plugin of plugins) {
addRxPlugin(plugin);
}

/** * to reduce the build-size, we use some plugins in dev-mode only */
if (isDevMode() && !NgxRxdbUtils.isTestEnvironment()) {
Expand Down
2 changes: 1 addition & 1 deletion packages/rxdb/core/src/lib/rxdb.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class NgxRxdbService {
*/
async initDb(config: RxDatabaseCreator): Promise<void> {
try {
await loadRxDBPlugins();
await loadRxDBPlugins(config.options?.plugins);
this.dbInstance = await createRxDatabase(config).catch(e => {
throw new Error(e.message ?? e);
});
Expand Down
3 changes: 1 addition & 2 deletions packages/rxdb/utils/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ export namespace NgxRxdbUtils {
*
* Differences from lodash:
* - does not give any special consideration for arguments objects, strings, or prototype objects (e.g. many will have `'length'` in the returned array)
*
* @internal
* @param object
*/
export function keys<T>(object: Nil | T): Array<StringifiedKey<T>> {
let val = keysOfNonArray(object);
Expand Down
8 changes: 4 additions & 4 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@
"packages/rxdb/config/src/index.ts"
],
"@ngx-odm/rxdb/core": ["dist/packages/rxdb/core", "packages/rxdb/core/src/index.ts"],
"@ngx-odm/rxdb/query-params": ["packages/rxdb/query-params/src/index.ts"],
"@ngx-odm/rxdb/replication-kinto": ["packages/rxdb/replication-kinto/src/index.ts"],
"@ngx-odm/rxdb/signals": ["packages/rxdb/signals/src/index.ts"],
"@ngx-odm/rxdb/testing": ["packages/rxdb/testing/src/index.ts"],
"@ngx-odm/rxdb/query-params": [ "dist/packages/rxdb/query-params", "packages/rxdb/query-params/src/index.ts"],
"@ngx-odm/rxdb/replication-kinto": ["dist/packages/rxdb/replication-kinto", "packages/rxdb/replication-kinto/src/index.ts"],
"@ngx-odm/rxdb/signals": ["dist/packages/rxdb/signals", "packages/rxdb/signals/src/index.ts"],
"@ngx-odm/rxdb/testing": ["dist/packages/rxdb/testing", "packages/rxdb/testing/src/index.ts"],
"@ngx-odm/rxdb/utils": [
"dist/packages/rxdb/utils",
"packages/rxdb/utils/src/index.ts"
Expand Down

0 comments on commit cefa3d8

Please sign in to comment.