mirror of
https://github.com/go-gitea/gitea.git
synced 2025-04-28 11:45:15 +00: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:
parent
04fab1818b
commit
c2c04ffff7
@ -3844,6 +3844,8 @@ deleted.display_name = Deleted Project
|
|||||||
type-1.display_name = Individual Project
|
type-1.display_name = Individual Project
|
||||||
type-2.display_name = Repository Project
|
type-2.display_name = Repository Project
|
||||||
type-3.display_name = Organization Project
|
type-3.display_name = Organization Project
|
||||||
|
enter_fullscreen = Fullscreen
|
||||||
|
exit_fullscreen = Exit Fullscreen
|
||||||
|
|
||||||
[git.filemode]
|
[git.filemode]
|
||||||
changed_filemode = %[1]s → %[2]s
|
changed_filemode = %[1]s → %[2]s
|
||||||
|
@ -8,8 +8,6 @@
|
|||||||
{{template "user/overview/header" .}}
|
{{template "user/overview/header" .}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="ui container fluid padded">
|
|
||||||
{{template "projects/view" .}}
|
{{template "projects/view" .}}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{{$canWriteProject := and .CanWriteProjects (or (not .Repository) (not .Repository.IsArchived))}}
|
{{$canWriteProject := and .CanWriteProjects (or (not .Repository) (not .Repository.IsArchived))}}
|
||||||
|
|
||||||
<div class="ui container tw-max-w-full">
|
<div class="ui container fluid padded projects-view">
|
||||||
<div class="flex-text-block tw-flex-wrap tw-mb-4">
|
<div class="ui container flex-text-block project-header">
|
||||||
<h2 class="tw-mb-0">{{.Project.Title}}</h2>
|
<h2>{{.Project.Title}}</h2>
|
||||||
<div class="tw-flex-1"></div>
|
<div class="tw-flex-1"></div>
|
||||||
<div class="ui secondary menu tw-m-0">
|
<div class="ui secondary menu tw-m-0">
|
||||||
{{$queryLink := QueryBuild "?" "labels" .SelectLabels "assignee" $.AssigneeID "archived_labels" (Iif $.ShowArchivedLabels "true")}}
|
{{$queryLink := QueryBuild "?" "labels" .SelectLabels "assignee" $.AssigneeID "archived_labels" (Iif $.ShowArchivedLabels "true")}}
|
||||||
@ -19,6 +19,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{{if $canWriteProject}}
|
{{if $canWriteProject}}
|
||||||
<div class="ui compact mini menu">
|
<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">
|
<a class="item" href="{{.Link}}/edit?redirect=project">
|
||||||
{{svg "octicon-pencil"}}
|
{{svg "octicon-pencil"}}
|
||||||
{{ctx.Locale.Tr "repo.issues.label_edit"}}
|
{{ctx.Locale.Tr "repo.issues.label_edit"}}
|
||||||
@ -56,13 +64,12 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">{{$.Project.RenderedContent}}</div>
|
<div class="ui container project-description">
|
||||||
|
{{$.Project.RenderedContent}}
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="project-board" data-project-borad-writable="{{$canWriteProject}}">
|
<div id="project-board" class="board {{if $canWriteProject}}sortable{{end}}" data-project-borad-writable="{{$canWriteProject}}" {{if $canWriteProject}}data-url="{{$.Link}}/move"{{end}}>
|
||||||
<div class="board {{if $canWriteProject}}sortable{{end}}" {{if $canWriteProject}}data-url="{{$.Link}}/move"{{end}}>
|
|
||||||
{{range .Columns}}
|
{{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" {{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}}">
|
<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>
|
<a class="ui small primary button" href="{{.RepoLink}}/issues/new/choose?project={{.Project.ID}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui container fluid padded">
|
|
||||||
{{template "projects/view" .}}
|
{{template "projects/view" .}}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
|
@ -1,31 +1,34 @@
|
|||||||
.board {
|
#project-board {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
overflow-x: auto;
|
overflow: auto;
|
||||||
overflow-y: clip;
|
|
||||||
align-items: stretch;
|
|
||||||
margin: 0 0.5em;
|
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 {
|
.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;
|
flex: 0 0 auto;
|
||||||
overflow: visible;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
cursor: default;
|
background-color: var(--color-project-column-bg);
|
||||||
}
|
border: 1px solid var(--color-secondary);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
.project-column .issue-card {
|
margin: 0 0.5rem;
|
||||||
color: var(--color-text);
|
padding: 0.5rem;
|
||||||
|
width: 320px;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-column-header {
|
.project-column-header {
|
||||||
@ -39,16 +42,15 @@
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-column > .cards {
|
.project-column > .ui.cards {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-content: baseline;
|
flex-wrap: nowrap;
|
||||||
margin: 0 !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
flex-wrap: nowrap !important;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-x: clip;
|
overflow: clip auto;
|
||||||
gap: .25rem;
|
gap: .25rem;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-column > .divider {
|
.project-column > .divider {
|
||||||
@ -98,3 +100,29 @@
|
|||||||
.card-ghost * {
|
.card-ghost * {
|
||||||
opacity: 0;
|
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 {
|
.ui.menu {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
font-family: var(--fonts-regular);
|
font-family: var(--fonts-regular);
|
||||||
font-weight: var(--font-weight-normal);
|
font-weight: var(--font-weight-normal);
|
||||||
@ -643,6 +644,7 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.ui.compact.vertical.menu {
|
.ui.compact.vertical.menu {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -7,6 +7,7 @@ import {formatDatetime} from '../utils/time.ts';
|
|||||||
import {renderAnsi} from '../render/ansi.ts';
|
import {renderAnsi} from '../render/ansi.ts';
|
||||||
import {POST, DELETE} from '../modules/fetch.ts';
|
import {POST, DELETE} from '../modules/fetch.ts';
|
||||||
import type {IntervalId} from '../types.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"
|
// 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';
|
type RunStatus = 'unknown' | 'waiting' | 'running' | 'success' | 'failure' | 'cancelled' | 'skipped' | 'blocked';
|
||||||
@ -416,21 +417,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
toggleFullScreen() {
|
toggleFullScreen() {
|
||||||
this.isFullScreen = !this.isFullScreen;
|
this.isFullScreen = !this.isFullScreen;
|
||||||
const fullScreenEl = document.querySelector('.action-view-right');
|
toggleFullScreen('.action-view-right', this.isFullScreen, '.action-view-body');
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async hashChangeListener() {
|
async hashChangeListener() {
|
||||||
const selectedLogStep = window.location.hash;
|
const selectedLogStep = window.location.hash;
|
||||||
|
@ -2,8 +2,9 @@ import {contrastColor} from '../utils/color.ts';
|
|||||||
import {createSortable} from '../modules/sortable.ts';
|
import {createSortable} from '../modules/sortable.ts';
|
||||||
import {POST, request} from '../modules/fetch.ts';
|
import {POST, request} from '../modules/fetch.ts';
|
||||||
import {fomanticQuery} from '../modules/fomantic/base.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 type {SortableEvent} from 'sortablejs';
|
||||||
|
import {toggleFullScreen} from '../utils.ts';
|
||||||
|
|
||||||
function updateIssueCount(card: HTMLElement): void {
|
function updateIssueCount(card: HTMLElement): void {
|
||||||
const parent = card.parentElement;
|
const parent = card.parentElement;
|
||||||
@ -34,8 +35,8 @@ async function moveIssue({item, from, to, oldIndex}: SortableEvent): Promise<voi
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function initRepoProjectSortable(): Promise<void> {
|
async function initRepoProjectSortable(): Promise<void> {
|
||||||
// the HTML layout is: #project-board > .board > .project-column .cards > .issue-card
|
// the HTML layout is: #project-board.board > .project-column .cards > .issue-card
|
||||||
const mainBoard = document.querySelector('#project-board > .board.sortable');
|
const mainBoard = document.querySelector('#project-board');
|
||||||
let boardColumns = mainBoard.querySelectorAll<HTMLElement>('.project-column');
|
let boardColumns = mainBoard.querySelectorAll<HTMLElement>('.project-column');
|
||||||
createSortable(mainBoard, {
|
createSortable(mainBoard, {
|
||||||
group: 'project-column',
|
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 {
|
export function initRepoProject(): void {
|
||||||
|
initRepoProjectToggleFullScreen();
|
||||||
|
|
||||||
const writableProjectBoard = document.querySelector('#project-board[data-project-borad-writable="true"]');
|
const writableProjectBoard = document.querySelector('#project-board[data-project-borad-writable="true"]');
|
||||||
if (!writableProjectBoard) return;
|
if (!writableProjectBoard) return;
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {decode, encode} from 'uint8-to-base64';
|
import {decode, encode} from 'uint8-to-base64';
|
||||||
import type {IssuePageInfo, IssuePathInfo, RepoOwnerPathInfo} from './types.ts';
|
import type {IssuePageInfo, IssuePathInfo, RepoOwnerPathInfo} from './types.ts';
|
||||||
|
import {toggleClass, toggleElem} from './utils/dom.ts';
|
||||||
|
|
||||||
// transform /path/to/file.ext to /path/to
|
// transform /path/to/file.ext to /path/to
|
||||||
export function dirname(path: string): string {
|
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 {
|
export function isVideoFile({name, type}: {name?: string, type?: string}): boolean {
|
||||||
return /\.(mpe?g|mp4|mkv|webm)$/i.test(name || '') || type?.startsWith('video/');
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user