Files
Atay-Makhzan/web_src/js/features/repo-projects.ts
T

187 lines
7.6 KiB
TypeScript
Raw Normal View History

import {contrastColor} from '../utils/color.ts';
import {createSortable} from '../modules/sortable.ts';
2024-12-19 09:37:12 +01:00
import {POST, request} from '../modules/fetch.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {queryElemChildren, queryElems, toggleElem} from '../utils/dom.ts';
2024-12-19 09:37:12 +01:00
import type {SortableEvent} from 'sortablejs';
import {toggleFullScreen} from '../utils.ts';
2026-04-04 04:03:59 +08:00
import {registerGlobalInitFunc} from '../modules/observer.ts';
import {localUserSettings} from '../modules/user-settings.ts';
2024-12-19 09:37:12 +01:00
function updateIssueCount(card: HTMLElement): void {
2025-12-03 03:13:16 +01:00
const parent = card.parentElement!;
2024-12-19 09:37:12 +01:00
const count = parent.querySelectorAll('.issue-card').length;
2025-12-03 03:13:16 +01:00
parent.querySelector('.project-column-issue-count')!.textContent = String(count);
}
2024-12-19 09:37:12 +01:00
async function moveIssue({item, from, to, oldIndex}: SortableEvent): Promise<void> {
const columnCards = to.querySelectorAll('.issue-card');
updateIssueCount(from);
updateIssueCount(to);
const columnSorting = {
2023-05-18 03:14:31 +02:00
issues: Array.from(columnCards, (card, i) => ({
2025-12-03 03:13:16 +01:00
issueID: parseInt(card.getAttribute('data-issue')!),
sorting: i,
})),
};
try {
await POST(`${to.getAttribute('data-url')}/move`, {
data: columnSorting,
});
} catch (error) {
console.error(error);
2025-12-03 03:13:16 +01:00
if (oldIndex !== undefined) {
from.insertBefore(item, from.children[oldIndex]);
}
}
}
2024-12-19 09:37:12 +01:00
async function initRepoProjectSortable(): Promise<void> {
// the HTML layout is: #project-board.board > .project-column .cards > .issue-card
2026-02-02 01:00:34 +08:00
const mainBoard = document.querySelector<HTMLElement>('#project-board')!;
2024-12-19 09:37:12 +01:00
let boardColumns = mainBoard.querySelectorAll<HTMLElement>('.project-column');
2023-07-17 20:06:37 +02:00
createSortable(mainBoard, {
2023-08-12 12:30:28 +02:00
group: 'project-column',
draggable: '.project-column',
handle: '.project-column-header',
delayOnTouchOnly: true,
delay: 500,
onSort: async () => { // eslint-disable-line @typescript-eslint/no-misused-promises
2024-12-19 09:37:12 +01:00
boardColumns = mainBoard.querySelectorAll<HTMLElement>('.project-column');
const columnSorting = {
columns: Array.from(boardColumns, (column, i) => ({
2025-12-03 03:13:16 +01:00
columnID: parseInt(column.getAttribute('data-id')!),
sorting: i,
})),
};
try {
2025-12-03 03:13:16 +01:00
await POST(mainBoard.getAttribute('data-url')!, {
data: columnSorting,
});
} catch (error) {
console.error(error);
2021-11-22 09:19:01 +01:00
}
},
});
for (const boardColumn of boardColumns) {
2026-02-02 01:00:34 +08:00
const boardCardList = boardColumn.querySelector<HTMLElement>('.cards')!;
2023-07-17 20:06:37 +02:00
createSortable(boardCardList, {
2021-11-22 09:19:01 +01:00
group: 'shared',
onAdd: moveIssue, // eslint-disable-line @typescript-eslint/no-misused-promises
onUpdate: moveIssue, // eslint-disable-line @typescript-eslint/no-misused-promises
delayOnTouchOnly: true,
delay: 500,
2021-11-22 09:19:01 +01:00
});
2020-08-17 04:07:38 +01:00
}
}
2024-12-19 09:37:12 +01:00
function initRepoProjectColumnEdit(writableProjectBoard: Element): void {
2025-12-03 03:13:16 +01:00
const elModal = document.querySelector<HTMLElement>('.ui.modal#project-column-modal-edit')!;
const elForm = elModal.querySelector<HTMLFormElement>('form')!;
2024-12-19 09:37:12 +01:00
2025-12-03 03:13:16 +01:00
const elColumnId = elForm.querySelector<HTMLInputElement>('input[name="id"]')!;
const elColumnTitle = elForm.querySelector<HTMLInputElement>('input[name="title"]')!;
const elColumnColor = elForm.querySelector<HTMLInputElement>('input[name="color"]')!;
2024-12-19 09:37:12 +01:00
const attrDataColumnId = 'data-modal-project-column-id';
const attrDataColumnTitle = 'data-modal-project-column-title-input';
const attrDataColumnColor = 'data-modal-project-column-color-input';
// the "new" button is not in project board, so need to query from document
queryElems(document, '.show-project-column-modal-edit', (el) => {
el.addEventListener('click', () => {
2025-12-03 03:13:16 +01:00
elColumnId.value = el.getAttribute(attrDataColumnId)!;
elColumnTitle.value = el.getAttribute(attrDataColumnTitle)!;
elColumnColor.value = el.getAttribute(attrDataColumnColor)!;
2024-12-19 09:37:12 +01:00
elColumnColor.dispatchEvent(new Event('input', {bubbles: true})); // trigger the color picker
2023-04-19 23:28:28 +09:00
});
});
2021-02-11 17:32:27 +01:00
2024-12-19 09:37:12 +01:00
elForm.addEventListener('submit', async (e) => {
2020-08-17 04:07:38 +01:00
e.preventDefault();
2024-12-19 09:37:12 +01:00
const columnId = elColumnId.value;
const actionBaseLink = elForm.getAttribute('data-action-base-link');
const formData = new FormData(elForm);
const formLink = columnId ? `${actionBaseLink}/${columnId}` : `${actionBaseLink}/columns/new`;
const formMethod = columnId ? 'PUT' : 'POST';
try {
elForm.classList.add('is-loading');
await request(formLink, {method: formMethod, data: formData});
if (!columnId) {
window.location.reload(); // newly added column, need to reload the page
return;
}
// update the newly saved column title and color in the project board (to avoid reload)
2025-12-03 03:13:16 +01:00
const elEditButton = writableProjectBoard.querySelector<HTMLButtonElement>(`.show-project-column-modal-edit[${attrDataColumnId}="${columnId}"]`)!;
2024-12-19 09:37:12 +01:00
elEditButton.setAttribute(attrDataColumnTitle, elColumnTitle.value);
elEditButton.setAttribute(attrDataColumnColor, elColumnColor.value);
2025-12-03 03:13:16 +01:00
const elBoardColumn = writableProjectBoard.querySelector<HTMLElement>(`.project-column[data-id="${columnId}"]`)!;
const elBoardColumnTitle = elBoardColumn.querySelector<HTMLElement>(`.project-column-title-text`)!;
2024-12-19 09:37:12 +01:00
elBoardColumnTitle.textContent = elColumnTitle.value;
if (elColumnColor.value) {
const textColor = contrastColor(elColumnColor.value);
elBoardColumn.style.setProperty('background', elColumnColor.value, 'important');
elBoardColumn.style.setProperty('color', textColor, 'important');
2026-03-27 04:39:24 +01:00
queryElemChildren(elBoardColumn, '.divider', (divider: HTMLElement) => divider.style.color = textColor);
2024-12-19 09:37:12 +01:00
} else {
elBoardColumn.style.removeProperty('background');
elBoardColumn.style.removeProperty('color');
2026-03-27 04:39:24 +01:00
queryElemChildren(elBoardColumn, '.divider', (divider: HTMLElement) => divider.style.removeProperty('color'));
2024-12-19 09:37:12 +01:00
}
2025-06-28 16:15:51 +08:00
fomanticQuery(elModal).modal('hide');
2024-12-19 09:37:12 +01:00
} finally {
elForm.classList.remove('is-loading');
}
});
2020-08-17 04:07:38 +01:00
}
2024-12-19 09:37:12 +01:00
2026-04-04 04:03:59 +08:00
function initRepoProjectToggleFullScreen(elProjectsView: HTMLElement): void {
const enterFullscreenBtn = document.querySelector('.screen-full');
const exitFullscreenBtn = document.querySelector('.screen-normal');
if (!enterFullscreenBtn || !exitFullscreenBtn) return;
2026-04-04 04:03:59 +08:00
const settingKey = 'projects-view-options';
type ProjectsViewOptions = {
fullScreen: boolean;
};
const opts = localUserSettings.getJsonObject<ProjectsViewOptions>(settingKey, {fullScreen: false});
const toggleFullscreenState = (isFullScreen: boolean) => {
2026-04-04 04:03:59 +08:00
toggleFullScreen(elProjectsView, isFullScreen);
toggleElem(enterFullscreenBtn, !isFullScreen);
toggleElem(exitFullscreenBtn, isFullScreen);
2026-04-04 04:03:59 +08:00
opts.fullScreen = isFullScreen;
localUserSettings.setJsonObject(settingKey, opts);
};
enterFullscreenBtn.addEventListener('click', () => toggleFullscreenState(true));
exitFullscreenBtn.addEventListener('click', () => toggleFullscreenState(false));
2026-04-04 04:03:59 +08:00
if (opts.fullScreen) {
// a temporary solution to remember the full screen state, not perfect,
// just make UX better than before, especially for users who need to change the label filter frequently and want to keep full screen mode.
toggleFullscreenState(true);
}
}
2026-04-04 04:03:59 +08:00
export function initRepoProjectsView(): void {
registerGlobalInitFunc('initRepoProjectsView', (elProjectsView) => {
initRepoProjectToggleFullScreen(elProjectsView);
2026-04-04 04:03:59 +08:00
const writableProjectBoard = document.querySelector('#project-board[data-project-board-writable="true"]');
if (!writableProjectBoard) return;
2024-12-19 09:37:12 +01:00
2026-04-04 04:03:59 +08:00
initRepoProjectSortable(); // no await
initRepoProjectColumnEdit(writableProjectBoard);
});
2024-12-19 09:37:12 +01:00
}