584 lines
16 KiB
PHP
584 lines
16 KiB
PHP
<?php
|
||
$is_player = $selected_deck !== null;
|
||
$caption_colors = array('#e86a5c', '#2f9e99', '#f0a04b');
|
||
include('template/' . $OJ_TEMPLATE . '/header.php');
|
||
?>
|
||
|
||
<style>
|
||
body#MainBg-C.cw-catalog-page {
|
||
background: #f3eadc !important;
|
||
background-image:
|
||
repeating-linear-gradient(
|
||
to bottom,
|
||
rgba(120, 90, 50, 0.05) 0,
|
||
rgba(120, 90, 50, 0.05) 1px,
|
||
transparent 1px,
|
||
transparent 32px
|
||
) !important;
|
||
}
|
||
|
||
body#MainBg-C.cw-player-page {
|
||
background: #1b2430 !important;
|
||
}
|
||
|
||
#main.ui.main.container.cw-main {
|
||
width: 100% !important;
|
||
max-width: none !important;
|
||
margin: 0 !important;
|
||
padding: 0 24px 48px !important;
|
||
}
|
||
|
||
.cw-player-page #main.ui.main.container.cw-main {
|
||
padding: 0 !important;
|
||
}
|
||
|
||
.cw-player-page .footer {
|
||
display: none;
|
||
}
|
||
|
||
.cw-hero {
|
||
text-align: center;
|
||
padding: 28px 0 8px;
|
||
}
|
||
|
||
.cw-hero h1 {
|
||
margin: 0;
|
||
font-size: 3.4rem;
|
||
font-weight: 800;
|
||
color: #e86a5c;
|
||
letter-spacing: 0.04em;
|
||
line-height: 1.1;
|
||
}
|
||
|
||
.cw-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||
gap: 28px;
|
||
max-width: 1180px;
|
||
margin: 28px auto 0;
|
||
}
|
||
|
||
.cw-card {
|
||
display: block;
|
||
background: #fff;
|
||
border-radius: 18px;
|
||
overflow: hidden;
|
||
text-decoration: none !important;
|
||
color: inherit;
|
||
box-shadow: 0 14px 32px rgba(80, 50, 20, 0.12);
|
||
transform: translateY(0);
|
||
transition: transform 0.18s ease, box-shadow 0.18s ease;
|
||
}
|
||
|
||
.cw-card:hover,
|
||
.cw-card:focus {
|
||
transform: translateY(-4px);
|
||
box-shadow: 0 20px 40px rgba(80, 50, 20, 0.16);
|
||
}
|
||
|
||
.cw-card-cover {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
aspect-ratio: 16 / 9;
|
||
background:
|
||
radial-gradient(circle at 20% 20%, rgba(255,255,255,0.35), transparent 42%),
|
||
linear-gradient(160deg, #f7d3c8, #f6efe4 55%, #d8efe9);
|
||
}
|
||
|
||
.cw-card-meta {
|
||
display: flex;
|
||
align-items: baseline;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
padding: 14px 16px 16px;
|
||
color: #fff;
|
||
}
|
||
|
||
.cw-card-title {
|
||
font-size: 1.05rem;
|
||
font-weight: 700;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.cw-empty {
|
||
max-width: 640px;
|
||
margin: 40px auto 0;
|
||
padding: 28px 24px;
|
||
text-align: center;
|
||
background: rgba(255, 255, 255, 0.72);
|
||
border-radius: 18px;
|
||
color: #4b3b2a;
|
||
font-size: 1.05rem;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
.cw-theater {
|
||
min-height: calc(100vh - 77px);
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 18px 20px 24px;
|
||
color: #f4f1ea;
|
||
}
|
||
|
||
.cw-back {
|
||
align-self: flex-start;
|
||
color: #f4f1ea !important;
|
||
font-size: 1.05rem;
|
||
font-weight: 600;
|
||
text-decoration: underline;
|
||
text-underline-offset: 4px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.cw-stage {
|
||
flex: 1;
|
||
display: grid;
|
||
grid-template-columns: 72px minmax(0, 1fr) 72px;
|
||
align-items: center;
|
||
gap: 12px;
|
||
min-height: 0;
|
||
}
|
||
|
||
.cw-nav-btn {
|
||
width: 64px;
|
||
height: 64px;
|
||
border: 0;
|
||
border-radius: 50%;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 2rem;
|
||
cursor: pointer;
|
||
color: #fff;
|
||
background: #5b6570;
|
||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.25);
|
||
}
|
||
|
||
.cw-nav-btn.next {
|
||
background: #e86a5c;
|
||
}
|
||
|
||
.cw-nav-btn.is-disabled,
|
||
.cw-nav-btn:disabled {
|
||
opacity: 0.28;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.cw-frame {
|
||
position: relative;
|
||
width: 100%;
|
||
max-height: calc(100vh - 220px);
|
||
aspect-ratio: 16 / 9;
|
||
margin: 0 auto;
|
||
background: #fff;
|
||
border-radius: 18px;
|
||
overflow: hidden;
|
||
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.cw-frame canvas {
|
||
display: block;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.cw-status {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 24px;
|
||
text-align: center;
|
||
color: #3b3228;
|
||
font-size: 1.15rem;
|
||
font-weight: 600;
|
||
background: #fff8ee;
|
||
}
|
||
|
||
.cw-status.is-error {
|
||
color: #9b2c2c;
|
||
}
|
||
|
||
.cw-status[hidden] {
|
||
display: none;
|
||
}
|
||
|
||
.cw-pager {
|
||
text-align: center;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.cw-pager-num {
|
||
font-size: 1.8rem;
|
||
font-weight: 800;
|
||
letter-spacing: 0.04em;
|
||
}
|
||
|
||
.cw-pager-num em {
|
||
font-style: normal;
|
||
color: #e86a5c;
|
||
border-bottom: 4px solid #e86a5c;
|
||
padding: 0 2px;
|
||
}
|
||
|
||
.cw-dots {
|
||
display: flex;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.cw-dots button {
|
||
width: 10px;
|
||
height: 10px;
|
||
padding: 0;
|
||
border: 0;
|
||
border-radius: 50%;
|
||
background: #7b8490;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.cw-dots button.is-current {
|
||
background: #e86a5c;
|
||
}
|
||
|
||
.cw-dots button:disabled {
|
||
cursor: default;
|
||
}
|
||
|
||
.cw-mobile-nav {
|
||
display: none;
|
||
justify-content: space-between;
|
||
padding: 8px 8px 0;
|
||
}
|
||
|
||
@media (max-width: 991px) {
|
||
.cw-hero h1 {
|
||
font-size: 2.4rem;
|
||
}
|
||
|
||
.cw-stage {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.cw-frame {
|
||
max-height: none;
|
||
}
|
||
|
||
.cw-mobile-nav {
|
||
display: flex;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
document.body.classList.add('<?php echo $is_player ? 'cw-player-page' : 'cw-catalog-page'; ?>');
|
||
var main = document.getElementById('main');
|
||
if (main) {
|
||
main.classList.add('cw-main');
|
||
}
|
||
</script>
|
||
|
||
<?php if ($is_player) { ?>
|
||
<div class="cw-theater">
|
||
<a class="cw-back" href="courseware.php"><?php echo htmlspecialchars($MSG_COURSEWARE_BACK, ENT_QUOTES, 'UTF-8'); ?></a>
|
||
<div class="cw-stage">
|
||
<button type="button" class="cw-nav-btn desktop-only" id="cw-prev" aria-label="<?php echo htmlspecialchars($MSG_COURSEWARE_PREV, ENT_QUOTES, 'UTF-8'); ?>" disabled aria-disabled="true">‹</button>
|
||
<div class="cw-frame" id="cw-frame">
|
||
<canvas id="cw-canvas"></canvas>
|
||
<div class="cw-status" id="cw-status" role="status" aria-live="polite"><?php echo htmlspecialchars($MSG_COURSEWARE_OPENING, ENT_QUOTES, 'UTF-8'); ?></div>
|
||
</div>
|
||
<button type="button" class="cw-nav-btn next desktop-only" id="cw-next" aria-label="<?php echo htmlspecialchars($MSG_COURSEWARE_NEXT, ENT_QUOTES, 'UTF-8'); ?>" disabled aria-disabled="true">›</button>
|
||
</div>
|
||
<div class="cw-mobile-nav">
|
||
<button type="button" class="cw-nav-btn" id="cw-prev-m" aria-label="<?php echo htmlspecialchars($MSG_COURSEWARE_PREV, ENT_QUOTES, 'UTF-8'); ?>" disabled aria-disabled="true">‹</button>
|
||
<button type="button" class="cw-nav-btn next" id="cw-next-m" aria-label="<?php echo htmlspecialchars($MSG_COURSEWARE_NEXT, ENT_QUOTES, 'UTF-8'); ?>" disabled aria-disabled="true">›</button>
|
||
</div>
|
||
<div class="cw-pager">
|
||
<div class="cw-pager-num" aria-live="polite"><em id="cw-page-now"><?php echo intval($current_page); ?></em> / <span id="cw-page-total"><?php echo htmlspecialchars($MSG_COURSEWARE_PAGE_UNKNOWN, ENT_QUOTES, 'UTF-8'); ?></span></div>
|
||
<div class="cw-dots" id="cw-dots" role="tablist"></div>
|
||
</div>
|
||
</div>
|
||
<script src="<?php echo $path_fix; ?>include/pptxviewjs/jszip.min.js"></script>
|
||
<script src="<?php echo $path_fix; ?>include/pptxviewjs/PptxViewJS.min.js"></script>
|
||
<script>
|
||
(function () {
|
||
var slug = <?php echo json_encode($selected_deck['slug'], JSON_UNESCAPED_UNICODE); ?>;
|
||
var page = <?php echo intval($current_page); ?>;
|
||
var desiredPage = page;
|
||
var total = 0;
|
||
var totalKnown = false;
|
||
var ready = false;
|
||
var failed = false;
|
||
var viewer = null;
|
||
var navQueue = Promise.resolve();
|
||
var navBusy = false;
|
||
var canvas = document.getElementById('cw-canvas');
|
||
var statusEl = document.getElementById('cw-status');
|
||
var buttons = {
|
||
prev: [document.getElementById('cw-prev'), document.getElementById('cw-prev-m')],
|
||
next: [document.getElementById('cw-next'), document.getElementById('cw-next-m')]
|
||
};
|
||
var msg = {
|
||
unknown: <?php echo json_encode($MSG_COURSEWARE_PAGE_UNKNOWN, JSON_UNESCAPED_UNICODE); ?>,
|
||
pageN: <?php echo json_encode($MSG_COURSEWARE_PAGE_N, JSON_UNESCAPED_UNICODE); ?>,
|
||
login: <?php echo json_encode($MSG_COURSEWARE_ERR_LOGIN, JSON_UNESCAPED_UNICODE); ?>,
|
||
missing: <?php echo json_encode($MSG_COURSEWARE_ERR_NOT_FOUND, JSON_UNESCAPED_UNICODE); ?>,
|
||
broken: <?php echo json_encode($MSG_COURSEWARE_ERR_BROKEN, JSON_UNESCAPED_UNICODE); ?>,
|
||
player: <?php echo json_encode($MSG_COURSEWARE_ERR_PLAYER, JSON_UNESCAPED_UNICODE); ?>
|
||
};
|
||
|
||
function setStatus(text, isError) {
|
||
if (!text) {
|
||
statusEl.hidden = true;
|
||
statusEl.removeAttribute('role');
|
||
statusEl.setAttribute('role', 'status');
|
||
statusEl.setAttribute('aria-live', 'polite');
|
||
return;
|
||
}
|
||
statusEl.hidden = false;
|
||
statusEl.textContent = text;
|
||
statusEl.classList.toggle('is-error', !!isError);
|
||
statusEl.setAttribute('role', isError ? 'alert' : 'status');
|
||
statusEl.setAttribute('aria-live', isError ? 'assertive' : 'polite');
|
||
}
|
||
|
||
function setBtnState(list, disabled) {
|
||
list.forEach(function (btn) {
|
||
if (!btn) {
|
||
return;
|
||
}
|
||
btn.disabled = !!disabled;
|
||
btn.setAttribute('aria-disabled', disabled ? 'true' : 'false');
|
||
btn.classList.toggle('is-disabled', !!disabled);
|
||
});
|
||
}
|
||
|
||
function clampPage(value) {
|
||
var next = parseInt(value, 10);
|
||
if (isNaN(next) || next < 1) {
|
||
next = 1;
|
||
}
|
||
if (totalKnown && total >= 1 && next > total) {
|
||
next = total;
|
||
}
|
||
return next;
|
||
}
|
||
|
||
function shownPage() {
|
||
if (viewer && ready && !failed) {
|
||
return viewer.getCurrentSlideIndex() + 1;
|
||
}
|
||
return page;
|
||
}
|
||
|
||
function updateChrome() {
|
||
if (viewer && ready) {
|
||
page = viewer.getCurrentSlideIndex() + 1;
|
||
total = viewer.getSlideCount() || total;
|
||
totalKnown = total >= 1;
|
||
}
|
||
document.getElementById('cw-page-now').textContent = String(page);
|
||
document.getElementById('cw-page-total').textContent = totalKnown ? String(total) : msg.unknown;
|
||
var disableNav = !ready || failed;
|
||
setBtnState(buttons.prev, disableNav || desiredPage <= 1);
|
||
setBtnState(buttons.next, disableNav || (totalKnown && desiredPage >= total));
|
||
var dots = document.getElementById('cw-dots');
|
||
if (totalKnown && dots.childNodes.length !== total) {
|
||
dots.innerHTML = '';
|
||
for (var i = 1; i <= total; i++) {
|
||
var dot = document.createElement('button');
|
||
dot.type = 'button';
|
||
dot.setAttribute('aria-label', msg.pageN.replace('%s', String(i)));
|
||
(function (index) {
|
||
dot.addEventListener('click', function (event) {
|
||
event.stopPropagation();
|
||
go(index);
|
||
});
|
||
})(i);
|
||
dots.appendChild(dot);
|
||
}
|
||
}
|
||
Array.prototype.forEach.call(dots.children, function (dot, idx) {
|
||
dot.classList.toggle('is-current', idx + 1 === page);
|
||
dot.disabled = !ready || failed;
|
||
dot.setAttribute('aria-disabled', (!ready || failed) ? 'true' : 'false');
|
||
});
|
||
if (window.history && window.history.replaceState) {
|
||
window.history.replaceState(null, '', 'courseware.php?deck=' + encodeURIComponent(slug) + '&page=' + page);
|
||
}
|
||
}
|
||
|
||
function go(nextPage) {
|
||
if (!viewer || !ready || failed) {
|
||
return;
|
||
}
|
||
desiredPage = clampPage(nextPage);
|
||
if (navBusy) {
|
||
return;
|
||
}
|
||
navBusy = true;
|
||
navQueue = navQueue.then(function pump() {
|
||
if (!viewer || !ready || failed) {
|
||
navBusy = false;
|
||
return;
|
||
}
|
||
var target = clampPage(desiredPage);
|
||
var current = shownPage();
|
||
if (target === current) {
|
||
page = current;
|
||
navBusy = false;
|
||
updateChrome();
|
||
return;
|
||
}
|
||
return viewer.goToSlide(target - 1).then(function () {
|
||
page = shownPage();
|
||
updateChrome();
|
||
if (clampPage(desiredPage) !== page) {
|
||
return pump();
|
||
}
|
||
navBusy = false;
|
||
});
|
||
}).catch(function () {
|
||
navBusy = false;
|
||
setStatus(msg.broken, true);
|
||
});
|
||
}
|
||
|
||
function sizeCanvas() {
|
||
var frame = document.getElementById('cw-frame');
|
||
var rect = frame.getBoundingClientRect();
|
||
var ratio = window.devicePixelRatio || 1;
|
||
canvas.width = Math.max(1, Math.floor(rect.width * ratio));
|
||
canvas.height = Math.max(1, Math.floor(rect.height * ratio));
|
||
}
|
||
|
||
function fail(kind) {
|
||
failed = true;
|
||
ready = false;
|
||
if (kind === 'login') {
|
||
setStatus(msg.login, true);
|
||
} else if (kind === 'missing') {
|
||
setStatus(msg.missing, true);
|
||
} else if (kind === 'player') {
|
||
setStatus(msg.player, true);
|
||
} else {
|
||
setStatus(msg.broken, true);
|
||
}
|
||
updateChrome();
|
||
}
|
||
|
||
try {
|
||
if (!window.Chart || !window.JSZip || !window.PptxViewJS || !window.PptxViewJS.PPTXViewer) {
|
||
fail('player');
|
||
return;
|
||
}
|
||
|
||
sizeCanvas();
|
||
viewer = new PptxViewJS.PPTXViewer({
|
||
canvas: canvas,
|
||
slideSizeMode: 'fit',
|
||
backgroundColor: '#ffffff'
|
||
});
|
||
viewer.on('slideChanged', updateChrome);
|
||
viewer.on('renderComplete', function () {
|
||
if (!failed) {
|
||
setStatus('');
|
||
updateChrome();
|
||
}
|
||
});
|
||
|
||
viewer.loadFromArrayBuffer = function (buffer) {
|
||
return this.loadFile(buffer);
|
||
};
|
||
|
||
fetch('courseware-file.php?deck=' + encodeURIComponent(slug), { credentials: 'same-origin' }).then(function (res) {
|
||
if (res.status === 401) {
|
||
throw { kind: 'login' };
|
||
}
|
||
if (res.status === 404 || !res.ok) {
|
||
throw { kind: 'missing' };
|
||
}
|
||
return res.arrayBuffer();
|
||
}).then(function (buf) {
|
||
return viewer.loadFromArrayBuffer(buf);
|
||
}).then(function () {
|
||
total = viewer.getSlideCount() || 0;
|
||
totalKnown = total >= 1;
|
||
if (!totalKnown) {
|
||
throw { kind: 'broken' };
|
||
}
|
||
ready = true;
|
||
page = clampPage(page);
|
||
desiredPage = page;
|
||
return viewer.goToSlide(page - 1);
|
||
}).then(function () {
|
||
setStatus('');
|
||
updateChrome();
|
||
}).catch(function (err) {
|
||
fail(err && err.kind ? err.kind : 'broken');
|
||
});
|
||
} catch (err) {
|
||
fail('player');
|
||
}
|
||
|
||
buttons.prev.forEach(function (btn) {
|
||
if (btn) btn.addEventListener('click', function () { go(desiredPage - 1); });
|
||
});
|
||
buttons.next.forEach(function (btn) {
|
||
if (btn) btn.addEventListener('click', function () { go(desiredPage + 1); });
|
||
});
|
||
document.addEventListener('keydown', function (event) {
|
||
if (event.key === 'ArrowLeft') {
|
||
event.preventDefault();
|
||
go(desiredPage - 1);
|
||
}
|
||
if (event.key === 'ArrowRight' || event.key === ' ') {
|
||
event.preventDefault();
|
||
go(desiredPage + 1);
|
||
}
|
||
if (event.key === 'Escape') window.location.href = 'courseware.php';
|
||
});
|
||
document.getElementById('cw-frame').addEventListener('click', function (event) {
|
||
var rect = this.getBoundingClientRect();
|
||
var x = event.clientX - rect.left;
|
||
if (x < rect.width * 0.4) go(desiredPage - 1);
|
||
if (x > rect.width * 0.6) go(desiredPage + 1);
|
||
});
|
||
window.addEventListener('resize', function () {
|
||
sizeCanvas();
|
||
if (viewer && ready && !failed) viewer.render();
|
||
});
|
||
updateChrome();
|
||
})();
|
||
</script>
|
||
<?php } else { ?>
|
||
<div class="cw-hero">
|
||
<h1><?php echo htmlspecialchars($MSG_COURSEWARE, ENT_QUOTES, 'UTF-8'); ?></h1>
|
||
</div>
|
||
|
||
<?php if (empty($decks)) { ?>
|
||
<div class="cw-empty"><?php echo htmlspecialchars($MSG_COURSEWARE_EMPTY, ENT_QUOTES, 'UTF-8'); ?></div>
|
||
<?php } else { ?>
|
||
<div class="cw-grid">
|
||
<?php foreach ($decks as $index => $deck) {
|
||
$bar = $caption_colors[$index % count($caption_colors)];
|
||
?>
|
||
<a class="cw-card" href="courseware.php?deck=<?php echo rawurlencode($deck['slug']); ?>">
|
||
<div class="cw-card-cover" aria-hidden="true"></div>
|
||
<div class="cw-card-meta" style="background:<?php echo $bar; ?>">
|
||
<div class="cw-card-title"><?php echo htmlspecialchars($deck['title'], ENT_QUOTES, 'UTF-8'); ?></div>
|
||
</div>
|
||
</a>
|
||
<?php } ?>
|
||
</div>
|
||
<?php } ?>
|
||
<?php } ?>
|
||
|
||
<?php include('template/' . $OJ_TEMPLATE . '/footer.php'); ?>
|