From 1784868ea6f1619f3cf29b5e1555e0afd2df009e Mon Sep 17 00:00:00 2001 From: Juanfran Date: Wed, 22 Jan 2025 19:29:22 +0100 Subject: [PATCH] feat: pinned toolbars --- .../board/actions/board-page.actions.ts | 1 + .../board-toolbar.component.html | 108 +++++++++--------- .../board-toolbar.component.scss | 5 + .../board-toolbar/board-toolbar.component.ts | 90 +++++++++++++-- .../cocomaterial/cocomaterial.component.scss | 2 +- .../cocomaterial/cocomaterial.component.ts | 4 + .../live-reaction/live-reaction.component.ts | 5 + .../board/components/note/note.component.ts | 6 + .../board/reducers/boardPage.reducer.ts | 8 ++ package.json | 3 +- pnpm-lock.yaml | 15 +++ 11 files changed, 185 insertions(+), 62 deletions(-) diff --git a/apps/web/src/app/modules/board/actions/board-page.actions.ts b/apps/web/src/app/modules/board/actions/board-page.actions.ts index 551a0db0..9a6f63dd 100644 --- a/apps/web/src/app/modules/board/actions/board-page.actions.ts +++ b/apps/web/src/app/modules/board/actions/board-page.actions.ts @@ -42,6 +42,7 @@ export const BoardPageActions = createActionGroup({ Redo: emptyProps(), 'Toggle user highlight': props<{ id: User['id'] }>(), 'Set popup open': props<{ popup: string }>(), + 'Set popup pinned': props<{ pinned: boolean }>(), 'Ready to vote': emptyProps(), 'Board not found': props<{ id: BoardUser['id'] }>(), 'Select emoji': props<{ emoji: NativeEmoji }>(), diff --git a/apps/web/src/app/modules/board/components/board-toolbar/board-toolbar.component.html b/apps/web/src/app/modules/board/components/board-toolbar/board-toolbar.component.html index 999c483d..e3040c41 100644 --- a/apps/web/src/app/modules/board/components/board-toolbar/board-toolbar.component.html +++ b/apps/web/src/app/modules/board/components/board-toolbar/board-toolbar.component.html @@ -79,58 +79,62 @@ @defer (prefetch on idle) { -
- @if (popup() === 'token') { - - - } - - @if (popup() === 'tools') { - - - } - - @if (popup() === 'note') { - - - } - - @if (popup() === 'emoji') { - - - } - - @if (popup() === 'templates') { - - - } - - @if (popup() === 'cocomaterial') { - - } - - @if (popup() === 'live-reaction') { - - } - - @if (popup() === 'image') { - - - } -
+ @if (showPopup()) { +
+
+ @if (showPin()) { +
+
+ +
+
+ } + + @switch (popup()) { + @case ('token') { + + } + @case ('tools') { + + } + @case ('note') { + + } + @case ('emoji') { + + } + @case ('templates') { + + } + @case ('cocomaterial') { + + } + @case ('live-reaction') { + + } + @case ('image') { + + } + } +
+
+ } } diff --git a/apps/web/src/app/modules/board/components/board-toolbar/board-toolbar.component.scss b/apps/web/src/app/modules/board/components/board-toolbar/board-toolbar.component.scss index 99d6fefe..afea3502 100644 --- a/apps/web/src/app/modules/board/components/board-toolbar/board-toolbar.component.scss +++ b/apps/web/src/app/modules/board/components/board-toolbar/board-toolbar.component.scss @@ -44,3 +44,8 @@ hr { padding: var(--spacing-4); box-shadow: 0 0 15px 3px rgba(66, 62, 82, 0.1); } + +.toolbar-pinned { + justify-items: end; + padding-block-end: var(--spacing-2); +} diff --git a/apps/web/src/app/modules/board/components/board-toolbar/board-toolbar.component.ts b/apps/web/src/app/modules/board/components/board-toolbar/board-toolbar.component.ts index 0b10fb72..9d2ac32c 100644 --- a/apps/web/src/app/modules/board/components/board-toolbar/board-toolbar.component.ts +++ b/apps/web/src/app/modules/board/components/board-toolbar/board-toolbar.component.ts @@ -5,6 +5,7 @@ import { inject, signal, HostListener, + computed, } from '@angular/core'; import { Store } from '@ngrx/store'; import { BoardActions } from '../../actions/board.actions'; @@ -45,6 +46,9 @@ import { ToolsComponent } from '../tools/tools.component'; import { defaultNoteColor } from '../note'; import { NgTemplateOutlet } from '@angular/common'; import { BoardToolbardButtonComponent } from './components/board-toolboard-button.component'; +import { LucideAngularModule, Pin, PinOff } from 'lucide-angular'; + +export class AppModule {} @Component({ selector: 'tapiz-board-toolbar', templateUrl: './board-toolbar.component.html', @@ -63,6 +67,7 @@ import { BoardToolbardButtonComponent } from './components/board-toolboard-butto ToolsComponent, NgTemplateOutlet, BoardToolbardButtonComponent, + LucideAngularModule, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], providers: [HotkeysService], @@ -76,10 +81,44 @@ export class BoardToolbarComponent { #zoneService = inject(ZoneService); #fileUploadService = inject(FileUploadService); + icons = { + pin: Pin, + pinOff: PinOff, + }; + toolbarSubscription?: Subscription; boardMode = this.#store.selectSignal(boardPageFeature.selectBoardMode); popup = this.#store.selectSignal(boardPageFeature.selectPopupOpen); + pinned = this.#store.selectSignal(boardPageFeature.selectPopupPinned); noteColor = signal(defaultNoteColor); + showPopup = computed(() => { + const withPopup = [ + 'token', + 'tools', + 'note', + 'emoji', + 'templates', + 'cocomaterial', + 'live-reaction', + 'image', + ]; + + return withPopup.includes(this.popup()); + }); + + showPin = computed(() => { + const withPin = [ + 'token', + 'note', + 'emoji', + 'templates', + 'cocomaterial', + 'live-reaction', + 'image', + ]; + + return withPin.includes(this.popup()); + }); @HostListener('document:keydown.alt', ['$event']) selectAreaShortcut( e: KeyboardEvent, @@ -213,6 +252,21 @@ export class BoardToolbarComponent { } note() { + const createNote = () => { + console.log('create note'); + this.toolbarSubscription = this.#zoneService + .select() + .subscribe(({ userId, position }) => { + this.#notesService.createNote(userId, position, this.noteColor()); + + if (!this.pinned()) { + this.popupOpen(''); + } else { + createNote(); + } + }); + }; + if (this.popup() === 'note') { this.popupOpen(''); return; @@ -220,12 +274,7 @@ export class BoardToolbarComponent { this.popupOpen('note'); - this.toolbarSubscription = this.#zoneService - .select() - .subscribe(({ userId, position }) => { - this.#notesService.createNote(userId, position, this.noteColor()); - this.popupOpen(''); - }); + createNote(); } select() { @@ -410,6 +459,7 @@ export class BoardToolbarComponent { } emojiSelected(emojiEvent: EmojiClickEvent) { + console.log(emojiEvent); this.#store.dispatch( BoardPageActions.selectEmoji({ emoji: emojiEvent.detail.emoji as NativeEmoji, @@ -466,7 +516,9 @@ export class BoardToolbarComponent { this.toolbarSubscription = this.#zoneService .select() .subscribe(({ position }) => { - this.popupOpen(''); + if (!this.pinned()) { + this.popupOpen(''); + } const tokenContent: Token = { ...token, @@ -543,6 +595,14 @@ export class BoardToolbarComponent { this.popupOpen(''); } + togglePinned() { + this.#store.dispatch( + BoardPageActions.setPopupPinned({ + pinned: this.pinned() ? false : true, + }), + ); + } + templateSelector() { if (this.popup() === 'templates') { this.popupOpen(''); @@ -553,6 +613,20 @@ export class BoardToolbarComponent { } seletedTemplate() { - this.popupOpen(''); + if (!this.pinned()) { + this.popupOpen(''); + } + } + + cocomaterialSelected() { + if (!this.pinned()) { + this.popupOpen(''); + } + } + + reactionSelected() { + if (!this.pinned()) { + this.popupOpen(''); + } } } diff --git a/apps/web/src/app/modules/board/components/cocomaterial/cocomaterial.component.scss b/apps/web/src/app/modules/board/components/cocomaterial/cocomaterial.component.scss index 1b2212b9..90a1e46f 100644 --- a/apps/web/src/app/modules/board/components/cocomaterial/cocomaterial.component.scss +++ b/apps/web/src/app/modules/board/components/cocomaterial/cocomaterial.component.scss @@ -1,6 +1,6 @@ :host { display: block; - padding: var(--spacing-4); + padding: 0 var(--spacing-4) var(--spacing-4) var(--spacing-4); inline-size: 495px; } diff --git a/apps/web/src/app/modules/board/components/cocomaterial/cocomaterial.component.ts b/apps/web/src/app/modules/board/components/cocomaterial/cocomaterial.component.ts index ba543cd2..777293a5 100644 --- a/apps/web/src/app/modules/board/components/cocomaterial/cocomaterial.component.ts +++ b/apps/web/src/app/modules/board/components/cocomaterial/cocomaterial.component.ts @@ -6,6 +6,7 @@ import { computed, inject, signal, + output, } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; @@ -56,6 +57,7 @@ export class CocomaterialComponent { #boardMoveService = inject(BoardMoveService); #nodesActions = inject(NodesActions); tagInput = viewChild.required>('tagInput'); + cocomaterialSelected = output(); boardMode = this.#store.selectSignal(boardPageFeature.selectBoardMode); @@ -197,6 +199,8 @@ export class CocomaterialComponent { ], }), ); + + this.cocomaterialSelected.emit(vector); } } diff --git a/apps/web/src/app/modules/board/components/live-reaction/live-reaction.component.ts b/apps/web/src/app/modules/board/components/live-reaction/live-reaction.component.ts index 79415d97..ed29ce6a 100644 --- a/apps/web/src/app/modules/board/components/live-reaction/live-reaction.component.ts +++ b/apps/web/src/app/modules/board/components/live-reaction/live-reaction.component.ts @@ -3,6 +3,7 @@ import { Component, computed, inject, + output, signal, } from '@angular/core'; import { BoardMoveService } from '../../services/board-move.service'; @@ -66,6 +67,8 @@ export class LiveReactionComponent { #http = inject(HttpClient); selected = signal(''); + reactionSelected = output(); + category = signal('Smilies'); normalizedCategory = computed(() => { @@ -158,6 +161,8 @@ export class LiveReactionComponent { x: data.position.x - 50, y: data.position.y - 50, }); + + this.reactionSelected.emit(this.selected()); }); } diff --git a/apps/web/src/app/modules/board/components/note/note.component.ts b/apps/web/src/app/modules/board/components/note/note.component.ts index db7c99d8..09afdbc4 100644 --- a/apps/web/src/app/modules/board/components/note/note.component.ts +++ b/apps/web/src/app/modules/board/components/note/note.component.ts @@ -36,6 +36,7 @@ import { NoteHeightCalculatorService } from './components/note-height-calculator import { defaultNoteColor } from '.'; import { BoardFacade } from '../../../../services/board-facade.service'; import { boardPageFeature } from '../../reducers/boardPage.reducer'; +import { BoardPageActions } from '../../actions/board-page.actions'; @Component({ selector: 'tapiz-note', @@ -84,6 +85,7 @@ export class NoteComponent { #activeToolbarOption = this.#store.selectSignal( boardPageFeature.selectPopupOpen, ); + #toolbarPinned = this.#store.selectSignal(boardPageFeature.selectPopupPinned); #mentions = this.#store.selectSignal(boardPageFeature.selectMentions); #emoji = this.#store.selectSignal(boardPageFeature.selectEmoji); #userHighlight = this.#store.selectSignal( @@ -577,6 +579,10 @@ export class NoteComponent { }), ); } + + if (!this.#toolbarPinned()) { + this.#store.dispatch(BoardPageActions.setPopupOpen({ popup: '' })); + } } onDrop() { diff --git a/apps/web/src/app/modules/board/reducers/boardPage.reducer.ts b/apps/web/src/app/modules/board/reducers/boardPage.reducer.ts index 1e31978d..e4d1b8b0 100644 --- a/apps/web/src/app/modules/board/reducers/boardPage.reducer.ts +++ b/apps/web/src/app/modules/board/reducers/boardPage.reducer.ts @@ -26,6 +26,7 @@ export interface BoardPageState { showUserVotes: User['id'] | null; boardMode: number; popupOpen: string; + popupPinned: boolean; isAdmin: boolean; privateId: string; owners: string[]; @@ -69,6 +70,7 @@ const initialPageState: BoardPageState = { showUserVotes: null, boardMode: 0, popupOpen: '', + popupPinned: false, isAdmin: false, privateId: '', owners: [], @@ -308,6 +310,12 @@ const reducer = createReducer( return state; }), + on(BoardPageActions.setPopupPinned, (state, { pinned }): BoardPageState => { + return { + ...state, + popupPinned: pinned, + }; + }), on(BoardPageActions.readyToVote, (state): BoardPageState => { return { ...state, diff --git a/package.json b/package.json index 5fddf3db..4036a59c 100644 --- a/package.json +++ b/package.json @@ -58,8 +58,8 @@ "@ngrx/store": "19.0.0", "@ngrx/store-devtools": "19.0.0", "@nx/angular": "20.3.2", - "@rx-angular/template": "^19.1.0", "@rx-angular/state": "^19.0.2", + "@rx-angular/template": "^19.1.0", "@simonwep/pickr": "^1.9.0", "@tiptap/core": "2.9.1", "@tiptap/extension-bold": "2.9.1", @@ -94,6 +94,7 @@ "fastify-socket.io": "^5.1.0", "jsdom": "^24.0.0", "lucia": "^3.2.0", + "lucide-angular": "^0.473.0", "ngx-infinite-scroll": "^19.0.0", "ngxtension": "^4.1.0", "open-props": "^1.7.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d3cbbf9..b87517a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -203,6 +203,9 @@ importers: lucia: specifier: ^3.2.0 version: 3.2.0 + lucide-angular: + specifier: ^0.473.0 + version: 0.473.0(@angular/common@19.1.0(@angular/core@19.1.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0(rxjs@7.8.1)(zone.js@0.15.0)) ngx-infinite-scroll: specifier: ^19.0.0 version: 19.0.0(@angular/common@19.1.0(@angular/core@19.1.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0(rxjs@7.8.1)(zone.js@0.15.0)) @@ -7178,6 +7181,12 @@ packages: lucia@3.2.0: resolution: {integrity: sha512-eXMxXwk6hqtjRTj4W/x3EnTUtAztLPm0p2N2TEBMDEbakDLXiYnDQ9z/qahjPdPdhPguQc+vwO0/88zIWxlpuw==} + lucide-angular@0.473.0: + resolution: {integrity: sha512-V2g0j8/pMm2qQcwPohIrez1pwXEXTlnjiTHCcJ7dXCxQQDaaupBJhaDG6W5ZvApPjWx1zeDlV3ORLHLqjKK9rw==} + peerDependencies: + '@angular/common': 13.x - 19.x + '@angular/core': 13.x - 19.x + luxon@3.5.0: resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} engines: {node: '>=12'} @@ -17843,6 +17852,12 @@ snapshots: dependencies: oslo: 1.2.0 + lucide-angular@0.473.0(@angular/common@19.1.0(@angular/core@19.1.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.1.0(rxjs@7.8.1)(zone.js@0.15.0)): + dependencies: + '@angular/common': 19.1.0(@angular/core@19.1.0(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1) + '@angular/core': 19.1.0(rxjs@7.8.1)(zone.js@0.15.0) + tslib: 2.8.1 + luxon@3.5.0: {} magic-string@0.30.10: