diff --git a/apps/web/src/app/modules/board/board/board.component.html b/apps/web/src/app/modules/board/board/board.component.html index e1db1fd4..d524422d 100644 --- a/apps/web/src/app/modules/board/board/board.component.html +++ b/apps/web/src/app/modules/board/board/board.component.html @@ -53,7 +53,8 @@ } @defer { - + + @if (!isDemo) { } diff --git a/apps/web/src/app/modules/board/board/board.component.ts b/apps/web/src/app/modules/board/board/board.component.ts index 4d9a644d..736634ff 100644 --- a/apps/web/src/app/modules/board/board/board.component.ts +++ b/apps/web/src/app/modules/board/board/board.component.ts @@ -97,6 +97,7 @@ import { PopupPortalComponent } from '@tapiz/ui/popup/popup-portal.component'; import { NotesVisibilityComponent } from '../components/notes-visibility/notes-visibility.component'; import { NoteHeightCalculatorComponent } from '@tapiz/nodes/note'; import { BoardDragDirective } from './directives/board-drag.directive'; +import { BoardHeaderOptionsComponent } from '../components/board-header-options/board-header-options.component'; @Component({ selector: 'tapiz-board', @@ -132,6 +133,7 @@ import { BoardDragDirective } from './directives/board-drag.directive'; PopupPortalComponent, NotesVisibilityComponent, NoteHeightCalculatorComponent, + BoardHeaderOptionsComponent, ], hostDirectives: [ CopyPasteDirective, diff --git a/apps/web/src/app/modules/board/components/board-header-options/board-header-options.component.html b/apps/web/src/app/modules/board/components/board-header-options/board-header-options.component.html new file mode 100644 index 00000000..599542b4 --- /dev/null +++ b/apps/web/src/app/modules/board/components/board-header-options/board-header-options.component.html @@ -0,0 +1,55 @@ +@if (boardMode() === 0) { + @if (isAdmin() && !isDemo) { + + } + + +} + +@if (allowSwitchMode()) { +
+ @if (boardMode() === 0) { + + } @else { + + } +
+} + +@if (boardMode() === 0 && !isDemo && showUsers() && users().length > 0) { + +} diff --git a/apps/web/src/app/modules/board/components/board-header-options/board-header-options.component.scss b/apps/web/src/app/modules/board/components/board-header-options/board-header-options.component.scss new file mode 100644 index 00000000..80b0fdf7 --- /dev/null +++ b/apps/web/src/app/modules/board/components/board-header-options/board-header-options.component.scss @@ -0,0 +1,165 @@ +:host { + --toolbar-button-height: 3rem; + --toolbar-button-width: 3rem; + + align-items: center; + cursor: default; + display: flex; + justify-content: space-between; + right: 1rem; + position: fixed; + top: 1rem; + transition: background 0.5s ease; + background: var(--blur-bg); + border-radius: var(--radius-6); + gap: var(--spacing-1); + margin-inline-start: auto; + flex-shrink: 0; + padding: var(--size-2); + height: 65px; + backdrop-filter: blur(90px); +} + +.change-mode-button-holder { + display: flex; +} + +.change-mode-button { + align-items: center; + display: flex; + font-family: + "Inter", + -apple-system, + system-ui, + sans-serif; + justify-content: center; + block-size: 50px; + padding: var(--spacing-3) var(--spacing-4); + color: var(--grey-90); + background: var(--grey-30); + border-radius: var(--radius-6); + font-size: var(--font-size-1); + + mat-icon { + --icon-size: 16px; + inline-size: var(--icon-size); + block-size: var(--icon-size); + font-size: var(--icon-size); + margin-block-start: 1px; + } + + &:hover { + background: var(--grey-40); + } +} + +.change-edit mat-icon { + margin-inline-end: 2px; +} + +.change-close mat-icon { + margin-inline-start: 4px; +} + +.textarea { + color: var(--white); + background: transparent; + border: none; + height: 100%; + outline: none; + overflow: hidden; + resize: none; +} + +.button-action { + display: flex; + align-items: center; + color: var(--primary-color-button-bg); + border-radius: var(--radius-round); + padding: var(--spacing-2); + justify-content: center; + height: var(--toolbar-button-height); + width: var(--toolbar-button-width); + .mat-icon { + color: var(--primary-color-button-bg); + fill: var(--primary-color-button-bg); + } + &:hover { + transition: background 0.2s ease; + background: var(--grey-30); + } +} + +.avatar { + border-radius: var(--radius-round); + width: 48px; + height: 48px; + img { + display: block; + } +} + +.user-count { + align-items: center; + background: var(--grey-30); + border-radius: 30px; + box-sizing: border-box; + color: #2c2c2c; + display: inline-flex; + font-family: + "Work Sans", + -apple-system, + system-ui, + sans-serif; + font-size: var(--font-size-1); + font-weight: 400; + height: 44px; + width: 44px; + justify-content: center; + line-height: 1.2; + margin-left: 8px; + position: relative; + + &:hover { + background: var(--grey-40); + + .user-hover { + background: var(--grey-90); + border-radius: 20px; + color: var(--white); + display: flex; + align-items: center; + font-size: 16px; + font-weight: var(--font-weight-medium); + line-height: 1.2; + height: 44px; + width: 200%; + padding: 0 16px; + opacity: 1; + + &::after { + content: ""; + position: absolute; + top: 50%; /* Start at vertical center */ + left: calc(100% - 0.18rem); /* Align to the right edge */ + transform: translateY(-50%); /* Center vertically */ + + /* Create the triangle using borders */ + width: 0; + height: 0; + border-left: 10px solid #1f255a; /* Color matches the bubble background */ + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + } + } + } + + .user-hover { + height: 0; + width: 0; + opacity: 0; + position: absolute; + right: calc(100% + 0.5rem); + top: 0; + } +} diff --git a/apps/web/src/app/modules/board/components/board-header-options/board-header-options.component.ts b/apps/web/src/app/modules/board/components/board-header-options/board-header-options.component.ts new file mode 100644 index 00000000..718d68e7 --- /dev/null +++ b/apps/web/src/app/modules/board/components/board-header-options/board-header-options.component.ts @@ -0,0 +1,112 @@ +import { + Component, + ChangeDetectionStrategy, + inject, + input, +} from '@angular/core'; +import { Store } from '@ngrx/store'; +import { PageActions } from '../../actions/page.actions'; +import { selectIsAdmin, selectUserId } from '../../selectors/page.selectors'; +import { ExportService } from '../../services/export.service'; +import { MatIconModule } from '@angular/material/icon'; +import { MatDialog, MatDialogModule } from '@angular/material/dialog'; +import { ShareBoardComponent } from '../share-board/share-board.component'; +import { pageFeature } from '../../reducers/page.reducer'; +import { BoardSettingsComponent } from '../board-settings/board-settings.component'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { map } from 'rxjs'; +import { computed } from '@angular/core'; +import { ConfigService } from '../../../../services/config.service'; +import { MatButtonModule } from '@angular/material/button'; +import { BoardFacade } from '../../../../services/board-facade.service'; + +@Component({ + selector: 'tapiz-board-header-options', + templateUrl: './board-header-options.component.html', + styleUrls: ['./board-header-options.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [MatIconModule, MatDialogModule, MatButtonModule], +}) +export class BoardHeaderOptionsComponent { + #exportService = inject(ExportService); + #store = inject(Store); + #dialog = inject(MatDialog); + #boardFacade = inject(BoardFacade); + #boardUsers = this.#store.selectSignal(pageFeature.selectBoardUsers); + #configService = inject(ConfigService); + #users = toSignal( + this.#boardFacade + .getUsers() + .pipe(map((users) => users.map((user) => user.content))), + { initialValue: [] }, + ); + userId = this.#store.selectSignal(selectUserId); + users = computed(() => { + const boardUsers = this.#boardUsers(); + + return this.#users() + .filter((user) => user.id !== this.userId()) + .map((user) => { + const boardUser = boardUsers.find( + (boardUser) => boardUser.id === user.id, + ); + + return { + ...user, + picture: boardUser?.picture, + }; + }); + }); + #settings = toSignal(this.#boardFacade.getSettings(), { initialValue: null }); + + showUsers = computed(() => { + return !this.#settings()?.content.anonymousMode; + }); + + allowSwitchMode = input(true); + + boardMode = this.#store.selectSignal(pageFeature.selectBoardMode); + isAdmin = this.#store.selectSignal(selectIsAdmin); + boardId = this.#store.selectSignal(pageFeature.selectBoardId); + + get isDemo() { + return !!this.#configService.config.DEMO; + } + + changeBoardMode(boardMode: number) { + this.#store.dispatch( + PageActions.changeBoardMode({ + boardMode, + }), + ); + } + + export() { + this.#exportService.getExportFile().then( + (url) => { + const fileLink = document.createElement('a'); + + fileLink.href = url; + fileLink.download = `${this.boardId()}.json`; + fileLink.click(); + }, + () => { + console.error('export error'); + }, + ); + } + + share() { + this.#dialog.open(ShareBoardComponent, { + width: '600px', + autoFocus: 'dialog', + }); + } + + settings() { + this.#dialog.open(BoardSettingsComponent, { + width: '600px', + autoFocus: 'dialog', + }); + } +} diff --git a/apps/web/src/app/modules/board/components/header/header.component.html b/apps/web/src/app/modules/board/components/header/header.component.html index ff88c0aa..28f1473b 100644 --- a/apps/web/src/app/modules/board/components/header/header.component.html +++ b/apps/web/src/app/modules/board/components/header/header.component.html @@ -47,60 +47,3 @@ } @else { Demo } -
- @if (boardMode() === 0) { - @if (isAdmin() && !isDemo) { - - } - - - } - - @if (allowSwitchMode()) { -
- @if (boardMode() === 0) { - - } @else { - - } -
- } - - @if (boardMode() === 0 && !isDemo && showUsers() && users().length > 0) { - - } -
diff --git a/apps/web/src/app/modules/board/components/header/header.component.scss b/apps/web/src/app/modules/board/components/header/header.component.scss index 9b37a56e..d20494d5 100644 --- a/apps/web/src/app/modules/board/components/header/header.component.scss +++ b/apps/web/src/app/modules/board/components/header/header.component.scss @@ -1,17 +1,12 @@ :host { - --toolbar-button-height: 3rem; - --toolbar-button-width: 3rem; - align-items: center; cursor: default; display: flex; justify-content: space-between; - left: 0; - padding: 1rem; + left: 1rem; position: fixed; - top: 0; + top: 1rem; transition: background 0.5s ease; - width: 100%; } .logo { @@ -68,60 +63,6 @@ padding: 0; } -.change-mode-button-holder { - display: flex; -} - -.change-mode-button { - align-items: center; - display: flex; - font-family: - "Inter", - -apple-system, - system-ui, - sans-serif; - justify-content: center; - block-size: 50px; - padding: var(--spacing-3) var(--spacing-4); - color: var(--grey-90); - background: var(--grey-30); - border-radius: var(--radius-6); - font-size: var(--font-size-1); - - mat-icon { - --icon-size: 16px; - inline-size: var(--icon-size); - block-size: var(--icon-size); - font-size: var(--icon-size); - margin-block-start: 1px; - } - - &:hover { - background: var(--grey-40); - } -} - -.change-edit mat-icon { - margin-inline-end: 2px; -} - -.change-close mat-icon { - margin-inline-start: 4px; -} - -.header-end { - background: var(--blur-bg); - border-radius: var(--radius-6); - display: flex; - align-items: center; - gap: var(--spacing-1); - margin-inline-start: auto; - flex-shrink: 0; - position: relative; - padding: var(--size-2); - height: 65px; - backdrop-filter: blur(90px); -} .textarea { color: var(--white); @@ -132,96 +73,3 @@ overflow: hidden; resize: none; } - -.button-action { - display: flex; - align-items: center; - color: var(--primary-color-button-bg); - border-radius: var(--radius-round); - padding: var(--spacing-2); - justify-content: center; - height: var(--toolbar-button-height); - width: var(--toolbar-button-width); - .mat-icon { - color: var(--primary-color-button-bg); - fill: var(--primary-color-button-bg); - } - &:hover { - transition: background 0.2s ease; - background: var(--grey-30); - } -} - -.avatar { - border-radius: var(--radius-round); - width: 48px; - height: 48px; - img { - display: block; - } -} - -.user-count { - align-items: center; - background: var(--grey-30); - border-radius: 30px; - box-sizing: border-box; - color: #2c2c2c; - display: inline-flex; - font-family: - "Work Sans", - -apple-system, - system-ui, - sans-serif; - font-size: var(--font-size-1); - font-weight: 400; - height: 44px; - width: 44px; - justify-content: center; - line-height: 1.2; - margin-left: 8px; - position: relative; - - &:hover { - background: var(--grey-40); - - .user-hover { - background: var(--grey-90); - border-radius: 20px; - color: var(--white); - display: flex; - align-items: center; - font-size: 16px; - font-weight: var(--font-weight-medium); - line-height: 1.2; - height: 44px; - width: 200%; - padding: 0 16px; - opacity: 1; - - &::after { - content: ""; - position: absolute; - top: 50%; /* Start at vertical center */ - left: calc(100% - 0.18rem); /* Align to the right edge */ - transform: translateY(-50%); /* Center vertically */ - - /* Create the triangle using borders */ - width: 0; - height: 0; - border-left: 10px solid #1f255a; /* Color matches the bubble background */ - border-top: 6px solid transparent; - border-bottom: 6px solid transparent; - } - } - } - - .user-hover { - height: 0; - width: 0; - opacity: 0; - position: absolute; - right: calc(100% + 0.5rem); - top: 0; - } -} diff --git a/apps/web/src/app/modules/board/components/header/header.component.ts b/apps/web/src/app/modules/board/components/header/header.component.ts index c8ad5efe..a1f9f8a3 100644 --- a/apps/web/src/app/modules/board/components/header/header.component.ts +++ b/apps/web/src/app/modules/board/components/header/header.component.ts @@ -5,34 +5,22 @@ import { signal, inject, viewChild, - input, } from '@angular/core'; import { Store } from '@ngrx/store'; import { BoardActions } from '../../actions/board.actions'; import { PageActions } from '../../actions/page.actions'; -import { selectIsAdmin, selectUserId } from '../../selectors/page.selectors'; -import { ExportService } from '../../services/export.service'; +import { selectIsAdmin } from '../../selectors/page.selectors'; import { ClickOutside } from 'ngxtension/click-outside'; import { AutoFocusDirective } from '../../directives/autofocus.directive'; import { RouterLink } from '@angular/router'; import { MatIconModule } from '@angular/material/icon'; -import { MatDialog, MatDialogModule } from '@angular/material/dialog'; -import { ShareBoardComponent } from '../share-board/share-board.component'; import { pageFeature } from '../../reducers/page.reducer'; -import { BoardSettingsComponent } from '../board-settings/board-settings.component'; import { HotkeysService } from '@tapiz/cdk/services/hostkeys.service'; -import { - takeUntilDestroyed, - toObservable, - toSignal, -} from '@angular/core/rxjs-interop'; -import { map, switchMap } from 'rxjs'; -import { computed } from '@angular/core'; +import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'; +import { switchMap } from 'rxjs'; import { ConfigService } from '../../../../services/config.service'; import { NgOptimizedImage } from '@angular/common'; -import { appFeature } from '../../../../+state/app.reducer'; import { MatButtonModule } from '@angular/material/button'; -import { BoardFacade } from '../../../../services/board-facade.service'; @Component({ selector: 'tapiz-header', @@ -44,61 +32,23 @@ import { BoardFacade } from '../../../../services/board-facade.service'; AutoFocusDirective, ClickOutside, MatIconModule, - MatDialogModule, MatButtonModule, NgOptimizedImage, ], providers: [HotkeysService], }) export class HeaderComponent { - #exportService = inject(ExportService); #store = inject(Store); - #dialog = inject(MatDialog); #hotkeysService = inject(HotkeysService); - #boardFacade = inject(BoardFacade); - #boardUsers = this.#store.selectSignal(pageFeature.selectBoardUsers); #configService = inject(ConfigService); - #users = toSignal( - this.#boardFacade - .getUsers() - .pipe(map((users) => users.map((user) => user.content))), - { initialValue: [] }, - ); - userId = this.#store.selectSignal(selectUserId); - users = computed(() => { - const boardUsers = this.#boardUsers(); - - return this.#users() - .filter((user) => user.id !== this.userId()) - .map((user) => { - const boardUser = boardUsers.find( - (boardUser) => boardUser.id === user.id, - ); - - return { - ...user, - picture: boardUser?.picture, - }; - }); - }); - #settings = toSignal(this.#boardFacade.getSettings(), { initialValue: null }); - - showUsers = computed(() => { - return !this.#settings()?.content.anonymousMode; - }); - textarea = viewChild>('textarea'); - allowSwitchMode = input(true); - edit = signal(false); - boardMode = this.#store.selectSignal(pageFeature.selectBoardMode); name = this.#store.selectSignal(pageFeature.selectName); isAdmin = this.#store.selectSignal(selectIsAdmin); boardId = this.#store.selectSignal(pageFeature.selectBoardId); teamName = this.#store.selectSignal(pageFeature.selectTeamName); teamId = this.#store.selectSignal(pageFeature.selectTeamId); - user = this.#store.selectSignal(appFeature.selectUser); get isDemo() { return !!this.#configService.config.DEMO; @@ -128,21 +78,6 @@ export class HeaderComponent { ); } - export() { - this.#exportService.getExportFile().then( - (url) => { - const fileLink = document.createElement('a'); - - fileLink.href = url; - fileLink.download = `${this.boardId()}.json`; - fileLink.click(); - }, - () => { - console.error('export error'); - }, - ); - } - editName() { this.edit.set(true); } @@ -173,18 +108,4 @@ export class HeaderComponent { this.#store.dispatch(BoardActions.setBoardName({ name })); } } - - share() { - this.#dialog.open(ShareBoardComponent, { - width: '600px', - autoFocus: 'dialog', - }); - } - - settings() { - this.#dialog.open(BoardSettingsComponent, { - width: '600px', - autoFocus: 'dialog', - }); - } }