Repository: go-gitea/gitea
Pull request #34081: Add fullscreen mode as a more efficient operation way to view projects
Author: login: kerwin612
Labels: name: type/feature
description: Completely new functionality. Can only be merged if feature freeze is not active.
name: topic/ui
description: Change the appearance of the Gitea UI
name: lgtm/done
description: This PR has enough approvals to get merged. There are no important open reservations anymore.
Created: 2025-03-31T03:19:59Z | Merged: 2025-04-23T05:42:23Z

https://github.com/go-gitea/gitea/issues/34015#issuecomment-2764988868

Maybe fix #33482, maybe fix #34015

Linked issue #33482: Dragging items between and within columns in project boards
### Description

Our triagers reported the following user experience issues with Project boards after updating to Gitea 1.23.1:

- Project boards are now displayed as a table where the height matches the tallest column. Previously, they were constrained to fit within the browser view, with individual scrollbars for each column. This change makes dragging items between columns—especially from the bottom of one column to the top of another—more difficult.

- (resolved) ~~Moving an item laterally within a project board column appears to trigger an event, when it should likely just update its column placement.~~

<details>

<summary> (screenshot for 2, resolved)</summary>

![Image](https://github.com/user-attachments/assets/6e09c4cf-f813-4711-bb28-9336f4805b78)

<details>

### Gitea Version

1.23.1 built with GNU Make 4.4.1, go1.23.5 : bindata, timetzdata, sqlite, sqlite_unlock_notify

Linked issue comments: user: login: wxiaoguang
created_at: 2025-02-03T12:47:26Z
body: > * Workboards are now displayed as a table where the height matches the tallest column. Previously, they were constrained to fit within the browser view, with individual scrollbars for each column. This change makes dragging items between columns—especially from the bottom of one column to the top of another—more difficult.

Related to feat(#31666): Set the columns height to hug all its contents (#31726) ?

* (Just FYI, I haven't really looked into it, help is welcome!)

<details>

<summary> the second one (moving issue in column) has been resolved</summary>

> * Moving an item laterally within a Workboard column appears to trigger an event, when it should likely just update its column placement.

~~Related to Add issue comment when moving issues from one column to another of the project (#29311)~~ FIXED BY  #33496 and its backport

</deatils>
author_association: CONTRIBUTOR
user: login: lunny
created_at: 2025-02-03T17:41:46Z
body: > > * Workboards are now displayed as a table where the height matches the tallest column. Previously, they were constrained to fit within the browser view, with individual scrollbars for each column. This change makes dragging items between columns—especially from the bottom of one column to the top of another—more difficult.
> 
> Related to feat([#31666](https://github.com/go-gitea/gitea/issues/31666)): Set the columns height to hug all its contents ([#31726](https://github.com/go-gitea/gitea/pull/31726)) ?
> 
> > * Moving an item laterally within a Workboard column appears to trigger an event, when it should likely just update its column placement.
> 
> Related to Add issue comment when moving issues from one column to another of the project ([#29311](https://github.com/go-gitea/gitea/pull/29311)) [<img alt="" width="16" height="16" src="https://avatars.githubusercontent.com/u/81045?s=64&amp;u=99b64f0ca6ef63643c7583ab87dd31c52d28e673&amp;v=4">@lunny](https://github.com/lunny)

#29311 is a follow-up design to what GitHub did.
author_association: MEMBER
user: login: wxiaoguang
created_at: 2025-02-04T13:53:16Z
body: > > > * Moving an item laterally within a Workboard column appears to trigger an event, when it should likely just update its column placement.
> > 
> > 
> > Related to Add issue comment when moving issues from one column to another of the project ([#29311](https://github.com/go-gitea/gitea/pull/29311)) [<img alt="" width="16" height="16" src="https://avatars.githubusercontent.com/u/81045?s=64&amp;u=99b64f0ca6ef63643c7583ab87dd31c52d28e673&amp;v=4">@lunny](https://github.com/lunny)
> 
> [#29311](https://github.com/go-gitea/gitea/pull/29311) is a follow-up design to what GitHub did.

Are you sure that GitHub also creates such useless noise when moving the issue in the same column?
author_association: CONTRIBUTOR
user: login: lunny
created_at: 2025-02-04T18:43:10Z
body: > > > > * Moving an item laterally within a Workboard column appears to trigger an event, when it should likely just update its column placement.
> > > 
> > > 
> > > Related to Add issue comment when moving issues from one column to another of the project ([#29311](https://github.com/go-gitea/gitea/pull/29311)) [<img alt="" width="16" height="16" src="https://avatars.githubusercontent.com/u/81045?s=64&amp;u=99b64f0ca6ef63643c7583ab87dd31c52d28e673&amp;v=4">@lunny](https://github.com/lunny)
> > 
> > 
> > [#29311](https://github.com/go-gitea/gitea/pull/29311) is a follow-up design to what GitHub did.
> 
> Are you sure that GitHub also creates such useless noise when moving the issue in the same column?

I think I'm wrong. Moving in the same column with different positons should not trigger a comment. #33496
author_association: MEMBER
user: login: wxiaoguang
created_at: 2025-02-05T03:56:29Z
body: The first one is still unresolved (ps: actually I haven't really looked into it, if anyone has ideas, welcome to help, thank you!)
author_association: CONTRIBUTOR

Diff:
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 508b4cff374f2..b4c5121de50ec 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 bd74114fe2e47..1bfbc8d8b47de 100644
--- a/templates/org/projects/view.tmpl
+++ b/templates/org/projects/view.tmpl
@@ -8,8 +8,6 @@
 			{{template "user/overview/header" .}}
 		</div>
 	{{end}}
-	<div class="ui container fluid padded">
-		{{template "projects/view" .}}
-	</div>
+	{{template "projects/view" .}}
 </div>
 {{template "base/footer" .}}
diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl
index d6a335ef4b5ad..7e89db0005d54 100644
--- a/templates/projects/view.tmpl
+++ b/templates/projects/view.tmpl
@@ -1,8 +1,8 @@
 {{$canWriteProject := and .CanWriteProjects (or (not .Repository) (not .Repository.IsArchived))}}
 
-<div class="ui container tw-max-w-full">
-	<div class="flex-text-block tw-flex-wrap tw-mb-4">
-		<h2 class="tw-mb-0">{{.Project.Title}}</h2>
+<div class="ui container fluid padded projects-view">
+	<div class="ui container flex-text-block project-header">
+		<h2>{{.Project.Title}}</h2>
 		<div class="tw-flex-1"></div>
 		<div class="ui secondary menu tw-m-0">
 			{{$queryLink := QueryBuild "?" "labels" .SelectLabels "assignee" $.AssigneeID "archived_labels" (Iif $.ShowArchivedLabels "true")}}
@@ -19,6 +19,14 @@
 		</div>
 		{{if $canWriteProject}}
 			<div class="ui compact mini menu">
+				<a class="item screen-full">
+					{{svg "octicon-screen-full"}}
+					{{ctx.Locale.Tr "projects.enter_fullscreen"}}
+				</a>
+				<a class="item screen-normal tw-hidden">
+					{{svg "octicon-screen-normal"}}
+					{{ctx.Locale.Tr "projects.exit_fullscreen"}}
+				</a>
 				<a class="item" href="{{.Link}}/edit?redirect=project">
 					{{svg "octicon-pencil"}}
 					{{ctx.Locale.Tr "repo.issues.label_edit"}}
@@ -56,13 +64,12 @@
 		{{end}}
 	</div>
 
-	<div class="content">{{$.Project.RenderedContent}}</div>
-
-	<div class="divider"></div>
-</div>
+	<div class="ui container project-description">
+		{{$.Project.RenderedContent}}
+		<div class="divider"></div>
+	</div>
 
-<div id="project-board" data-project-borad-writable="{{$canWriteProject}}">
-	<div class="board {{if $canWriteProject}}sortable{{end}}" {{if $canWriteProject}}data-url="{{$.Link}}/move"{{end}}>
+	<div id="project-board" class="board {{if $canWriteProject}}sortable{{end}}" data-project-borad-writable="{{$canWriteProject}}" {{if $canWriteProject}}data-url="{{$.Link}}/move"{{end}}>
 		{{range .Columns}}
 			<div class="project-column" {{if .Color}}style="background: {{.Color}} !important; color: {{ContrastColor .Color}} !important"{{end}} data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.Link}}/{{.ID}}">
 				<div class="project-column-header{{if $canWriteProject}} tw-cursor-grab{{end}}">
diff --git a/templates/repo/projects/view.tmpl b/templates/repo/projects/view.tmpl
index 7267a99b1db0f..1acaaf8b4bb10 100644
--- a/templates/repo/projects/view.tmpl
+++ b/templates/repo/projects/view.tmpl
@@ -8,9 +8,7 @@
 			<a class="ui small primary button" href="{{.RepoLink}}/issues/new/choose?project={{.Project.ID}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
 		</div>
 	</div>
-	<div class="ui container fluid padded">
-		{{template "projects/view" .}}
-	</div>
+	{{template "projects/view" .}}
 </div>
 
 {{template "base/footer" .}}
diff --git a/web_src/css/features/projects.css b/web_src/css/features/projects.css
index 72ef523913e34..80c9d89638efc 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 a5efd23053de2..5072dcbd0e17d 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 5a4c22bc9007c..447347890b7f4 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 11f5c19c8d335..dc4aa8274b931 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<voi
 }
 
 async function initRepoProjectSortable(): Promise<void> {
-  // 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<HTMLElement>('.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 b825a9339d101..e33b1413e8a7d 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);
+  }
+}


Reviews:
user: login: hiifong
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-14T10:13:56Z
user: login: wxiaoguang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T01:13:25Z
user: login: kerwin612
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-21T02:46:48Z
user: login: wxiaoguang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T03:09:20Z
user: login: wxiaoguang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T00:30:49Z
user: login: wxiaoguang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T00:31:31Z
user: login: wxiaoguang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T00:35:18Z
user: login: wxiaoguang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T07:21:22Z
user: login: wxiaoguang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T07:21:26Z
user: login: wxiaoguang
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T07:36:56Z
user: login: wxiaoguang
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T01:52:07Z
user: login: kerwin612
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-06-20T12:50:50Z

Review comments:
diff_hunk: @@ -8,8 +8,8 @@
 			{{template "user/overview/header" .}}
 		</div>
 	{{end}}
-	<div class="ui container fluid padded">
-		{{template "projects/view" .}}
+	<div class="ui container fluid padded org-projects-view">
+		{{template "projects/view" dict "." . "FullscreenElementsSelector" ".org-projects-view"}}
path: templates/org/projects/view.tmpl
user: login: wxiaoguang
body: The old layout is not right, the new change makes things worse.

`projects/view` already has its own "ui container" and it could have a unified CSS class name.
created_at: 2025-04-18T01:13:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -8,8 +8,8 @@
 			{{template "user/overview/header" .}}
 		</div>
 	{{end}}
-	<div class="ui container fluid padded">
-		{{template "projects/view" .}}
+	<div class="ui container fluid padded org-projects-view">
+		{{template "projects/view" dict "." . "FullscreenElementsSelector" ".org-projects-view"}}
path: templates/org/projects/view.tmpl
user: login: kerwin612
body: I'm very sorry. From what I can see on the page, I don't find anything inappropriate. I can't tell what's wrong with the layout. Or rather, I don't understand what you think the correct layout should be.
created_at: 2025-04-21T02:46:48Z
author_association: MEMBER
diff_hunk: @@ -8,8 +8,8 @@
 			{{template "user/overview/header" .}}
 		</div>
 	{{end}}
-	<div class="ui container fluid padded">
-		{{template "projects/view" .}}
+	<div class="ui container fluid padded org-projects-view">
+		{{template "projects/view" dict "." . "FullscreenElementsSelector" ".org-projects-view"}}
path: templates/org/projects/view.tmpl
user: login: wxiaoguang
body: See latest commits
created_at: 2025-04-21T03:09:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -139,7 +140,26 @@ 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 fullscreenElementsSelector = enterFullscreenBtn.getAttribute('data-fullscreen-elements-selector');
path: web_src/js/features/repo-projects.ts
user: login: wxiaoguang
body: Why it still needs `data-fullscreen-elements-selector`?
created_at: 2025-04-22T00:30:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -139,7 +140,26 @@ 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 fullscreenElementsSelector = enterFullscreenBtn.getAttribute('data-fullscreen-elements-selector');
+
+  const toggleFullscreenState = (isFullScreen: boolean) => {
+    toggleFullScreen(fullscreenElementsSelector, isFullScreen);
+    toggleElem(enterFullscreenBtn);
+    toggleElem(exitFullscreenBtn);
path: web_src/js/features/repo-projects.ts
user: login: wxiaoguang
body: Why not explicitly use "true/false" (isFullScreen / !isFullScreen)?
created_at: 2025-04-22T00:31:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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 fullScreenEls = document.querySelectorAll(fullscreenElementsSelector);
+  const outerEl = document.querySelector('.full.height');
+  toggleClass(fullscreenElementsSelector, 'fullscreen', isFullScreen);
+  if (isFullScreen) {
+    for (const e of fullScreenEls) outerEl.append(e);
path: web_src/js/utils.ts
user: login: wxiaoguang
body: Why there should be multiple "full screen elements"?
created_at: 2025-04-22T00:35:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -98,3 +98,27 @@
 .card-ghost * {
   opacity: 0;
 }
+
+.fullscreen.projects-view .ui.container.flex-text-block {
+  position: fixed;
+  z-index: 1000;
+  top: 0;
+  left: 0;
+  right: 0;
+  margin: 0 !important;
+  padding: .25rem;
+  width: 100%;
+  max-width: 100%;
+  background-color: var(--color-body);
+}
+
+/* Hide project-description in full-screen due to its variable height. No need to show it for better space use. */
+.fullscreen.projects-view div[class="ui container"] {
path: web_src/css/features/projects.css
user: login: wxiaoguang
body: Don't use such trick. Why not use a clear CSS class name for the target elements?
created_at: 2025-04-22T07:21:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -98,3 +98,27 @@
 .card-ghost * {
   opacity: 0;
 }
+
+.fullscreen.projects-view .ui.container.flex-text-block {
path: web_src/css/features/projects.css
user: login: wxiaoguang
body: Don't use such trick. Why not use a clear CSS class name for the target elements?
created_at: 2025-04-22T07:21:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,7 +36,7 @@ async function moveIssue({item, from, to, oldIndex}: SortableEvent): Promise<voi
 
 async function initRepoProjectSortable(): Promise<void> {
   // the HTML layout is: #project-board > .board > .project-column .cards > .issue-card
path: web_src/js/features/repo-projects.ts
user: login: wxiaoguang
body: ```suggestion
  // the HTML layout is: #project-board.board > .project-column .cards > .issue-card
```
created_at: 2025-04-22T07:36:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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);
path: web_src/css/features/projects.css
user: login: kerwin612
body: #34785 #34791 
created_at: 2025-06-20T12:50:50Z
author_association: MEMBER

PR comments:
user: login: kerwin612
created_at: 2025-04-14T03:11:12Z
body: ![fullscreen](https://github.com/user-attachments/assets/cf2bca19-3a7d-47ab-9aeb-90249a2aeb23)
author_association: MEMBER
user: login: wxiaoguang
created_at: 2025-04-14T10:23:54Z
body: Could the "Actions Logs" fullscreen share the same logic?
author_association: CONTRIBUTOR
user: login: wxiaoguang
created_at: 2025-04-22T07:40:17Z
body: It doesn't seem right to have such scrollbar in fullscreen mode.

Each column should be scrollable

![image](https://github.com/user-attachments/assets/9cacd3b9-deb7-40a7-92f2-51169564bfb8)

author_association: CONTRIBUTOR
user: login: wxiaoguang
created_at: 2025-04-22T07:41:59Z
body: Broken menu

![image](https://github.com/user-attachments/assets/1e2ab42d-0296-46d6-aecd-c71e1efa79a4)

author_association: CONTRIBUTOR
user: login: kerwin612
created_at: 2025-04-23T00:59:20Z
body: > Each column should be scrollable
> 
> Broken menu


It's all handled:
![after](https://github.com/user-attachments/assets/10378a28-9c01-4f27-b1df-346dc67b5e03)


author_association: MEMBER