mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Add fullscreen mode as a more efficient operation way to view projects (#34081)
Maybe fix #33482, maybe fix #34015 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -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" .}} | ||||
|   | ||||
| @@ -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="ui container project-description"> | ||||
| 		{{$.Project.RenderedContent}} | ||||
| 		<div class="divider"></div> | ||||
| 	</div> | ||||
|  | ||||
| 	<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}}"> | ||||
|   | ||||
| @@ -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" .}} | ||||
|   | ||||
| @@ -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); | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user