From d7333ebe4b8fb6342a3f3c60afbc2f6ec4c3c351 Mon Sep 17 00:00:00 2001 From: Gui Seek Date: Mon, 30 Dec 2024 12:29:42 -0300 Subject: [PATCH] =?UTF-8?q?refactor(album):=20melhora=20marca=C3=A7=C3=A3o?= =?UTF-8?q?=20de=20pessoas=20em=20fotos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/devmx/public/icons/share.svg | 6 + apps/devmx/public/portal-devpr-mx.png | Bin 0 -> 4580 bytes apps/devmx/public/templates/user-tag.html | 89 +++++++++++ apps/devmx/src/scss/_overrides.scss | 4 + apps/devmx/src/styles.scss | 2 +- .../server/src/assets/templates/user-tag.html | 91 ++++++++++-- apps/server/src/envs/env.dev.ts | 1 + apps/server/src/envs/env.prod.ts | 1 + .../src/server/use-cases/update-course.ts | 2 +- .../server/use-cases/update-institution.ts | 2 +- .../src/server/use-cases/update-subject.ts | 2 +- .../containers/account/account.container.html | 81 +++++++++- .../containers/account/account.container.scss | 6 +- .../containers/account/account.container.ts | 3 + .../search-user/search-user-except.pipe.ts | 13 ++ .../search-user/search-user.component.html | 5 +- .../search-user/search-user.component.ts | 8 +- .../src/lib/application/photo.facade.ts | 3 +- .../data-access/src/lib/resolvers/index.ts | 3 +- .../lib/resolvers/photo-resolver-wrapped.ts | 11 ++ .../data-source/src/lib/schemas/photo.ts | 15 +- .../domain/src/server/use-cases/add-photo.ts | 2 +- .../src/server/use-cases/update-album.ts | 2 +- .../src/server/use-cases/update-photo-tags.ts | 42 ++++-- .../src/server/use-cases/update-photo.ts | 2 +- .../feature-admin/src/lib/sheets/index.ts | 2 + .../sheets/photo-tags/photo-tags.service.ts | 17 +++ .../sheets/photo-tags/photo-tags.sheet.html | 0 .../sheets/photo-tags/photo-tags.sheet.scss | 0 .../lib/sheets/photo-tags/photo-tags.sheet.ts | 21 +++ .../src/lib/sheets/photos/photos.service.ts | 17 +++ .../src/lib/sheets/photos/photos.sheet.html | 16 ++ .../src/lib/sheets/photos/photos.sheet.scss | 0 .../src/lib/sheets/photos/photos.sheet.ts | 22 +++ .../src/lib/album-feature-shell.routes.ts | 29 +++- .../lib/containers/album/album.container.ts | 21 ++- .../lib/containers/albums/albums.container.ts | 4 - .../feature-shell/src/lib/containers/index.ts | 2 + .../photo-details.container.html | 92 ++++++++++++ .../photo-details.container.scss | 121 +++++++++++++++ .../photo-details/photo-details.container.ts | 129 ++++++++++++++++ .../lib/containers/photo/photo.container.html | 25 ++++ .../lib/containers/photo/photo.container.scss | 22 +++ .../lib/containers/photo/photo.container.ts | 25 ++++ .../feature-shell/src/lib/resolvers/index.ts | 3 +- .../feature-shell/src/lib/resolvers/photo.ts | 8 + packages/album/ui-shared/package.json | 3 +- .../photo-viewer/photo-viewer.component.html | 36 ++++- .../photo-viewer/photo-viewer.component.scss | 25 +++- .../photo-viewer/photo-viewer.component.ts | 140 +++++++++++++----- .../photo-viewer/photo-viewer.service.ts | 30 +++- .../lib/components/tag-user/tag-user-data.ts | 5 + .../components/tag-user/tag-user-options.ts | 8 + .../components/tag-user/tag-user.component.ts | 10 +- .../components/tag-user/tag-user.service.ts | 11 +- .../src/server/use-cases/update-event.ts | 2 +- .../containers/events/events.container.html | 12 +- .../containers/events/events.container.scss | 9 +- .../src/server/use-cases/update-skill.ts | 4 +- .../src/lib/interfaces/update.ts | 5 +- .../src/server/dtos/send-mail.ts | 10 +- .../api-interfaces/src/server/envs/env.ts | 2 + .../shared/api-interfaces/src/server/index.ts | 1 + .../src/server/ports/entity.service.ts | 2 +- .../src/lib/infrastructure/mongo.service.ts | 6 +- .../src/lib/utils/create-schema.ts | 8 +- .../ui-global/icon/src/lib/types/icon.ts | 1 + .../sort-direction.component.html | 12 +- .../sort-direction.component.ts | 40 ++--- .../util-data/src/lib/utils/array-equals.ts | 9 ++ .../util-data/src/lib/utils/create-mail.ts | 37 +++++ .../shared/util-data/src/lib/utils/index.ts | 1 + .../util-data/src/server/templates/index.ts | 1 + .../util-data/src/server/templates/reader.ts | 7 + 74 files changed, 1236 insertions(+), 173 deletions(-) create mode 100644 apps/devmx/public/icons/share.svg create mode 100644 apps/devmx/public/portal-devpr-mx.png create mode 100644 apps/devmx/public/templates/user-tag.html create mode 100644 packages/account/ui-shared/src/lib/components/search-user/search-user-except.pipe.ts create mode 100644 packages/album/data-access/src/lib/resolvers/photo-resolver-wrapped.ts create mode 100644 packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.service.ts create mode 100644 packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.sheet.html create mode 100644 packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.sheet.scss create mode 100644 packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.sheet.ts create mode 100644 packages/album/feature-admin/src/lib/sheets/photos/photos.service.ts create mode 100644 packages/album/feature-admin/src/lib/sheets/photos/photos.sheet.html create mode 100644 packages/album/feature-admin/src/lib/sheets/photos/photos.sheet.scss create mode 100644 packages/album/feature-admin/src/lib/sheets/photos/photos.sheet.ts create mode 100644 packages/album/feature-shell/src/lib/containers/photo-details/photo-details.container.html create mode 100644 packages/album/feature-shell/src/lib/containers/photo-details/photo-details.container.scss create mode 100644 packages/album/feature-shell/src/lib/containers/photo-details/photo-details.container.ts create mode 100644 packages/album/feature-shell/src/lib/containers/photo/photo.container.html create mode 100644 packages/album/feature-shell/src/lib/containers/photo/photo.container.scss create mode 100644 packages/album/feature-shell/src/lib/containers/photo/photo.container.ts create mode 100644 packages/album/feature-shell/src/lib/resolvers/photo.ts create mode 100644 packages/album/ui-shared/src/lib/components/tag-user/tag-user-data.ts create mode 100644 packages/album/ui-shared/src/lib/components/tag-user/tag-user-options.ts create mode 100644 packages/shared/util-data/src/lib/utils/array-equals.ts create mode 100644 packages/shared/util-data/src/server/templates/reader.ts diff --git a/apps/devmx/public/icons/share.svg b/apps/devmx/public/icons/share.svg new file mode 100644 index 00000000..ba06f6d7 --- /dev/null +++ b/apps/devmx/public/icons/share.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/devmx/public/portal-devpr-mx.png b/apps/devmx/public/portal-devpr-mx.png new file mode 100644 index 0000000000000000000000000000000000000000..d296289383d7cea1d65f6af3cbfccc5f5b9f804f GIT binary patch literal 4580 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 O0000N literal 0 HcmV?d00001 diff --git a/apps/devmx/public/templates/user-tag.html b/apps/devmx/public/templates/user-tag.html new file mode 100644 index 00000000..a3bc55fe --- /dev/null +++ b/apps/devmx/public/templates/user-tag.html @@ -0,0 +1,89 @@ + + + + + + + + + +
+

Faala {{name}}, belê?

+ +

+ Alguém marcou você no album de fotos "{{title}}" + da comunidade. +

+ +
+ + 🛟 Você pode remover sua tag da foto se preferir, algumas orientações + para isso: + +
    +
  1. Clique no botão ☰ no lado direito acima da foto
  2. +
  3. Cliique em Remover minha tag
  4. +
  5. Salvar alterações
  6. +
+
+
+ +
+ +
+

+ Não responda este e-mail, pois foi enviado de forma + automatizada. +

+

Operações devparana.mx.

