229 lines
9.9 KiB
PHP
229 lines
9.9 KiB
PHP
<?php
|
||
// phpcs:disable PEAR.Commenting.FileComment,PEAR.Commenting.ClassComment
|
||
|
||
/**
|
||
* Main application entrypoint.
|
||
*/
|
||
|
||
declare(strict_types=1);
|
||
|
||
require_once dirname(__DIR__) . '/bootstrap.php';
|
||
|
||
use IronKanban\Repository\ProjectRepository;
|
||
use IronKanban\Service\BoardService;
|
||
|
||
$projectRepository = new ProjectRepository();
|
||
$projects = $projectRepository->getAll();
|
||
$requestedProject = isset($_GET['project']) ? trim((string) $_GET['project']) : '';
|
||
$activeProjectId = $requestedProject !== '' ? $requestedProject : ($projects[0]->id ?? '');
|
||
$boardService = new BoardService();
|
||
$initialState = null;
|
||
|
||
if ($activeProjectId !== '') {
|
||
try {
|
||
$initialState = $boardService->getBoardState($activeProjectId);
|
||
} catch (Throwable $exception) {
|
||
$initialState = null;
|
||
}
|
||
}
|
||
?>
|
||
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="csrf-token" content="<?php echo e(csrf_token()); ?>">
|
||
<title><?php echo e((string) config('app_name')); ?></title>
|
||
<link rel="stylesheet" href="/assets/app.css">
|
||
</head>
|
||
<body>
|
||
<div class="app-shell">
|
||
<aside class="sidebar">
|
||
<div>
|
||
<p class="eyebrow">Git-backed markdown kanban</p>
|
||
<h1>IronKanban</h1>
|
||
<p class="sidebar-copy">Projects stay as flat files, while the UI gives you drag-and-drop columns, quick edits, notes, and polling-based refreshes.</p>
|
||
</div>
|
||
<section class="project-list">
|
||
<div class="section-heading">
|
||
<h2>Projects</h2>
|
||
<span><?php echo count($projects); ?></span>
|
||
</div>
|
||
<?php if ($projects === []) : ?>
|
||
<div class="empty-state compact">
|
||
<p>No projects found in <code><?php echo e(project_root()); ?></code>.</p>
|
||
</div>
|
||
<?php else : ?>
|
||
<?php foreach ($projects as $project) : ?>
|
||
<a class="project-link<?php echo $project->id === $activeProjectId ? ' active' : ''; ?>" href="/?project=<?php echo rawurlencode($project->id); ?>">
|
||
<strong><?php echo e($project->title); ?></strong>
|
||
<span><?php echo e($project->id); ?></span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</section>
|
||
<section class="project-root">
|
||
<div class="section-heading">
|
||
<h2>Storage</h2>
|
||
</div>
|
||
<p><code><?php echo e(project_root()); ?></code></p>
|
||
</section>
|
||
</aside>
|
||
|
||
<main class="workspace" id="app" data-project-id="<?php echo e($activeProjectId); ?>">
|
||
<?php if ($initialState === null) : ?>
|
||
<section class="hero empty-state">
|
||
<h2>No project selected</h2>
|
||
<p>Create or copy a project folder into <code><?php echo e(project_root()); ?></code> using the markdown layout from the notes.</p>
|
||
</section>
|
||
<?php else : ?>
|
||
<header class="workspace-header">
|
||
<div>
|
||
<p class="eyebrow">Project Board</p>
|
||
<h2><?php echo e((string) $initialState['project']['title']); ?></h2>
|
||
<p class="project-description"><?php echo nl2br(e((string) $initialState['project']['body'])); ?></p>
|
||
</div>
|
||
<div class="header-actions">
|
||
<button type="button" class="button ghost" data-action="new-column">New Column</button>
|
||
<button type="button" class="button primary" data-action="new-task">New Task</button>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="workspace-stack">
|
||
<section class="notes-panel is-collapsible is-collapsed" id="notes-panel" data-collapsible>
|
||
<div class="notes-panel-header">
|
||
<div>
|
||
<p class="eyebrow">Project Notes</p>
|
||
<h2>Notes</h2>
|
||
</div>
|
||
<div class="notes-panel-actions">
|
||
<button type="button" class="button ghost compact" data-action="new-note">Add Note</button>
|
||
<button
|
||
type="button"
|
||
class="button ghost compact collapse-toggle"
|
||
data-action="toggle-notes"
|
||
data-target="notes-list"
|
||
aria-expanded="false"
|
||
aria-controls="notes-list"
|
||
>
|
||
Show Notes
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div id="notes-list" class="notes-list" hidden></div>
|
||
</section>
|
||
|
||
<section class="board-panel">
|
||
<div class="board-scroll">
|
||
<div class="board-columns" id="board-columns"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="trash-panel" id="trash-panel">
|
||
<div class="trash-panel-header">
|
||
<div>
|
||
<p class="eyebrow">Discarded Tasks</p>
|
||
<h2>Trash</h2>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
class="button ghost compact collapse-toggle"
|
||
data-action="toggle-trash"
|
||
aria-expanded="false"
|
||
aria-controls="trash-columns-wrap"
|
||
>
|
||
Show Trash
|
||
</button>
|
||
</div>
|
||
<div class="trash-dropzone" id="trash-dropzone" data-column-id="trash">
|
||
<p class="trash-dropzone-title">Drop tasks here to discard them</p>
|
||
<p class="trash-dropzone-meta" id="trash-dropzone-meta">Trash is empty.</p>
|
||
</div>
|
||
<div id="trash-columns-wrap" class="trash-columns-wrap" hidden>
|
||
<div class="board-scroll">
|
||
<div class="board-columns board-columns-trash" id="trash-columns"></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
<?php endif; ?>
|
||
</main>
|
||
</div>
|
||
|
||
<dialog id="task-dialog" class="dialog">
|
||
<form method="dialog" class="dialog-card" id="task-form">
|
||
<div class="dialog-header">
|
||
<h3 id="task-dialog-title">Task</h3>
|
||
<button type="submit" class="icon-button" aria-label="Close">×</button>
|
||
</div>
|
||
<input type="hidden" name="id">
|
||
<label>
|
||
<span>Title</span>
|
||
<input type="text" name="title" maxlength="200" required>
|
||
</label>
|
||
<label>
|
||
<span>Column</span>
|
||
<select name="column"></select>
|
||
</label>
|
||
<label>
|
||
<span>Priority</span>
|
||
<select name="priority">
|
||
<option value="low">Low</option>
|
||
<option value="normal">Normal</option>
|
||
<option value="high">High</option>
|
||
<option value="urgent">Urgent</option>
|
||
</select>
|
||
</label>
|
||
<label class="checkbox-row">
|
||
<input type="checkbox" name="completed">
|
||
<span>Completed</span>
|
||
</label>
|
||
<label class="checkbox-row">
|
||
<input type="checkbox" name="is_active" checked>
|
||
<span>Active</span>
|
||
</label>
|
||
<label>
|
||
<span>Markdown</span>
|
||
<textarea name="body" rows="12"></textarea>
|
||
</label>
|
||
<div class="dialog-actions">
|
||
<button type="button" class="button ghost" data-action="trash-task">Move To Trash</button>
|
||
<button type="submit" class="button primary">Save Task</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<dialog id="note-dialog" class="dialog">
|
||
<form method="dialog" class="dialog-card" id="note-form">
|
||
<div class="dialog-header">
|
||
<h3 id="note-dialog-title">Note</h3>
|
||
<button type="submit" class="icon-button" aria-label="Close">×</button>
|
||
</div>
|
||
<input type="hidden" name="id">
|
||
<label>
|
||
<span>Title</span>
|
||
<input type="text" name="title" maxlength="200" required>
|
||
</label>
|
||
<label>
|
||
<span>Markdown</span>
|
||
<textarea name="body" rows="14"></textarea>
|
||
</label>
|
||
<div class="dialog-actions">
|
||
<button type="submit" class="button primary">Save Note</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<script id="initial-state" type="application/json"><?php echo
|
||
$initialState !== null
|
||
? (string) json_encode(
|
||
$initialState,
|
||
JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
|
||
)
|
||
: 'null';
|
||
?></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js"></script>
|
||
<script src="/assets/app.js"></script>
|
||
</body>
|
||
</html>
|