Repository navigation
Expand file tree
/
Copy pathlibrary.php
More file actions
173 lines (166 loc) · 8.82 KB
/
Copy pathlibrary.php
File metadata and controls
173 lines (166 loc) · 8.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
<?php
$sessionsDir = __DIR__ . '/focus-sessions/';
$sessions = [];
if (is_dir($sessionsDir)) {
foreach (glob($sessionsDir . '*.json') as $file) {
if (strpos($file, '.progress.json') !== false) continue;
if (basename($file) === 'task-log.json') continue;
$id = basename($file, '.json');
$data = json_decode(file_get_contents($file), true);
$sets = isset($data['sets']) ? $data['sets'] : [];
// Extract task titles
$titles = array_filter(array_map(function($s) {
return isset($s['t']) ? trim($s['t']) : (isset($s['title']) ? trim($s['title']) : '');
}, $sets));
$sessions[] = [
'id' => $id,
'name' => isset($data['name']) && $data['name'] ? $data['name'] : 'Untitled Session',
'blocks' => count($sets),
'titles' => array_values($titles),
'modified' => filemtime($file),
];
}
}
usort($sessions, function($a, $b) { return $b['modified'] - $a['modified']; });
// Deduplicate: keep only the most recent session per name
$seen = [];
$deduped = [];
foreach ($sessions as $s) {
$key = strtolower(trim($s['name']));
if (!isset($seen[$key])) {
$seen[$key] = true;
$deduped[] = $s;
}
}
$sessions = $deduped;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Focus Sessions</title>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet">
<style>
:root { --bg:#0a0a0a;--surface:#141414;--border:#2a2a2a;--text:#f0f0f0;--muted:#666;--work:#7b8cff;--radius:12px; }
*{box-sizing:border-box;margin:0;padding:0;}
body{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh;padding:40px 20px 80px;}
.page{max-width:680px;margin:0 auto;}
h1{font-family:'Bebas Neue',sans-serif;font-size:clamp(3rem,8vw,5rem);letter-spacing:0.04em;color:var(--work);line-height:1;margin-bottom:6px;}
.subtitle{color:var(--muted);font-size:0.9rem;margin-bottom:40px;}
.list{display:flex;flex-direction:column;gap:10px;}
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 20px;transition:border-color 0.2s;}
.card:hover{border-color:var(--work);}
.card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:6px;}
.card-name{font-family:'Bebas Neue',sans-serif;font-size:1.3rem;letter-spacing:0.05em;color:var(--text);text-decoration:none;display:block;line-height:1.2;}
.card-name:hover{color:var(--work);}
.card-meta{font-size:0.72rem;color:var(--muted);letter-spacing:0.05em;white-space:nowrap;padding-top:4px;}
.card-tasks{margin-top:8px;}
.card-task-item{font-size:0.78rem;color:var(--muted);line-height:1.4;padding:2px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;}
.card-task-item::before{content:'·';margin-right:6px;opacity:0.4;}
.card-tasks-more{font-size:0.72rem;color:var(--muted);opacity:0.5;padding:2px 0 0 14px;}
.card-actions{display:flex;gap:8px;margin-top:12px;align-items:center;}
.btn-open{background:var(--work);color:#fff;font-family:'Bebas Neue',sans-serif;font-size:0.9rem;letter-spacing:0.08em;border:none;border-radius:6px;padding:6px 14px;cursor:pointer;text-decoration:none;}
.btn-open:hover{opacity:0.85;}
.btn-copy{background:none;border:1px solid var(--border);color:var(--muted);font-family:'DM Sans',sans-serif;font-size:0.78rem;border-radius:6px;padding:6px 12px;cursor:pointer;transition:all 0.15s;}
.btn-copy:hover{border-color:var(--work);color:var(--work);}
.btn-delete{background:none;border:1px solid var(--border);color:var(--muted);font-family:'DM Sans',sans-serif;font-size:0.78rem;border-radius:6px;padding:6px 12px;cursor:pointer;transition:all 0.15s;margin-left:auto;}
.btn-delete:hover{border-color:#f44;color:#f44;}
.empty{text-align:center;color:var(--muted);padding:60px 0;font-size:0.95rem;line-height:1.8;}
.empty a{color:var(--work);}
.new-btn{display:block;width:100%;margin-top:8px;padding:14px;background:transparent;border:2px dashed var(--border);border-radius:var(--radius);color:var(--muted);font-family:'DM Sans',sans-serif;font-size:0.9rem;text-align:center;text-decoration:none;transition:all 0.2s;}
.new-btn:hover{border-color:var(--work);color:var(--work);}
.nav{display:flex;justify-content:center;gap:24px;margin-top:40px;padding-top:24px;border-top:1px solid var(--border);}
.nav a{color:var(--muted);font-size:0.78rem;letter-spacing:0.08em;text-decoration:none;text-transform:uppercase;}
.nav a:hover{color:var(--work);}
#theme-toggle{position:fixed;top:14px;right:16px;background:none;border:none;cursor:pointer;font-size:1.3rem;opacity:0.4;padding:4px;}
#theme-toggle:hover{opacity:0.85;}
body.light-mode{--bg:#f0ede8;--surface:#fff;--border:#ddd;--text:#111;--muted:#888;--work:#3a4fd4;}
</style>
</head>
<body>
<button id="theme-toggle" onclick="toggleTheme()">🌙</button>
<div class="page">
<h1>Focus<br>Sessions</h1>
<p class="subtitle"><?= count($sessions) ?> saved session<?= count($sessions) !== 1 ? 's' : '' ?></p>
<?php if (empty($sessions)): ?>
<div class="empty">No saved sessions yet.<br><a href="./">Open the timer</a> and save a session to see it here.</div>
<?php else: ?>
<div class="list">
<?php foreach ($sessions as $s): ?>
<div class="card">
<div class="card-top">
<div>
<a class="card-name" href="./?id=<?= urlencode($s['id']) ?>"><?= htmlspecialchars($s['name']) ?></a>
<?php if (!empty($s['titles'])): ?>
<div class="card-tasks">
<?php foreach (array_slice($s['titles'], 0, 4) as $title): ?>
<div class="card-task-item"><?= htmlspecialchars($title) ?></div>
<?php endforeach; ?>
<?php if (count($s['titles']) > 4): ?>
<div class="card-tasks-more">+<?= count($s['titles'])-4 ?> more</div>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<div class="card-meta"><?= $s['blocks'] ?> block<?= $s['blocks'] !== 1 ? 's' : '' ?><br><?= date('M j', $s['modified']) ?></div>
</div>
<div class="card-actions">
<a class="btn-open" href="./?id=<?= urlencode($s['id']) ?>">▶ Open</a>
<button class="btn-copy" onclick="copyToNew('<?= htmlspecialchars($s['id']) ?>')">✥ Copy to new session</button>
<button class="btn-delete" onclick="deleteSession('<?= htmlspecialchars($s['id']) ?>', this)">🗑 Delete</button>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<a class="new-btn" href="./">+ New Session</a>
<nav class="nav">
<a href="./">▶ Timer</a>
<a href="log-page.php">✓ Task Log</a>
</nav>
</div>
<script>
function toggleTheme(){var l=document.body.classList.toggle('light-mode');document.getElementById('theme-toggle').textContent=l?'🌑':'🌙';try{localStorage.setItem('theme',l?'light':'dark');}catch(e){}}
(function(){try{if(localStorage.getItem('theme')==='light'){document.body.classList.add('light-mode');document.addEventListener('DOMContentLoaded',function(){var b=document.getElementById('theme-toggle');if(b)b.textContent='🌑';});}}catch(e){}})();
function deleteSession(id, btn) {
if (!confirm('Delete this session? This cannot be undone.')) return;
fetch('sessions.php?id=' + encodeURIComponent(id), { method: 'DELETE' })
.then(function(r) { return r.json(); })
.then(function() {
var card = btn.closest('.card');
if (card) {
card.style.transition = 'opacity 0.3s, transform 0.3s';
card.style.opacity = 0;
card.style.transform = 'translateX(20px)';
setTimeout(function() {
card.remove();
var remaining = document.querySelectorAll('.card').length;
var sub = document.querySelector('.subtitle');
if (sub) sub.textContent = remaining + ' saved session' + (remaining !== 1 ? 's' : '');
}, 320);
}
})
.catch(function() { alert('Could not delete session.'); });
}
function copyToNew(id) {
fetch('sessions.php?id=' + encodeURIComponent(id))
.then(function(r){ return r.json(); })
.then(function(payload) {
// Strip the name so it loads as a fresh unnamed session with the same blocks
var newData = { name: '', sets: payload.sets };
return fetch('sessions.php', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify(newData)
});
})
.then(function(r){ return r.json(); })
.then(function(json) {
if (json.id) window.location.href = './?id=' + json.id;
})
.catch(function(){ alert('Could not copy session.'); });
}
</script>
</body>
</html>