mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-01-20 15:22:58 -05:00
lti
This commit is contained in:
51
templates/lti/deep_link_return.html
Normal file
51
templates/lti/deep_link_return.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Returning to Course...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.loading {
|
||||
text-align: center;
|
||||
}
|
||||
.spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #2196F3;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>Returning to your course...</p>
|
||||
</div>
|
||||
|
||||
<!-- Auto-submit form that posts JWT back to Moodle -->
|
||||
<form id="deepLinkReturnForm" method="post" action="{{ return_url }}" style="display: none;">
|
||||
<input type="hidden" name="JWT" value="{{ jwt }}">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
// Auto-submit on page load
|
||||
document.getElementById('deepLinkReturnForm').submit();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
58
templates/lti/launch_error.html
Normal file
58
templates/lti/launch_error.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LTI Launch Error</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.error-container {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #d32f2f;
|
||||
margin-top: 0;
|
||||
}
|
||||
.error-icon {
|
||||
font-size: 48px;
|
||||
color: #d32f2f;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.error-message {
|
||||
background: #ffebee;
|
||||
border-left: 4px solid #d32f2f;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.help-text {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-container">
|
||||
<div class="error-icon">⚠️</div>
|
||||
<h1>{{ error }}</h1>
|
||||
<div class="error-message">
|
||||
<strong>Error Details:</strong><br>
|
||||
{{ message }}
|
||||
</div>
|
||||
<div class="help-text">
|
||||
<p>If this problem persists, please contact your system administrator.</p>
|
||||
<p>You may close this window and try again.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
222
templates/lti/select_media.html
Normal file
222
templates/lti/select_media.html
Normal file
@@ -0,0 +1,222 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Select Media - MediaCMS</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.header {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
.filter-bar {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
.media-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
.media-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.media-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
.media-card.selected {
|
||||
border: 3px solid #2196F3;
|
||||
}
|
||||
.media-thumbnail {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
object-fit: cover;
|
||||
background: #eee;
|
||||
}
|
||||
.media-info {
|
||||
padding: 15px;
|
||||
}
|
||||
.media-title {
|
||||
font-weight: 600;
|
||||
margin: 0 0 5px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.media-meta {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
.checkbox-wrapper {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
.checkbox-wrapper input[type="checkbox"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
box-shadow: 0 -2px 4px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.btn-primary {
|
||||
background: #2196F3;
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: #1976D2;
|
||||
}
|
||||
.btn-secondary {
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
.selected-count {
|
||||
color: #666;
|
||||
}
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Select Media to Embed</h1>
|
||||
<p>Choose one or more media items to add to your course</p>
|
||||
</div>
|
||||
|
||||
<div class="filter-bar">
|
||||
<label>
|
||||
<input type="checkbox" id="myMediaOnly" {% if show_my_media_only %}checked{% endif %}>
|
||||
Show only my media
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<form id="selectMediaForm" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="media-grid">
|
||||
{% for media in media_list %}
|
||||
<div class="media-card" data-media-id="{{ media.id }}" onclick="toggleMedia({{ media.id }})">
|
||||
<div style="position: relative;">
|
||||
{% if media.thumbnail_url %}
|
||||
<img src="{{ media.thumbnail_url }}" alt="{{ media.title }}" class="media-thumbnail">
|
||||
{% else %}
|
||||
<div class="media-thumbnail" style="display: flex; align-items: center; justify-content: center; background: #e0e0e0;">
|
||||
<span style="font-size: 48px;">🎬</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="checkbox" name="media_ids[]" value="{{ media.id }}" id="media_{{ media.id }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="media-info">
|
||||
<h3 class="media-title">{{ media.title }}</h3>
|
||||
<div class="media-meta">
|
||||
By {{ media.user.name|default:media.user.username }}<br>
|
||||
{{ media.add_date|date:"M d, Y" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="empty-state">
|
||||
<h3>No media found</h3>
|
||||
<p>Try adjusting your filter settings</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="bottom-bar">
|
||||
<span class="selected-count">
|
||||
<strong id="selectedCount">0</strong> media selected
|
||||
</span>
|
||||
<div>
|
||||
<button type="button" class="btn btn-secondary" onclick="window.close()">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary" id="submitBtn" disabled>Add to Course</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function toggleMedia(mediaId) {
|
||||
const checkbox = document.getElementById('media_' + mediaId);
|
||||
checkbox.checked = !checkbox.checked;
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
function updateSelection() {
|
||||
const checkboxes = document.querySelectorAll('input[name="media_ids[]"]');
|
||||
let count = 0;
|
||||
|
||||
checkboxes.forEach(cb => {
|
||||
const card = cb.closest('.media-card');
|
||||
if (cb.checked) {
|
||||
card.classList.add('selected');
|
||||
count++;
|
||||
} else {
|
||||
card.classList.remove('selected');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('selectedCount').textContent = count;
|
||||
document.getElementById('submitBtn').disabled = count === 0;
|
||||
}
|
||||
|
||||
// Initialize
|
||||
document.querySelectorAll('input[name="media_ids[]"]').forEach(cb => {
|
||||
cb.addEventListener('change', function(e) {
|
||||
e.stopPropagation();
|
||||
updateSelection();
|
||||
});
|
||||
});
|
||||
|
||||
// My media filter
|
||||
document.getElementById('myMediaOnly').addEventListener('change', function() {
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('my_media_only', this.checked);
|
||||
window.location = url.toString();
|
||||
});
|
||||
|
||||
updateSelection();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user