+
+ + diff --git a/apps/devmx/src/scss/_overrides.scss b/apps/devmx/src/scss/_overrides.scss index 46288587..ab164d14 100644 --- a/apps/devmx/src/scss/_overrides.scss +++ b/apps/devmx/src/scss/_overrides.scss @@ -1,3 +1,7 @@ +.cdk-overlay-backdrop { + background-color: rgba(0,0,0,.9); +} + ::ng-deep body .align-button.mat-mdc-button .mdc-button__label { display: inline-flex; align-items: center; diff --git a/apps/devmx/src/styles.scss b/apps/devmx/src/styles.scss index 8608911c..ee860dd0 100644 --- a/apps/devmx/src/styles.scss +++ b/apps/devmx/src/styles.scss @@ -1,4 +1,4 @@ -// @use '@angular/cdk/overlay-prebuilt.css'; +@use '@angular/cdk/overlay-prebuilt.css'; @use '@angular/material' as mat; @use './scss/theming/app-shell' as app-shell; @use './scss/theme' as theme; diff --git a/apps/server/src/assets/templates/user-tag.html b/apps/server/src/assets/templates/user-tag.html index deb85e75..cf7a4ac3 100644 --- a/apps/server/src/assets/templates/user-tag.html +++ b/apps/server/src/assets/templates/user-tag.html @@ -1,12 +1,79 @@ -
-

Olá {{displayName}}, tudo beleza?

- -

- Você foi marcado(a) em uma foto da comunidade, acesse o album - {{title}} para ver -

-
- -
- devparana mx -
+ + + + + +
+

Faala {{name}}, belê?

+ +

+ Alguém marcou você no album de fotos "{{title}}" + da comunidade. +

+ +
+

+ 🛟 Você pode remover sua tag da foto se preferir, algumas orientações + para isso: +

+
    +
  1. Clique no botão ☰ no lado direito acima da foto
  2. +
  3. Cliique em Remover minha tag
  4. +
  5. Salvar alterações
  6. +
+
+
+ +
+ +
+

+ Não responda este e-mail, pois foi enviado de forma + automatizada. +

+

Operações devparana.mx.

+
+ + diff --git a/apps/server/src/envs/env.dev.ts b/apps/server/src/envs/env.dev.ts index f935d44d..1eb9b570 100644 --- a/apps/server/src/envs/env.dev.ts +++ b/apps/server/src/envs/env.dev.ts @@ -2,6 +2,7 @@ import { join } from 'node:path'; export const env = { production: false, + origin: 'http://localhost:4200', db: { name: process.env.DB_NAME, host: process.env.DB_HOST, diff --git a/apps/server/src/envs/env.prod.ts b/apps/server/src/envs/env.prod.ts index d2edbdcc..6f8c6ca0 100644 --- a/apps/server/src/envs/env.prod.ts +++ b/apps/server/src/envs/env.prod.ts @@ -6,6 +6,7 @@ if (!process.env.MONGO_URI) { export const env = { production: true, + origin: 'https://devparana.mx', db: { name: process.env.DB_NAME, host: process.env.DB_HOST, diff --git a/packages/academy/domain/src/server/use-cases/update-course.ts b/packages/academy/domain/src/server/use-cases/update-course.ts index dab72764..2636f169 100644 --- a/packages/academy/domain/src/server/use-cases/update-course.ts +++ b/packages/academy/domain/src/server/use-cases/update-course.ts @@ -1,7 +1,7 @@ import { UseCase, Course, EditableCourse } from '@devmx/shared-api-interfaces'; import { CoursesService } from '../services'; -export class UpdateCourseUseCase implements UseCase { +export class UpdateCourseUseCase implements UseCase { constructor(private coursesService: CoursesService) {} async execute(data: EditableCourse) { diff --git a/packages/academy/domain/src/server/use-cases/update-institution.ts b/packages/academy/domain/src/server/use-cases/update-institution.ts index 8396e4ad..98132675 100644 --- a/packages/academy/domain/src/server/use-cases/update-institution.ts +++ b/packages/academy/domain/src/server/use-cases/update-institution.ts @@ -6,7 +6,7 @@ import { } from '@devmx/shared-api-interfaces'; export class UpdateInstitutionUseCase - implements UseCase + implements UseCase { constructor(private institutionsService: InstitutionsService) {} diff --git a/packages/academy/domain/src/server/use-cases/update-subject.ts b/packages/academy/domain/src/server/use-cases/update-subject.ts index 1d9c5620..0af120e3 100644 --- a/packages/academy/domain/src/server/use-cases/update-subject.ts +++ b/packages/academy/domain/src/server/use-cases/update-subject.ts @@ -5,7 +5,7 @@ import { EditableSubject, } from '@devmx/shared-api-interfaces'; -export class UpdateSubjectUseCase implements UseCase { +export class UpdateSubjectUseCase implements UseCase { constructor(private subjectsService: SubjectsService) {} async execute(data: EditableSubject) { diff --git a/packages/account/feature-shell/src/lib/containers/account/account.container.html b/packages/account/feature-shell/src/lib/containers/account/account.container.html index 1bbf1810..4781de5c 100644 --- a/packages/account/feature-shell/src/lib/containers/account/account.container.html +++ b/packages/account/feature-shell/src/lib/containers/account/account.container.html @@ -1,4 +1,81 @@ -
+ + + + +
+ + + + + + +
+ Informações públicas + + +
+ +
+ +
+
+ + + +
+
+ + + +
+
+ + + + +
+
+
+
+ + + +
+
+ +
+
+
+
+
+ + diff --git a/packages/account/feature-shell/src/lib/containers/account/account.container.scss b/packages/account/feature-shell/src/lib/containers/account/account.container.scss index 73be8b5b..7e4f75a9 100644 --- a/packages/account/feature-shell/src/lib/containers/account/account.container.scss +++ b/packages/account/feature-shell/src/lib/containers/account/account.container.scss @@ -4,13 +4,13 @@ padding: 1em; display: flex; flex-direction: column; - max-width: 980px; + // max-width: 980px; form { gap: 64px; display: flex; flex-direction: row; - + padding-top: 2em; @media (max-width: 768px) { flex-direction: column-reverse; } @@ -36,7 +36,7 @@ gap: 1em; display: flex; flex-direction: column; - max-width: 580px; + // max-width: 580px; } fieldset { diff --git a/packages/account/feature-shell/src/lib/containers/account/account.container.ts b/packages/account/feature-shell/src/lib/containers/account/account.container.ts index 55b86072..f1ac0643 100644 --- a/packages/account/feature-shell/src/lib/containers/account/account.container.ts +++ b/packages/account/feature-shell/src/lib/containers/account/account.container.ts @@ -11,6 +11,7 @@ import { UserPhoto, provideUserPhoto } from '../../dialogs'; import { MatButtonModule } from '@angular/material/button'; import { AvatarComponent } from '@devmx/shared-ui-global'; import { UpdatePhoto } from '@devmx/account-data-access'; +import { MatTabsModule } from '@angular/material/tabs'; import { UserComponent } from './user/user.component'; import { ReactiveFormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; @@ -27,6 +28,7 @@ import { ChangeDetectionStrategy, } from '@angular/core'; + @Component({ selector: 'devmx-account', templateUrl: './account.container.html', @@ -46,6 +48,7 @@ import { VisibilityComponent, SelectFileComponent, MatButtonModule, + MatTabsModule, AvatarComponent, RouterLink, ], diff --git a/packages/account/ui-shared/src/lib/components/search-user/search-user-except.pipe.ts b/packages/account/ui-shared/src/lib/components/search-user/search-user-except.pipe.ts new file mode 100644 index 00000000..494d067f --- /dev/null +++ b/packages/account/ui-shared/src/lib/components/search-user/search-user-except.pipe.ts @@ -0,0 +1,13 @@ +import { UserRef } from '@devmx/shared-api-interfaces'; +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'searchUserExcept', +}) +export class SearchUserExceptPipe implements PipeTransform { + transform(value: U[], except: U[] = []) { + if (except.length === 0) return value; + + return value.filter((user) => except.some((u) => u.id !== user.id)); + } +} diff --git a/packages/account/ui-shared/src/lib/components/search-user/search-user.component.html b/packages/account/ui-shared/src/lib/components/search-user/search-user.component.html index 77ab0bf3..c6ea2e58 100644 --- a/packages/account/ui-shared/src/lib/components/search-user/search-user.component.html +++ b/packages/account/ui-shared/src/lib/components/search-user/search-user.component.html @@ -1,7 +1,10 @@ {{ label() }} + + {{ hint() }} + @if (userFacade.response$ | async; as response) { - @for (option of response.data; track option.id) { + @for (option of response.data | searchUserExcept: except(); track $index) { {{ option.displayName }} } diff --git a/packages/account/ui-shared/src/lib/components/search-user/search-user.component.ts b/packages/account/ui-shared/src/lib/components/search-user/search-user.component.ts index 6286da74..b5b90457 100644 --- a/packages/account/ui-shared/src/lib/components/search-user/search-user.component.ts +++ b/packages/account/ui-shared/src/lib/components/search-user/search-user.component.ts @@ -1,12 +1,13 @@ import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { MatFormFieldModule } from '@angular/material/form-field'; +import { SearchUserExceptPipe } from './search-user-except.pipe'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { User, UserRef } from '@devmx/shared-api-interfaces'; import { MatInputModule } from '@angular/material/input'; import { UserFacade } from '@devmx/account-data-access'; -import { User } from '@devmx/shared-api-interfaces'; -import { AsyncPipe } from '@angular/common'; import { debounceTime, filter, startWith } from 'rxjs'; +import { AsyncPipe } from '@angular/common'; import { input, inject, @@ -23,6 +24,7 @@ import { imports: [ ReactiveFormsModule, MatAutocompleteModule, + SearchUserExceptPipe, MatFormFieldModule, MatInputModule, AsyncPipe, @@ -39,6 +41,8 @@ export class SearchUserComponent { control = new FormControl(''); + except = input([]); + constructor() { this.control.valueChanges .pipe( diff --git a/packages/album/data-access/src/lib/application/photo.facade.ts b/packages/album/data-access/src/lib/application/photo.facade.ts index 70e51714..1baed5d2 100644 --- a/packages/album/data-access/src/lib/application/photo.facade.ts +++ b/packages/album/data-access/src/lib/application/photo.facade.ts @@ -16,7 +16,6 @@ import { UploadPhotoUseCase, } from '@devmx/album-domain/client'; - export class PhotoFacade extends EntityFacade { constructor( private createPhotoUseCase: CreatePhotoUseCase, @@ -65,7 +64,7 @@ export class PhotoFacade extends EntityFacade { } updateTags(data: UpdatePhotoTags) { - this.onUpdate(this.updatePhotoTagsUseCase.execute(data)); + return this.updatePhotoTagsUseCase.execute(data); } delete(id: string) { diff --git a/packages/album/data-access/src/lib/resolvers/index.ts b/packages/album/data-access/src/lib/resolvers/index.ts index 89cdf822..0e6200a2 100644 --- a/packages/album/data-access/src/lib/resolvers/index.ts +++ b/packages/album/data-access/src/lib/resolvers/index.ts @@ -1 +1,2 @@ -export * from './album-resolver-wrapped'; +export * from './album-resolver-wrapped'; +export * from './photo-resolver-wrapped'; diff --git a/packages/album/data-access/src/lib/resolvers/photo-resolver-wrapped.ts b/packages/album/data-access/src/lib/resolvers/photo-resolver-wrapped.ts new file mode 100644 index 00000000..b54e2dee --- /dev/null +++ b/packages/album/data-access/src/lib/resolvers/photo-resolver-wrapped.ts @@ -0,0 +1,11 @@ +import { Params } from '@devmx/shared-api-interfaces'; +import { PhotoFacade } from '../application'; +import { distinctUntilChanged, filter } from 'rxjs'; + +export const photoResolverWrapped = (facade: PhotoFacade, params: Params) => { + facade.loadOne(params['id']); + return facade.selected$.pipe( + distinctUntilChanged(), + filter((photo) => !!photo) + ); +}; diff --git a/packages/album/data-source/src/lib/schemas/photo.ts b/packages/album/data-source/src/lib/schemas/photo.ts index 05832d64..b5aa108e 100644 --- a/packages/album/data-source/src/lib/schemas/photo.ts +++ b/packages/album/data-source/src/lib/schemas/photo.ts @@ -2,7 +2,6 @@ import { UserCollection } from '@devmx/account-data-source'; import { createSchema } from '@devmx/shared-data-source'; import { Prop, Schema } from '@nestjs/mongoose'; import mongoose, { Document } from 'mongoose'; -import { userTag } from './user-tag'; import { toBase64 } from '../utils'; import { Album, @@ -11,7 +10,6 @@ import { ImageMimeType, } from '@devmx/shared-api-interfaces'; - @Schema({ timestamps: { createdAt: true } }) export class PhotoCollection extends Document implements Photo { override id: string; @@ -53,8 +51,19 @@ export class PhotoCollection extends Document implements Photo { caption?: string; @Prop({ - type: [userTag], + type: [ + { + x: Number, + y: Number, + user: { + id: mongoose.Schema.Types.ObjectId, + displayName: String, + name: String, + }, + }, + ], default: [], + _id: false, }) tags?: UserTag[]; diff --git a/packages/album/domain/src/server/use-cases/add-photo.ts b/packages/album/domain/src/server/use-cases/add-photo.ts index 909db435..efb18ce5 100644 --- a/packages/album/domain/src/server/use-cases/add-photo.ts +++ b/packages/album/domain/src/server/use-cases/add-photo.ts @@ -4,7 +4,7 @@ import { AlbumsService } from '../services'; import { AddPhoto } from '../dtos'; import { createUseCaseProvider } from '@devmx/shared-util-data/server'; -export class AddPhotoUseCase implements UseCase { +export class AddPhotoUseCase implements UseCase { constructor(private albumsService: AlbumsService) {} async execute({ id, photo }: AddPhoto) { diff --git a/packages/album/domain/src/server/use-cases/update-album.ts b/packages/album/domain/src/server/use-cases/update-album.ts index ed2bde1f..37615b5b 100644 --- a/packages/album/domain/src/server/use-cases/update-album.ts +++ b/packages/album/domain/src/server/use-cases/update-album.ts @@ -2,7 +2,7 @@ import { Album, EditableAlbum, UseCase } from '@devmx/shared-api-interfaces'; import { createUseCaseProvider } from '@devmx/shared-util-data/server'; import { AlbumsService } from '../services'; -export class UpdateAlbumUseCase implements UseCase { +export class UpdateAlbumUseCase implements UseCase { constructor(private albumsService: AlbumsService) {} async execute(data: EditableAlbum) { diff --git a/packages/album/domain/src/server/use-cases/update-photo-tags.ts b/packages/album/domain/src/server/use-cases/update-photo-tags.ts index 91436a12..153068d6 100644 --- a/packages/album/domain/src/server/use-cases/update-photo-tags.ts +++ b/packages/album/domain/src/server/use-cases/update-photo-tags.ts @@ -1,9 +1,8 @@ import { createUseCaseProvider, render } from '@devmx/shared-util-data/server'; -import { MailerService } from '@devmx/shared-api-interfaces/server'; +import { Env, MailerService } from '@devmx/shared-api-interfaces/server'; +import { NotFoundError, RequestError } from '@devmx/shared-util-errors'; import { UsersService } from '@devmx/account-domain/server'; import { AlbumsService, PhotosService } from '../services'; -import { NotFoundError } from '@devmx/shared-util-errors'; -import { createMail } from '@devmx/shared-util-data'; import { Photo, UseCase, @@ -16,7 +15,8 @@ export class UpdatePhotoTagsUseCase implements UseCase { private photosService: PhotosService, private albumsService: AlbumsService, private mailerService: MailerService, - private usersService: UsersService + private usersService: UsersService, + private env: Env ) {} async execute(data: UpdatePhotoTags) { @@ -26,7 +26,9 @@ export class UpdatePhotoTagsUseCase implements UseCase { throw new NotFoundError(`Photo ${data.id} não encontrada`); } - const album = await this.albumsService.findOne(data.album); + const album = await this.albumsService.findOne( + typeof data.album === 'object' ? data.album['id'] : data.album + ); if (!album) { throw new NotFoundError(`Album ${data.album} não encontrado`); @@ -38,6 +40,12 @@ export class UpdatePhotoTagsUseCase implements UseCase { (tag) => !tagUserIds.has(tag.user.id) ); + const updated = await this.photosService.update(data.id, data); + + if (!updated) { + throw new RequestError(`Não foi possível alterar a foto`); + } + if (newTags.length > 0) { for (const tag of newTags) { const user = await this.usersService.findOne(tag.user.id); @@ -47,22 +55,23 @@ export class UpdatePhotoTagsUseCase implements UseCase { ); } - const title = album.title; - const displayName = user.displayName; - const url = `https://devparana.mx/#/albuns/${data.album}`; + const { title } = album; + const { name, contact } = user; + + const url = `${this.env.origin}/#/albuns/fotos/${data.id}`; - const mail = createMail( - user.contact.email, - render('user-tag.html', { displayName, url, title }), - `Você foi marcado em uma foto da comunidade`, - 'portal@devparana.mx' - ); + const substitutions = { name, url, title }; - await this.mailerService.send(mail); + await this.mailerService.send({ + to: contact.email, + from: 'portal@devparana.mx', + html: render('user-tag.html', substitutions), + subject: `Você foi marcado em uma foto da comunidade`, + }); } } - return this.photosService.update(data.id, data); + return updated; } } @@ -72,5 +81,6 @@ export function provideUpdatePhotoTagsUseCase() { AlbumsService, MailerService, UsersService, + Env, ]); } diff --git a/packages/album/domain/src/server/use-cases/update-photo.ts b/packages/album/domain/src/server/use-cases/update-photo.ts index 6d12b816..8de06181 100644 --- a/packages/album/domain/src/server/use-cases/update-photo.ts +++ b/packages/album/domain/src/server/use-cases/update-photo.ts @@ -2,7 +2,7 @@ import { Photo, EditablePhoto, UseCase } from '@devmx/shared-api-interfaces'; import { createUseCaseProvider } from '@devmx/shared-util-data/server'; import { PhotosService } from '../services'; -export class UpdatePhotoUseCase implements UseCase { +export class UpdatePhotoUseCase implements UseCase { constructor(private albumsService: PhotosService) {} async execute(data: EditablePhoto) { diff --git a/packages/album/feature-admin/src/lib/sheets/index.ts b/packages/album/feature-admin/src/lib/sheets/index.ts index d1f7bf4c..a7df6d0f 100644 --- a/packages/album/feature-admin/src/lib/sheets/index.ts +++ b/packages/album/feature-admin/src/lib/sheets/index.ts @@ -1,2 +1,4 @@ export * from './album-details/album-details.sheet'; export * from './create-album/create-album.sheet'; +export * from './photo-tags/photo-tags.sheet'; +export * from './photos/photos.sheet'; diff --git a/packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.service.ts b/packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.service.ts new file mode 100644 index 00000000..5c13ac64 --- /dev/null +++ b/packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.service.ts @@ -0,0 +1,17 @@ +import { MatBottomSheet } from '@angular/material/bottom-sheet'; +import { Photo } from '@devmx/shared-api-interfaces'; +import { PhotoTagsSheet } from './photo-tags.sheet'; + +export class PhotoTagsService { + constructor(private bottomSheet: MatBottomSheet) {} + + open(data: Photo) { + return this.bottomSheet.open(PhotoTagsSheet, { + data, + }); + } +} + +export function providePhotoTags() { + return { provide: PhotoTagsService, deps: [MatBottomSheet] }; +} diff --git a/packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.sheet.html b/packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.sheet.html new file mode 100644 index 00000000..e69de29b diff --git a/packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.sheet.scss b/packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.sheet.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.sheet.ts b/packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.sheet.ts new file mode 100644 index 00000000..854f1885 --- /dev/null +++ b/packages/album/feature-admin/src/lib/sheets/photo-tags/photo-tags.sheet.ts @@ -0,0 +1,21 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatListModule } from '@angular/material/list'; +import { Photo } from '@devmx/shared-api-interfaces'; +import { + MAT_BOTTOM_SHEET_DATA, + MatBottomSheetRef, +} from '@angular/material/bottom-sheet'; + +@Component({ + selector: 'devmx-photo-tags', + templateUrl: './photo-tags.sheet.html', + styleUrl: './photo-tags.sheet.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [MatListModule, MatButtonModule], +}) +export class PhotoTagsSheet { + ref = inject>(MatBottomSheetRef); + + data = inject(MAT_BOTTOM_SHEET_DATA); +} diff --git a/packages/album/feature-admin/src/lib/sheets/photos/photos.service.ts b/packages/album/feature-admin/src/lib/sheets/photos/photos.service.ts new file mode 100644 index 00000000..1456d66e --- /dev/null +++ b/packages/album/feature-admin/src/lib/sheets/photos/photos.service.ts @@ -0,0 +1,17 @@ +import { MatBottomSheet } from '@angular/material/bottom-sheet'; +import { Album } from '@devmx/shared-api-interfaces'; +import { PhotosSheet } from './photos.sheet'; + +export class PhotosService { + constructor(private bottomSheet: MatBottomSheet) {} + + open(data: Album) { + return this.bottomSheet.open(PhotosSheet, { + data, + }); + } +} + +export function providePhotos() { + return { provide: PhotosService, deps: [MatBottomSheet] }; +} diff --git a/packages/album/feature-admin/src/lib/sheets/photos/photos.sheet.html b/packages/album/feature-admin/src/lib/sheets/photos/photos.sheet.html new file mode 100644 index 00000000..fac48c51 --- /dev/null +++ b/packages/album/feature-admin/src/lib/sheets/photos/photos.sheet.html @@ -0,0 +1,16 @@ + + @for (photo of data.photos; track $index) { + + + {{photo.tags?.length}} + + + } + + +
+ + +
diff --git a/packages/album/feature-admin/src/lib/sheets/photos/photos.sheet.scss b/packages/album/feature-admin/src/lib/sheets/photos/photos.sheet.scss new file mode 100644 index 00000000..e69de29b diff --git a/packages/album/feature-admin/src/lib/sheets/photos/photos.sheet.ts b/packages/album/feature-admin/src/lib/sheets/photos/photos.sheet.ts new file mode 100644 index 00000000..02f089e5 --- /dev/null +++ b/packages/album/feature-admin/src/lib/sheets/photos/photos.sheet.ts @@ -0,0 +1,22 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { IconComponent } from '@devmx/shared-ui-global/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { MatListModule } from '@angular/material/list'; +import { Album } from '@devmx/shared-api-interfaces'; +import { + MAT_BOTTOM_SHEET_DATA, + MatBottomSheetRef, +} from '@angular/material/bottom-sheet'; + +@Component({ + selector: 'devmx-photos', + templateUrl: './photos.sheet.html', + styleUrl: './photos.sheet.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [MatListModule, MatButtonModule, IconComponent], +}) +export class PhotosSheet { + ref = inject>(MatBottomSheetRef); + + data = inject(MAT_BOTTOM_SHEET_DATA); +} diff --git a/packages/album/feature-shell/src/lib/album-feature-shell.routes.ts b/packages/album/feature-shell/src/lib/album-feature-shell.routes.ts index aeead3a1..5822b1f0 100644 --- a/packages/album/feature-shell/src/lib/album-feature-shell.routes.ts +++ b/packages/album/feature-shell/src/lib/album-feature-shell.routes.ts @@ -1,10 +1,14 @@ import { albumFeatureShellProviders } from './album-feature-shell.providers'; import { AlbumFeatureShellComponent } from './album-feature-shell.component'; -import { AlbumContainer, AlbumsContainer } from './containers'; +import { Album, Photo } from '@devmx/shared-api-interfaces'; import { rolesGuard } from '@devmx/shared-ui-global/guards'; -import { Album } from '@devmx/shared-api-interfaces'; -import { albumResolver } from './resolvers'; +import { albumResolver, photoResolver } from './resolvers'; import { Route } from '@angular/router'; +import { + AlbumContainer, + AlbumsContainer, + PhotoDetailsContainer, +} from './containers'; export const albumFeatureShellRoutes: Route[] = [ { @@ -24,12 +28,15 @@ export const albumFeatureShellRoutes: Route[] = [ ), }, { - path: '', + path: 'fotos/:id', data: { - breadcrumb: 'Albuns', + breadcrumb: (data: { photo: Photo & { album: Album } }) => { + return `Foto do album ` + data.photo.album.title; + }, }, - title: 'Albuns', - component: AlbumsContainer, + title: 'Foto', + resolve: { photo: photoResolver }, + component: PhotoDetailsContainer, }, { path: ':id', @@ -42,6 +49,14 @@ export const albumFeatureShellRoutes: Route[] = [ resolve: { album: albumResolver }, component: AlbumContainer, }, + { + path: '', + data: { + breadcrumb: 'Albuns', + }, + title: 'Albuns', + component: AlbumsContainer, + }, ], }, ]; diff --git a/packages/album/feature-shell/src/lib/containers/album/album.container.ts b/packages/album/feature-shell/src/lib/containers/album/album.container.ts index 7e934873..35b5ac91 100644 --- a/packages/album/feature-shell/src/lib/containers/album/album.container.ts +++ b/packages/album/feature-shell/src/lib/containers/album/album.container.ts @@ -1,9 +1,9 @@ -import { inject, Component, ChangeDetectionStrategy } from '@angular/core'; import { Album, Authentication, Photo } from '@devmx/shared-api-interfaces'; -import { AlbumFacade, PhotoFacade } from '@devmx/album-data-access'; +import { inject, Component, ChangeDetectionStrategy } from '@angular/core'; import { AuthenticationFacade } from '@devmx/account-data-access'; import { ActivatedRoute, RouterModule } from '@angular/router'; import { MatButtonModule } from '@angular/material/button'; +import { PhotoFacade } from '@devmx/album-data-access'; import { AsyncPipe } from '@angular/common'; import { filter, map, take } from 'rxjs'; import { @@ -24,7 +24,7 @@ export class AlbumContainer { route = inject(ActivatedRoute); authFacade = inject(AuthenticationFacade); - albumFacade = inject(AlbumFacade); + photoFacade = inject(PhotoFacade); photoViewer = inject(PhotoViewerService); @@ -35,10 +35,17 @@ export class AlbumContainer { ); open(photo: Photo, auth: Authentication, album: string) { - this.photoViewer - .open({ photo, auth }) - .closed.pipe(take(1)) - .subscribe(this.updateTags(album)); + const photoViewer = this.photoViewer.open({ photo, auth }); + photoViewer.updated$.subscribe((updated) => { + const tags = updated.tags ?? []; + + this.photoFacade + .updateTags({ ...updated, tags, album }) + .pipe(take(1)) + .subscribe((data) => { + photoViewer.updated(data); + }); + }); } updateTags = (album: string) => (photo?: Photo) => { diff --git a/packages/album/feature-shell/src/lib/containers/albums/albums.container.ts b/packages/album/feature-shell/src/lib/containers/albums/albums.container.ts index 6d074f87..15e0d8c8 100644 --- a/packages/album/feature-shell/src/lib/containers/albums/albums.container.ts +++ b/packages/album/feature-shell/src/lib/containers/albums/albums.container.ts @@ -45,10 +45,6 @@ export class AlbumsContainer { this.albumFacade.load(); }; - private mergeParams(params: Params) { - return { ...this.route.snapshot.queryParams, ...params }; - } - onPageChange(queryParams: PageParams) { this.router.navigate([], { queryParams }); } diff --git a/packages/album/feature-shell/src/lib/containers/index.ts b/packages/album/feature-shell/src/lib/containers/index.ts index 04d242cc..beb4a039 100644 --- a/packages/album/feature-shell/src/lib/containers/index.ts +++ b/packages/album/feature-shell/src/lib/containers/index.ts @@ -1,3 +1,5 @@ export * from './album-details/album-details.container'; +export * from './photo-details/photo-details.container'; export * from './albums/albums.container'; export * from './album/album.container'; +export * from './photo/photo.container'; diff --git a/packages/album/feature-shell/src/lib/containers/photo-details/photo-details.container.html b/packages/album/feature-shell/src/lib/containers/photo-details/photo-details.container.html new file mode 100644 index 00000000..67e3c3c1 --- /dev/null +++ b/packages/album/feature-shell/src/lib/containers/photo-details/photo-details.container.html @@ -0,0 +1,92 @@ +@if (photo$ | async; as photo) { + +@let auth = (authFacade.auth$ | async)!; + + + + + + + + + +
+
+ + @for (tag of photo.tags; track $index) { +
+ +
+ } +
+
+ + +
+
+ Ninguém foi marcado ainda + Está na foto: + Estão na foto: +
+
+ @for (tag of photo.tags; track $index; let first = $first; let last = + $last) { + + + @if (!first && !last) {,} + + @else if (last) { + e + } + + {{tag.user.displayName}} + + + } +
+
+
+ + + +
+ +} diff --git a/packages/album/feature-shell/src/lib/containers/photo-details/photo-details.container.scss b/packages/album/feature-shell/src/lib/containers/photo-details/photo-details.container.scss new file mode 100644 index 00000000..5fd03937 --- /dev/null +++ b/packages/album/feature-shell/src/lib/containers/photo-details/photo-details.container.scss @@ -0,0 +1,121 @@ +@use '@angular/material' as mat; + +:host { + flex: 1; + padding: 1em; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + .photo-header { + display: flex; + position: relative; + height: 40px; + + .more-menu { + position: absolute; + right: 8px; + top: 8px; + } + } + + figure { + margin: 0; + width: 100%; + max-height: 100%; + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + overflow: hidden; + user-select: none; + + img { + display: flex; + // z-index: 10; + width: 100%; + height: 100%; + max-width: 100%; + cursor: context-menu; + object-fit: pointer; + user-select: none; + } + + .photo-viewer { + position: relative; + } + + .user-tag { + position: absolute; + transform: translate(-50%, -50%); + display: flex; + align-items: flex-end; + justify-content: flex-end; + transition: opacity 250ms ease-in-out; + color: #f9e000; + width: 64px; + height: 64px; + } + } + + .photo-content { + dl { + display: flex; + gap: 0.2em; + } + dd { + margin: 0; + display: flex; + gap: 0.1em; + } + + a { + display: inline-flex; + margin-left: 0.2em; + } + span { + display: inline-flex; + margin-left: 0.2em; + } + } + + // figure { + // margin: 0; + // gap: 0.6em; + // display: flex; + // flex-direction: column; + // border-radius: 1em; + // overflow: hidden; + + // @include mat.elevation(2); + + // img { + // display: flex; + // } + + // figcaption { + // padding: 0.2em 1em 0; + + // dl { + // display: flex; + // gap: 0.4em; + // } + // dd { + // margin: 0; + // display: flex; + // gap: 0.1em; + // } + + // a { + // display: inline-flex; + // margin-left: 0.2em; + // } + // span { + // display: inline-flex; + // margin-left: 0.2em; + // } + // } + // } +} diff --git a/packages/album/feature-shell/src/lib/containers/photo-details/photo-details.container.ts b/packages/album/feature-shell/src/lib/containers/photo-details/photo-details.container.ts new file mode 100644 index 00000000..41c909c3 --- /dev/null +++ b/packages/album/feature-shell/src/lib/containers/photo-details/photo-details.container.ts @@ -0,0 +1,129 @@ +import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; +import { AsyncPipe, NgPlural, NgPluralCase } from '@angular/common'; +import { AuthenticationFacade } from '@devmx/account-data-access'; +import { ActivatedRoute, RouterLink } from '@angular/router'; +import { IconComponent } from '@devmx/shared-ui-global/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { ClipboardModule } from '@angular/cdk/clipboard'; +import { PhotoFacade } from '@devmx/album-data-access'; +import { MatCardModule } from '@angular/material/card'; +import { MatMenuModule } from '@angular/material/menu'; +import { + of, + map, + tap, + take, + timer, + concat, + filter, + BehaviorSubject, +} from 'rxjs'; +import { + inject, + signal, + Component, + viewChildren, + ChangeDetectionStrategy, +} from '@angular/core'; +import { + Album, + Photo, + UserTag, + Authentication, + UpdatePhotoTags, +} from '@devmx/shared-api-interfaces'; + +@Component({ + selector: 'devmx-photo-details', + templateUrl: './photo-details.container.html', + styleUrl: './photo-details.container.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + MatCardModule, + MatTooltipModule, + MatButtonModule, + MatMenuModule, + ClipboardModule, + IconComponent, + RouterLink, + NgPlural, + NgPluralCase, + AsyncPipe, + ], +}) +export class PhotoDetailsContainer { + route = inject(ActivatedRoute); + + authFacade = inject(AuthenticationFacade); + + photoFacade = inject(PhotoFacade); + + tooltips = viewChildren(MatTooltip); + + #photo = new BehaviorSubject(null); + photo$ = this.#photo.asObservable(); + + resolved$ = this.route.data.pipe( + filter((data) => 'photo' in data), + map((data) => data['photo'] as Photo & { album: Album }) + ); + + keepEquals = signal(true); + + shareLabel = signal('Compartilhar'); + + constructor() { + this.resolved$.subscribe((photo) => { + if (photo) { + this.#photo.next(photo); + } + }); + } + + share(refTooltip: MatTooltip) { + const set = (label: string) => { + return tap(() => this.shareLabel.set(label)); + }; + + concat( + timer(500), + of('').pipe(set('Link copiado')), + of('').pipe(tap(() => refTooltip.show())), + timer(2000), + of('').pipe(tap(() => refTooltip.hide())), + timer(1000), + of('').pipe(set('Compartilhar')) + ).subscribe(); + } + + getUserFromTags(tags: UserTag[] = []) { + return tags.map((tag) => tag.user.displayName); + } + + toggleTags(photo: Photo) { + if (!photo.tags?.length) { + return; + } + + const tooltips = this.tooltips(); + + for (let i = 0; i < tooltips.length - 1; i++) { + tooltips[i].toggle(); + } + } + + hasTagWithMe(photo: Photo, user: Authentication) { + return photo.tags?.some((tag) => tag.user.id === user.id); + } + + removeMyTag(photo: Photo, user: Authentication) { + const tags = photo.tags?.filter((tag) => tag.user.id !== user.id) ?? []; + + this.photoFacade + .updateTags({ ...photo, tags } as UpdatePhotoTags) + .pipe(take(1)) + .subscribe((updated) => { + this.#photo.next(updated); + }); + } +} diff --git a/packages/album/feature-shell/src/lib/containers/photo/photo.container.html b/packages/album/feature-shell/src/lib/containers/photo/photo.container.html new file mode 100644 index 00000000..cad81ab5 --- /dev/null +++ b/packages/album/feature-shell/src/lib/containers/photo/photo.container.html @@ -0,0 +1,25 @@ +@if (photo$ | async; as photo) { +
+ + +
+

Foto do {{photo.album.title}}.

+ + @if (photo.tags?.length) { +

+ + Ninguém foi marcado ainda + Está na foto: + Estão na foto: + + + @for (tag of photo.tags; track $index) { + + {{tag.user.displayName}} + + } +

+ } +
+
+} diff --git a/packages/album/feature-shell/src/lib/containers/photo/photo.container.scss b/packages/album/feature-shell/src/lib/containers/photo/photo.container.scss new file mode 100644 index 00000000..fbf5a1d5 --- /dev/null +++ b/packages/album/feature-shell/src/lib/containers/photo/photo.container.scss @@ -0,0 +1,22 @@ +:host { + flex: 1; + padding: 1em; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + + figure { + margin: 0; + gap: 0.6em; + display: flex; + flex-direction: column; + border-radius: 1em; + overflow: hidden; + + img { + display: flex; + } + } +} diff --git a/packages/album/feature-shell/src/lib/containers/photo/photo.container.ts b/packages/album/feature-shell/src/lib/containers/photo/photo.container.ts new file mode 100644 index 00000000..be7d36d4 --- /dev/null +++ b/packages/album/feature-shell/src/lib/containers/photo/photo.container.ts @@ -0,0 +1,25 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { AsyncPipe, NgPlural, NgPluralCase } from '@angular/common'; +import { Album, Photo, UserTag } from '@devmx/shared-api-interfaces'; +import { ActivatedRoute, RouterLink } from '@angular/router'; +import { filter, map } from 'rxjs'; + +@Component({ + selector: 'devmx-photo', + templateUrl: './photo.container.html', + styleUrl: './photo.container.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [RouterLink, NgPlural, NgPluralCase, AsyncPipe], +}) +export class PhotoContainer { + route = inject(ActivatedRoute); + + photo$ = this.route.data.pipe( + filter((data) => 'photo' in data), + map((data) => data['photo'] as Photo & { album: Album }) + ); + + getUserFromTags(tags: UserTag[] = []) { + return tags.map((tag) => tag.user.displayName); + } +} diff --git a/packages/album/feature-shell/src/lib/resolvers/index.ts b/packages/album/feature-shell/src/lib/resolvers/index.ts index 4acaf634..8a0c44ba 100644 --- a/packages/album/feature-shell/src/lib/resolvers/index.ts +++ b/packages/album/feature-shell/src/lib/resolvers/index.ts @@ -1 +1,2 @@ -export * from './album'; +export * from './album'; +export * from './photo'; diff --git a/packages/album/feature-shell/src/lib/resolvers/photo.ts b/packages/album/feature-shell/src/lib/resolvers/photo.ts new file mode 100644 index 00000000..95ab3958 --- /dev/null +++ b/packages/album/feature-shell/src/lib/resolvers/photo.ts @@ -0,0 +1,8 @@ +import { PhotoFacade, photoResolverWrapped } from '@devmx/album-data-access'; +import { Photo } from '@devmx/shared-api-interfaces'; +import { ResolveFn } from '@angular/router'; +import { inject } from '@angular/core'; + +export const photoResolver: ResolveFn = (route) => { + return photoResolverWrapped(inject(PhotoFacade), route.params); +}; diff --git a/packages/album/ui-shared/package.json b/packages/album/ui-shared/package.json index 2f6b2450..5c53ccd6 100644 --- a/packages/album/ui-shared/package.json +++ b/packages/album/ui-shared/package.json @@ -9,7 +9,8 @@ "@devmx/shared-api-interfaces": "0.0.1", "@angular/cdk": "19.0.0", "@devmx/account-ui-shared": "0.0.1", - "@devmx/shared-util-data": "0.0.1" + "@devmx/shared-util-data": "0.0.1", + "rxjs": "^7.8.0" }, "sideEffects": false } diff --git a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.html b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.html index 62740887..b4aaabd9 100644 --- a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.html +++ b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.html @@ -17,20 +17,26 @@ -
- + @for (tag of data.photo.tags; track $index) {
@@ -42,12 +48,32 @@
+ + + + Clique/toque e segure por ½ segundo para marcar alguém na foto. Se não + encontrar ela aqui, use o botão aqui do lado esquerdo + : ) + +
} diff --git a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.scss b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.scss index 2e7d5c03..47c6dd10 100644 --- a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.scss +++ b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.scss @@ -17,6 +17,7 @@ figure { margin: 0; width: 100%; + max-height: 100%; position: relative; display: flex; flex-direction: column; @@ -44,16 +45,12 @@ position: absolute; transform: translate(-50%, -50%); display: flex; - align-items: center; + align-items: flex-end; justify-content: flex-end; - color: #f9e000; - opacity: 0.2; transition: opacity 250ms ease-in-out; + color: #f9e000; width: 64px; height: 64px; - &:hover { - opacity: 1; - } } } @@ -61,6 +58,20 @@ gap: 1em; padding: 0.6em; display: flex; - justify-content: flex-end; + flex-flow: row wrap; + justify-content: space-between; + align-items: center; + + small { + flex: 1; + line-height: 140%; + font-weight: normal; + } + + .mat-mdc-icon-button { + devmx-icon { + display: flex; + } + } } } diff --git a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.ts b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.ts index 572669f6..82da843d 100644 --- a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.ts +++ b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.component.ts @@ -1,9 +1,12 @@ import { Authentication, Photo, UserTag } from '@devmx/shared-api-interfaces'; -import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog'; import { IconComponent } from '@devmx/shared-ui-global/icon'; import { MatButtonModule } from '@angular/material/button'; +import { ClipboardModule } from '@angular/cdk/clipboard'; import { MatMenuModule } from '@angular/material/menu'; +import { arrayEquals } from '@devmx/shared-util-data'; +import { concat, of, Subject, tap, timer } from 'rxjs'; import { PhotoViewerData } from './photo-viewer-data'; import { detectTaggingIntent } from '../utils'; import { @@ -12,6 +15,7 @@ import { Component, viewChild, ElementRef, + viewChildren, AfterViewInit, ChangeDetectorRef, ChangeDetectionStrategy, @@ -27,76 +31,146 @@ import { styleUrl: './photo-viewer.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, providers: [provideTagUserService()], - imports: [MatTooltipModule, MatButtonModule, MatMenuModule, IconComponent], + imports: [ + MatTooltipModule, + MatButtonModule, + MatMenuModule, + ClipboardModule, + IconComponent, + ], }) export class PhotoViewerComponent implements AfterViewInit { tagUser = inject(TagUserService); - cdr = inject(ChangeDetectorRef); + #cdr = inject(ChangeDetectorRef); - ref = inject>(DialogRef); + #ref = inject>(DialogRef); data = inject(DIALOG_DATA); - originalTags: UserTag[] = []; + tooltips = viewChildren(MatTooltip); + + #originalTags: UserTag[] = []; imageRef = viewChild.required>('imageRef'); - image!: HTMLImageElement; + keepEquals = signal(true); - hasUpdates = signal(false); + shareLabel = signal('Compartilhar'); - hasTagWithMe(user: Authentication) { - return this.data.photo.tags?.some((tag) => tag.user.id === user.id); + #photo = new Subject(); + photo$ = this.#photo.asObservable(); + + #updated = new Subject(); + updated$ = this.#updated.asObservable(); + + updated(photo: Photo) { + this.#photo.next(photo); + } + + update(photo: Photo) { + this.#updated.next(photo); + } + + share(refTooltip: MatTooltip) { + const set = (label: string) => { + return tap(() => this.shareLabel.set(label)); + }; + + concat( + timer(500), + of('').pipe(set('Link copiado')), + of('').pipe(tap(() => refTooltip.show())), + timer(2000), + of('').pipe(tap(() => refTooltip.hide())), + timer(1000), + of('').pipe(set('Compartilhar')) + ).subscribe(); } removeMyTag(user: Authentication) { this.data.photo.tags = this.data.photo.tags?.filter( (tag) => tag.user.id !== user.id ); - const hasUpdates = this.arraysAreEqual( - this.originalTags, + + const keepEquals = arrayEquals( + this.#originalTags, this.data.photo.tags ?? [] ); - this.hasUpdates.set(!hasUpdates); + + this.keepEquals.set(keepEquals); } ngAfterViewInit() { - this.originalTags = [...(this.data.photo.tags ?? [])]; + this.#originalTags = [...(this.data.photo.tags ?? [])]; + + this.photo$.subscribe((photo) => { + this.data.photo = photo; + this.#originalTags = [...(this.data.photo.tags ?? [])]; + this.#cdr.detectChanges(); + + const keepEquals = arrayEquals( + this.#originalTags, + this.data.photo.tags ?? [] + ); - this.image = this.imageRef().nativeElement; + this.keepEquals.set(keepEquals); + }); + + const image = this.imageRef().nativeElement; - this.image.oncontextmenu = (ev) => ev.preventDefault(); + image.oncontextmenu = (ev) => ev.preventDefault(); - detectTaggingIntent(this.image).subscribe(({ x, y }) => { - const offsetX = x * this.image.width; - const offsetY = y * this.image.height; + detectTaggingIntent(image).subscribe((position) => { + const offsetX = position.x * image.width; + const offsetY = position.y * image.height; - const closed$ = this.tagUser.open(this.image, offsetX, offsetY).closed; + const except = this.data.photo.tags?.map((tag) => tag.user) ?? []; - closed$.subscribe((userRef) => { - if (userRef && this.data.photo.tags) { - const { id, displayName, name } = userRef; + const closed$ = this.tagUser.open({ + target: image, + data: { except }, + offsetX, + offsetY, + }).closed; + + closed$.subscribe((ref) => { + if (ref) { + const { id, displayName, name } = ref; const user = { id, displayName, name }; - this.data.photo.tags.push({ x: x * 100, y: y * 100, user }); - const hasUpdates = this.arraysAreEqual( - this.originalTags, - this.data.photo.tags + + const x = position.x * 100; + const y = position.y * 100; + + this.data.photo.tags?.push({ x, y, user }); + this.#cdr.detectChanges(); + + const keepEquals = arrayEquals( + this.#originalTags, + this.data.photo.tags ?? [] ); - this.hasUpdates.set(!hasUpdates); - this.cdr.detectChanges(); + + this.keepEquals.set(keepEquals); } }); }); } - arraysAreEqual(a: T[], b: T[]) { - if (a.length !== b.length) return false; + toggleTags() { + if (!this.data.photo.tags?.length) { + return; + } - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false; + for (const tooltip of this.tooltips()) { + tooltip.toggle(); } + } + + hasTagWithMe(user: Authentication) { + return this.data.photo.tags?.some((tag) => tag.user.id === user.id); + } - return true; + close() { + this.#ref.close(); } } diff --git a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.service.ts b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.service.ts index 41f0baf4..f15d1191 100644 --- a/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.service.ts +++ b/packages/album/ui-shared/src/lib/components/photo-viewer/photo-viewer.service.ts @@ -7,14 +7,30 @@ export class PhotoViewerService { constructor(private dialog: Dialog) {} open(data: PhotoViewerData) { - const width = `${data.photo.width}px`; - const height = `${data.photo.height}px`; - const disableClose = true; + const photoViewer = this.dialog.open< + Photo, + PhotoViewerData, + PhotoViewerComponent + >(PhotoViewerComponent, { + width: `${data.photo.width}px`, + height: `${data.photo.height}px`, + disableClose: true, + hasBackdrop: true, + maxHeight: `80%`, + maxWidth: `80%`, + data, + }); - return this.dialog.open( - PhotoViewerComponent, - { data, width, height, disableClose } - ); + const component = photoViewer.componentInstance; + + if (!component) { + throw `Erro ao criar instância PhotoViewer`; + } + + return { + updated$: component.updated$, + updated: (value: Photo) => component.updated(value), + }; } } diff --git a/packages/album/ui-shared/src/lib/components/tag-user/tag-user-data.ts b/packages/album/ui-shared/src/lib/components/tag-user/tag-user-data.ts new file mode 100644 index 00000000..aa89f108 --- /dev/null +++ b/packages/album/ui-shared/src/lib/components/tag-user/tag-user-data.ts @@ -0,0 +1,5 @@ +import { UserRef } from '@devmx/shared-api-interfaces'; + +export interface TagUserData { + except?: UserRef[]; +} diff --git a/packages/album/ui-shared/src/lib/components/tag-user/tag-user-options.ts b/packages/album/ui-shared/src/lib/components/tag-user/tag-user-options.ts new file mode 100644 index 00000000..59acfebf --- /dev/null +++ b/packages/album/ui-shared/src/lib/components/tag-user/tag-user-options.ts @@ -0,0 +1,8 @@ +import { TagUserData } from './tag-user-data'; + +export interface TagUserOptions { + target: HTMLElement; + offsetX?: number; + offsetY?: number; + data?: TagUserData; +} diff --git a/packages/album/ui-shared/src/lib/components/tag-user/tag-user.component.ts b/packages/album/ui-shared/src/lib/components/tag-user/tag-user.component.ts index fb04bb8c..6dc4ab51 100644 --- a/packages/album/ui-shared/src/lib/components/tag-user/tag-user.component.ts +++ b/packages/album/ui-shared/src/lib/components/tag-user/tag-user.component.ts @@ -1,11 +1,15 @@ import { inject, Component, ChangeDetectionStrategy } from '@angular/core'; import { SearchUserComponent } from '@devmx/account-ui-shared'; +import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog'; import { UserRef } from '@devmx/shared-api-interfaces'; -import { DialogRef } from '@angular/cdk/dialog'; +import { TagUserData } from './tag-user-data'; @Component({ selector: 'devmx-tag-user', - template: ``, + template: ``, changeDetection: ChangeDetectionStrategy.OnPush, imports: [SearchUserComponent], styles: ` @@ -19,4 +23,6 @@ import { DialogRef } from '@angular/cdk/dialog'; }) export class TagUserComponent { dialogRef = inject>(DialogRef); + + data = inject(DIALOG_DATA); } diff --git a/packages/album/ui-shared/src/lib/components/tag-user/tag-user.service.ts b/packages/album/ui-shared/src/lib/components/tag-user/tag-user.service.ts index ef233baf..cdb76f42 100644 --- a/packages/album/ui-shared/src/lib/components/tag-user/tag-user.service.ts +++ b/packages/album/ui-shared/src/lib/components/tag-user/tag-user.service.ts @@ -1,12 +1,14 @@ import { TagUserComponent } from './tag-user.component'; import { UserRef } from '@devmx/shared-api-interfaces'; +import { TagUserOptions } from './tag-user-options'; import { Overlay } from '@angular/cdk/overlay'; +import { TagUserData } from './tag-user-data'; import { Dialog } from '@angular/cdk/dialog'; export class TagUserService { constructor(private overlay: Overlay, private dialog: Dialog) {} - open(target: HTMLElement, offsetX = 0, offsetY = 0) { + open({ target, offsetX = 0, offsetY = 0, data }: TagUserOptions) { const panelClass = 'tag-user-position'; const positionStrategy = this.overlay @@ -24,9 +26,10 @@ export class TagUserService { }, ]); - return this.dialog.open(TagUserComponent, { - positionStrategy, - }); + return this.dialog.open( + TagUserComponent, + { positionStrategy, data } + ); } } diff --git a/packages/event/domain/src/server/use-cases/update-event.ts b/packages/event/domain/src/server/use-cases/update-event.ts index 0936bf06..4c669589 100644 --- a/packages/event/domain/src/server/use-cases/update-event.ts +++ b/packages/event/domain/src/server/use-cases/update-event.ts @@ -3,7 +3,7 @@ import { Event, UseCase } from '@devmx/shared-api-interfaces'; import { EventsService } from '../services'; import { UpdateEvent } from '../../lib/dtos'; -export class UpdateEventUseCase implements UseCase { +export class UpdateEventUseCase implements UseCase { constructor(private eventsService: EventsService) {} async execute(data: UpdateEvent) { diff --git a/packages/event/feature-shell/src/lib/containers/events/events.container.html b/packages/event/feature-shell/src/lib/containers/events/events.container.html index 31519fb2..920c5fd8 100644 --- a/packages/event/feature-shell/src/lib/containers/events/events.container.html +++ b/packages/event/feature-shell/src/lib/containers/events/events.container.html @@ -1,15 +1,11 @@
-
- + - -
+ -
- + - -
+
@defer (on timer(500ms)) { diff --git a/packages/event/feature-shell/src/lib/containers/events/events.container.scss b/packages/event/feature-shell/src/lib/containers/events/events.container.scss index b71ab8d2..528d546e 100644 --- a/packages/event/feature-shell/src/lib/containers/events/events.container.scss +++ b/packages/event/feature-shell/src/lib/containers/events/events.container.scss @@ -1,19 +1,16 @@ :host { flex: 1; display: flex; + padding-top: 0.6em; flex-direction: column; header { + gap: 0.4em; display: flex; flex-flow: row wrap; + align-items: baseline; justify-content: space-between; padding: 0.4em 0.8em; - - div { - gap: 1em; - display: flex; - align-items: baseline; - } } .events-container { diff --git a/packages/learn/domain/src/server/use-cases/update-skill.ts b/packages/learn/domain/src/server/use-cases/update-skill.ts index 007e1313..85ec3913 100644 --- a/packages/learn/domain/src/server/use-cases/update-skill.ts +++ b/packages/learn/domain/src/server/use-cases/update-skill.ts @@ -1,7 +1,9 @@ import { UseCase, Skill, EditableSkill } from '@devmx/shared-api-interfaces'; import { SkillsService } from '../services'; -export class UpdateSkillUseCase implements UseCase { +export class UpdateSkillUseCase + implements UseCase +{ constructor(private skillsService: SkillsService) {} async execute(data: EditableSkill) { diff --git a/packages/shared/api-interfaces/src/lib/interfaces/update.ts b/packages/shared/api-interfaces/src/lib/interfaces/update.ts index 7b8978d2..1518c94d 100644 --- a/packages/shared/api-interfaces/src/lib/interfaces/update.ts +++ b/packages/shared/api-interfaces/src/lib/interfaces/update.ts @@ -3,5 +3,8 @@ import { Entity } from './entity'; import { Observable } from 'rxjs'; export interface Update { - update(id: string, data: EditableEntity): Observable | Promise; + update( + id: string, + data: EditableEntity + ): Observable | Promise; } diff --git a/packages/shared/api-interfaces/src/server/dtos/send-mail.ts b/packages/shared/api-interfaces/src/server/dtos/send-mail.ts index 5a2de179..e8e8d7f8 100644 --- a/packages/shared/api-interfaces/src/server/dtos/send-mail.ts +++ b/packages/shared/api-interfaces/src/server/dtos/send-mail.ts @@ -2,5 +2,13 @@ import { SendMailOptions } from 'nodemailer'; export type SendMail = Pick< SendMailOptions, - 'from' | 'html' | 'replyTo' | 'subject' | 'text' | 'to' + | 'attachDataUrls' + | 'attachments' + | 'replyTo' + | 'subject' + | 'text' + | 'from' + | 'html' + | 'amp' + | 'to' >; diff --git a/packages/shared/api-interfaces/src/server/envs/env.ts b/packages/shared/api-interfaces/src/server/envs/env.ts index d10f3ca9..e6c2123d 100644 --- a/packages/shared/api-interfaces/src/server/envs/env.ts +++ b/packages/shared/api-interfaces/src/server/envs/env.ts @@ -1,6 +1,8 @@ export abstract class Env { abstract production: boolean; + abstract origin: string; + abstract db: { name: string; host: string; diff --git a/packages/shared/api-interfaces/src/server/index.ts b/packages/shared/api-interfaces/src/server/index.ts index 42593050..eec75b61 100644 --- a/packages/shared/api-interfaces/src/server/index.ts +++ b/packages/shared/api-interfaces/src/server/index.ts @@ -2,3 +2,4 @@ export * from './interfaces'; export * from './ports'; export * from './types'; export * from './envs'; +export * from './dtos'; diff --git a/packages/shared/api-interfaces/src/server/ports/entity.service.ts b/packages/shared/api-interfaces/src/server/ports/entity.service.ts index cad986b2..6dfb82e0 100644 --- a/packages/shared/api-interfaces/src/server/ports/entity.service.ts +++ b/packages/shared/api-interfaces/src/server/ports/entity.service.ts @@ -24,7 +24,7 @@ export abstract class EntityService value: T[P] ): Promise; - abstract update(id: string, data: EditableEntity): Promise; + abstract update(id: string, data: EditableEntity): Promise; abstract delete(id: string): Promise; } diff --git a/packages/shared/data-source/src/lib/infrastructure/mongo.service.ts b/packages/shared/data-source/src/lib/infrastructure/mongo.service.ts index 9c7332f6..2cb5664f 100644 --- a/packages/shared/data-source/src/lib/infrastructure/mongo.service.ts +++ b/packages/shared/data-source/src/lib/infrastructure/mongo.service.ts @@ -87,12 +87,10 @@ export abstract class MongoService async update(id: string, data: EditableEntity) { const value = this.applyEditableParser(data); + await this.entityModel.findOneAndUpdate({ _id: id }, value).exec(); try { - const updated = await this.entityModel - .findOneAndUpdate({ _id: id }, value) - .exec(); - return updated?.toJSON() as T; + return this.findOne(id) } catch (err) { if (err instanceof Error) { throw err; diff --git a/packages/shared/data-source/src/lib/utils/create-schema.ts b/packages/shared/data-source/src/lib/utils/create-schema.ts index 64c3e281..bfda45bb 100644 --- a/packages/shared/data-source/src/lib/utils/create-schema.ts +++ b/packages/shared/data-source/src/lib/utils/create-schema.ts @@ -28,10 +28,10 @@ export function createSchema(target: new () => T): Schema { return this._id.toString(); }); - // schema.set('timestamps', { - // createdAt: true, - // updatedAt: true, - // }); + schema.set('timestamps', { + createdAt: true, + updatedAt: true, + }); schema.set('toJSON', { virtuals: true, diff --git a/packages/shared/ui-global/icon/src/lib/types/icon.ts b/packages/shared/ui-global/icon/src/lib/types/icon.ts index 6b30b31a..a910329f 100644 --- a/packages/shared/ui-global/icon/src/lib/types/icon.ts +++ b/packages/shared/ui-global/icon/src/lib/types/icon.ts @@ -103,6 +103,7 @@ type Root = | 'send' | 'sent' | 'settings' + | 'share' | 'slash' | 'star-check' | 'star-close' diff --git a/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.html b/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.html index 7edf6056..24e10a76 100644 --- a/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.html +++ b/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.html @@ -1,5 +1,13 @@ -{{ current() === 'asc' ? ascText() : descText() }} + + @for (direction of directions; track direction.value) { + + {{ direction.viewValue }} + + } + + + diff --git a/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.ts b/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.ts index 782439f5..b5e3ebf5 100644 --- a/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.ts +++ b/packages/shared/ui-global/sort/src/lib/sort-direction/sort-direction.component.ts @@ -1,32 +1,38 @@ -import { NgClass } from '@angular/common'; -import { Component, input, output, signal } from '@angular/core'; -import { MatButtonModule } from '@angular/material/button'; +import { ChangeDetectionStrategy, Component, output } from '@angular/core'; import { SortDirection } from '@devmx/shared-api-interfaces'; -import { IconComponent } from '@devmx/shared-ui-global/icon'; +import { FormOption } from '@devmx/shared-ui-global/forms'; +import { MatChipsModule } from '@angular/material/chips'; @Component({ exportAs: 'sortDirection', selector: 'devmx-sort-direction', templateUrl: './sort-direction.component.html', styleUrl: './sort-direction.component.scss', - imports: [MatButtonModule, IconComponent, NgClass], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [MatChipsModule], }) export class SortDirectionComponent { - ascText = input(''); + sortChange = output(); - descText = input(''); + directions: FormOption[] = [ + { value: 'asc', viewValue: 'ASC' }, + { value: 'desc', viewValue: 'DESC' }, + ]; + // ascText = input(''); - sortChange = output(); + // descText = input(''); + + // sortChange = output(); - current = signal('asc'); + // current = signal('asc'); - toggle() { - if (this.current() === 'asc') { - this.current.set('desc'); - } else { - this.current.set('asc'); - } + // toggle() { + // if (this.current() === 'asc') { + // this.current.set('desc'); + // } else { + // this.current.set('asc'); + // } - this.sortChange.emit(this.current()); - } + // this.sortChange.emit(this.current()); + // } } diff --git a/packages/shared/util-data/src/lib/utils/array-equals.ts b/packages/shared/util-data/src/lib/utils/array-equals.ts new file mode 100644 index 00000000..16351c18 --- /dev/null +++ b/packages/shared/util-data/src/lib/utils/array-equals.ts @@ -0,0 +1,9 @@ +export function arrayEquals(a: T[], b: T[]) { + if (a.length !== b.length) return false; + + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + + return true; +} diff --git a/packages/shared/util-data/src/lib/utils/create-mail.ts b/packages/shared/util-data/src/lib/utils/create-mail.ts index fdb6b8e0..9d257ebf 100644 --- a/packages/shared/util-data/src/lib/utils/create-mail.ts +++ b/packages/shared/util-data/src/lib/utils/create-mail.ts @@ -1,3 +1,5 @@ +import { SendMail } from '@devmx/shared-api-interfaces/server'; + export function createMail( to: string, html: string, @@ -8,3 +10,38 @@ export function createMail( return { from, to, subject, html }; } + +export type CreateMailTemplateOptions = Omit< + SendMail, + 'to' | 'html' | 'subject' +> & { + withFooter?: boolean; +}; + +export function createMailTemplate( + to: string, + html: string, + subject = `DevParana.mx`, + extraOptions: CreateMailTemplateOptions = { } +): SendMail { + const logo = ` + + + + + + + + + `; + + return { + to, + ...extraOptions, + html: extraOptions.withFooter ? html + logo : html, + attachments: [ + extraOptions.withFooter ? { content: logo, filename: 'logo.svg' } : {}, + ], + subject: subject === 'DevParana.mx' ? subject : `${subject} - DevParana.mx`, + }; +} diff --git a/packages/shared/util-data/src/lib/utils/index.ts b/packages/shared/util-data/src/lib/utils/index.ts index ee357be4..7fd89553 100644 --- a/packages/shared/util-data/src/lib/utils/index.ts +++ b/packages/shared/util-data/src/lib/utils/index.ts @@ -1,3 +1,4 @@ +export * from './array-equals'; export * from './async'; export * from './by'; export * from './create-code'; diff --git a/packages/shared/util-data/src/server/templates/index.ts b/packages/shared/util-data/src/server/templates/index.ts index 193c205c..219554ce 100644 --- a/packages/shared/util-data/src/server/templates/index.ts +++ b/packages/shared/util-data/src/server/templates/index.ts @@ -1 +1,2 @@ +export * from './reader'; export * from './render'; diff --git a/packages/shared/util-data/src/server/templates/reader.ts b/packages/shared/util-data/src/server/templates/reader.ts new file mode 100644 index 00000000..f00b440c --- /dev/null +++ b/packages/shared/util-data/src/server/templates/reader.ts @@ -0,0 +1,7 @@ +import { createReadStream } from 'node:fs'; +import { join } from 'node:path'; + +export function reader(file: string) { + const path = join(__dirname, 'assets', 'templates', file); + return createReadStream(path, { encoding: 'utf-8' }); +}