From c2c04ffff7ea32d3dedc830ad26995813916a940 Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Wed, 23 Apr 2025 13:42:22 +0800 Subject: [PATCH] Add fullscreen mode as a more efficient operation way to view projects (#34081) Maybe fix #33482, maybe fix #34015 --------- Co-authored-by: wxiaoguang --- options/locale/locale_en-US.ini | 2 + templates/org/projects/view.tmpl | 4 +- templates/projects/view.tmpl | 25 +++++--- templates/repo/projects/view.tmpl | 4 +- web_src/css/features/projects.css | 76 ++++++++++++++++-------- web_src/css/modules/menu.css | 2 + web_src/js/components/RepoActionView.vue | 17 +----- web_src/js/features/repo-projects.ts | 24 +++++++- web_src/js/utils.ts | 22 +++++++ 9 files changed, 119 insertions(+), 57 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 508b4cff37..b4c5121de5 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3844,6 +3844,8 @@ deleted.display_name = Deleted Project type-1.display_name = Individual Project type-2.display_name = Repository Project type-3.display_name = Organization Project +enter_fullscreen = Fullscreen +exit_fullscreen = Exit Fullscreen [git.filemode] changed_filemode = %[1]s → %[2]s diff --git a/templates/org/projects/view.tmpl b/templates/org/projects/view.tmpl index bd74114fe2..1bfbc8d8b4 100644 --- a/templates/org/projects/view.tmpl +++ b/templates/org/projects/view.tmpl @@ -8,8 +8,6 @@ {{template "user/overview/header" .}} {{end}} -
- {{template "projects/view" .}} -
+ {{template "projects/view" .}} {{template "base/footer" .}} diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index d6a335ef4b..7e89db0005 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -1,8 +1,8 @@ {{$canWriteProject := and .CanWriteProjects (or (not .Repository) (not .Repository.IsArchived))}} -
-
-

{{.Project.Title}}

+
+ - -
-
+ {{template "base/footer" .}} diff --git a/web_src/css/features/projects.css b/web_src/css/features/projects.css index 72ef523913..80c9d89638 100644 --- a/web_src/css/features/projects.css +++ b/web_src/css/features/projects.css @@ -1,31 +1,34 @@ -.board { +#project-board { display: flex; + align-items: stretch; flex-direction: row; flex-wrap: nowrap; - overflow-x: auto; - overflow-y: clip; - align-items: stretch; + overflow: auto; margin: 0 0.5em; + max-height: calc(100vh - 120px); +} + +.project-header { + padding: 0.5em 0; + overflow-x: auto; /* in fullscreen mode, the position is fixed, so we can't use "flex wrap" which would change the height */ +} + +.project-header h2 { + white-space: nowrap; + margin: 0; } .project-column { - background-color: var(--color-project-column-bg) !important; - border: 1px solid var(--color-secondary) !important; - border-radius: var(--border-radius); - margin: 0 0.5rem !important; - padding: 0.5rem !important; - width: 320px; - height: initial; - min-height: max(calc(100vh - 400px), 300px); flex: 0 0 auto; - overflow: visible; display: flex; flex-direction: column; - cursor: default; -} - -.project-column .issue-card { - color: var(--color-text); + background-color: var(--color-project-column-bg); + border: 1px solid var(--color-secondary); + border-radius: var(--border-radius); + margin: 0 0.5rem; + padding: 0.5rem; + width: 320px; + overflow: visible; } .project-column-header { @@ -39,16 +42,15 @@ color: inherit; } -.project-column > .cards { +.project-column > .ui.cards { flex: 1; display: flex; - align-content: baseline; - margin: 0 !important; - padding: 0 !important; - flex-wrap: nowrap !important; + flex-wrap: nowrap; flex-direction: column; - overflow-x: clip; + overflow: clip auto; gap: .25rem; + margin: 0; + padding: 0; } .project-column > .divider { @@ -98,3 +100,29 @@ .card-ghost * { opacity: 0; } + +.fullscreen.projects-view .project-header { + position: fixed; + z-index: 1000; + top: 0; + left: 0; + right: 0; + padding: 0.5em; + width: 100%; + max-width: 100%; + background-color: var(--color-body); + border-bottom: 1px solid var(--color-secondary); +} + +/* Hide project-description in full-screen due to its variable height. No need to show it for better space use. */ +.fullscreen.projects-view .project-description { + display: none; +} + +.fullscreen.projects-view #project-board { + position: absolute; + top: 60px; + left: 0; + right: 0; + max-height: calc(100vh - 70px); +} diff --git a/web_src/css/modules/menu.css b/web_src/css/modules/menu.css index a5efd23053..5072dcbd0e 100644 --- a/web_src/css/modules/menu.css +++ b/web_src/css/modules/menu.css @@ -1,5 +1,6 @@ .ui.menu { display: flex; + flex-shrink: 0; margin: 1rem 0; font-family: var(--fonts-regular); font-weight: var(--font-weight-normal); @@ -643,6 +644,7 @@ display: inline-flex; margin: 0; vertical-align: middle; + flex-shrink: 0; } .ui.compact.vertical.menu { display: inline-block; diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 5a4c22bc90..447347890b 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -7,6 +7,7 @@ import {formatDatetime} from '../utils/time.ts'; import {renderAnsi} from '../render/ansi.ts'; import {POST, DELETE} from '../modules/fetch.ts'; import type {IntervalId} from '../types.ts'; +import {toggleFullScreen} from '../utils.ts'; // see "models/actions/status.go", if it needs to be used somewhere else, move it to a shared file like "types/actions.ts" type RunStatus = 'unknown' | 'waiting' | 'running' | 'success' | 'failure' | 'cancelled' | 'skipped' | 'blocked'; @@ -416,21 +417,7 @@ export default defineComponent({ toggleFullScreen() { this.isFullScreen = !this.isFullScreen; - const fullScreenEl = document.querySelector('.action-view-right'); - const outerEl = document.querySelector('.full.height'); - const actionBodyEl = document.querySelector('.action-view-body'); - const headerEl = document.querySelector('#navbar'); - const contentEl = document.querySelector('.page-content'); - const footerEl = document.querySelector('.page-footer'); - toggleElem(headerEl, !this.isFullScreen); - toggleElem(contentEl, !this.isFullScreen); - toggleElem(footerEl, !this.isFullScreen); - // move .action-view-right to new parent - if (this.isFullScreen) { - outerEl.append(fullScreenEl); - } else { - actionBodyEl.append(fullScreenEl); - } + toggleFullScreen('.action-view-right', this.isFullScreen, '.action-view-body'); }, async hashChangeListener() { const selectedLogStep = window.location.hash; diff --git a/web_src/js/features/repo-projects.ts b/web_src/js/features/repo-projects.ts index 11f5c19c8d..dc4aa8274b 100644 --- a/web_src/js/features/repo-projects.ts +++ b/web_src/js/features/repo-projects.ts @@ -2,8 +2,9 @@ import {contrastColor} from '../utils/color.ts'; import {createSortable} from '../modules/sortable.ts'; import {POST, request} from '../modules/fetch.ts'; import {fomanticQuery} from '../modules/fomantic/base.ts'; -import {queryElemChildren, queryElems} from '../utils/dom.ts'; +import {queryElemChildren, queryElems, toggleElem} from '../utils/dom.ts'; import type {SortableEvent} from 'sortablejs'; +import {toggleFullScreen} from '../utils.ts'; function updateIssueCount(card: HTMLElement): void { const parent = card.parentElement; @@ -34,8 +35,8 @@ async function moveIssue({item, from, to, oldIndex}: SortableEvent): Promise { - // the HTML layout is: #project-board > .board > .project-column .cards > .issue-card - const mainBoard = document.querySelector('#project-board > .board.sortable'); + // the HTML layout is: #project-board.board > .project-column .cards > .issue-card + const mainBoard = document.querySelector('#project-board'); let boardColumns = mainBoard.querySelectorAll('.project-column'); createSortable(mainBoard, { group: 'project-column', @@ -139,7 +140,24 @@ function initRepoProjectColumnEdit(writableProjectBoard: Element): void { }); } +function initRepoProjectToggleFullScreen(): void { + const enterFullscreenBtn = document.querySelector('.screen-full'); + const exitFullscreenBtn = document.querySelector('.screen-normal'); + if (!enterFullscreenBtn || !exitFullscreenBtn) return; + + const toggleFullscreenState = (isFullScreen: boolean) => { + toggleFullScreen('.projects-view', isFullScreen); + toggleElem(enterFullscreenBtn, !isFullScreen); + toggleElem(exitFullscreenBtn, isFullScreen); + }; + + enterFullscreenBtn.addEventListener('click', () => toggleFullscreenState(true)); + exitFullscreenBtn.addEventListener('click', () => toggleFullscreenState(false)); +} + export function initRepoProject(): void { + initRepoProjectToggleFullScreen(); + const writableProjectBoard = document.querySelector('#project-board[data-project-borad-writable="true"]'); if (!writableProjectBoard) return; diff --git a/web_src/js/utils.ts b/web_src/js/utils.ts index b825a9339d..e33b1413e8 100644 --- a/web_src/js/utils.ts +++ b/web_src/js/utils.ts @@ -1,5 +1,6 @@ import {decode, encode} from 'uint8-to-base64'; import type {IssuePageInfo, IssuePathInfo, RepoOwnerPathInfo} from './types.ts'; +import {toggleClass, toggleElem} from './utils/dom.ts'; // transform /path/to/file.ext to /path/to export function dirname(path: string): string { @@ -179,3 +180,24 @@ export function isImageFile({name, type}: {name?: string, type?: string}): boole export function isVideoFile({name, type}: {name?: string, type?: string}): boolean { return /\.(mpe?g|mp4|mkv|webm)$/i.test(name || '') || type?.startsWith('video/'); } + +export function toggleFullScreen(fullscreenElementsSelector: string, isFullScreen: boolean, sourceParentSelector?: string): void { + // hide other elements + const headerEl = document.querySelector('#navbar'); + const contentEl = document.querySelector('.page-content'); + const footerEl = document.querySelector('.page-footer'); + toggleElem(headerEl, !isFullScreen); + toggleElem(contentEl, !isFullScreen); + toggleElem(footerEl, !isFullScreen); + + const sourceParentEl = sourceParentSelector ? document.querySelector(sourceParentSelector) : contentEl; + + const fullScreenEl = document.querySelector(fullscreenElementsSelector); + const outerEl = document.querySelector('.full.height'); + toggleClass(fullscreenElementsSelector, 'fullscreen', isFullScreen); + if (isFullScreen) { + outerEl.append(fullScreenEl); + } else { + sourceParentEl.append(fullScreenEl); + } +}