Skip to content

Commit

Permalink
Add missing/moved template.
Browse files Browse the repository at this point in the history
  • Loading branch information
liffiton committed Jul 21, 2024
1 parent 16ed7b0 commit 7d94c03
Showing 1 changed file with 229 additions and 0 deletions.
229 changes: 229 additions & 0 deletions src/codehelp/templates/context_config.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@

<div class="content">
<h2 class="title is-size-4">Contexts</h2>
<p style="max-width: 50em">Contexts provide additional information to the LLM for each query a student makes. You can have a single default context that is always used, or you can create separate contexts for individual assignments or modules. If multiple contexts are available, students will be able to select from them when making queries.</p>
{% if contexts | length == 0 %}
<p class="has-text-danger">While not strictly required, we recommend defining at least one context to specify the language(s), frameworks, and/or libraries in use in this class.</p>
{% endif %}
{# Link to the 'contexts.md' docs page if it exists #}
{% if 'contexts' in docs_pages %}
<p>See the <a href="{{ url_for('docs.page', name='contexts') }}">contexts documentation</a> for more information and suggestions.</p>
{% endif %}
<script type="text/javascript">
document.addEventListener('alpine:init', () => {
Alpine.data('reorderable', () => ({
items: {{ contexts | tojson }},
drag_index: null,

dragenter(index) {
if (this.drag_index === null) { return }
if (index === this.drag_index) { return }
// reorder the list, placing the dragged item at this index and shifting others
let new_items = [];
const drag_item = this.items[this.drag_index];
this.items.forEach((el, i) => {
if (i === this.drag_index) { return }
else if (i === index && i < this.drag_index) {
new_items.push(drag_item);
new_items.push(el);
}
else if (i === index && i > this.drag_index) {
new_items.push(el);
new_items.push(drag_item);
}
else {
new_items.push(el);
}
});
this.items = new_items; // update w/ newly ordered list
this.drag_index = index; // this new index is now the one we're dragging
},
stop_drag() {
this.drag_index = null;
// post updated ordering to save in DB
fetch("{{ url_for('context_config.update_order') }}", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(this.items.map(item => item.id)),
});
},
}));
Alpine.data('available_dropdown', () => ({
showDropdown: false,
showModal: false,
newDate: null,

// throughout, 'this.ctx' refers to a ctx object from the for loop in the parent Alpine scope ('reorderable')
init() {
this.$watch('ctx.available', newval => {
// post updated date to save in DB
fetch('{{ url_for('context_config.update_available') }}', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({'ctx_id': this.ctx.id, 'available': this.ctx.available}),
});
});
},
get status() { return this.ctx.available === '9999-12-31' ? 'Hidden' : this.datePassed(this.ctx.available) ? 'Now' : 'Scheduled'; },
get min_date_str() { const min_date = new Date(); min_date.setDate(min_date.getDate() + 1); return min_date.toISOString().split('T')[0]; },
datePassed(date) {
const now_datetime = new Date(); // automatically UTC
const target_date = new Date(date); // automatically UTC
target_date.setHours(target_date.getHours() - 12); // UTC-12 for anywhere on Earth
return now_datetime >= target_date;
},
formatDate(date) {
// Add 'T00:00' to force parsing as local time so UTC-local shift doesn't change date
return new Date(date + 'T00:00').toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' });
},
chooseScheduled() {
if (this.status === 'Scheduled') {
this.newDate = this.ctx.available;
}
else {
// get today in YYYY-MM-DD format as starting point for date picker
this.newDate = this.min_date_str;
}
this.showModal = true;
},
}));
});
</script>
<table class="table is-hoverable is-narrow" x-data="reorderable">
<thead>
<tr>
<th class="p-0 has-text-centered has-text-grey" title="Reorder">
<svg aria-hidden="true" class="icon is-small" style="vertical-align: bottom;"><use href="#svg_arrow_up_down" /></svg>
</th>
<th>Name</th>
<th class="has-text-centered">Available</th>
<th class="has-text-centered has-text-grey"><small>actions</small></th>
</tr>
</thead>
<tbody>
<template x-for="(ctx, index) in items" x-bind:key="ctx.name">
<tr x-bind:draggable="(drag_index == index)" @dragenter="dragenter(index)" @dragover.prevent @dragend="stop_drag" x-bind:style="(drag_index == index) && {background: '#fc9'}">
<td style="cursor: move; vertical-align: middle; text-align: center;" @mousedown="drag_index=index" @mouseup="stop_drag" title="drag to reorder">
<svg aria-hidden="true" class="icon is-small mt-1"><use href="#svg_grip" /></svg>
</td>
<td style="vertical-align: middle;">
<a class="is-underlined hover-show-icon has-text-link-dark" x-bind:title="`edit '${ctx.name}'`" x-bind:href="'{{ url_for('context_config.context_form') }}/' + ctx.id" x-text="ctx.name"></a>
<svg aria-hidden="true" class="icon is-small has-text-link-dark"><use href="#svg_pencil" /></svg>
</td>
<td style="text-align: center; vertical-align: middle;">
<div x-data="available_dropdown">
<div class="dropdown" x-bind:class="{'is-active': showDropdown}">
<div class="dropdown-trigger">
<button class="button is-small is-rounded" style="white-space: normal;"
x-bind:class="{
'is-success': status === 'Now',
'is-warning': status === 'Scheduled',
'is-danger': status === 'Hidden',
}"
@click="showDropdown = !showDropdown"
@click.outside="showDropdown = false"
aria-haspopup="true" x-bind:aria-controls="`dropdown-menu${index}`">
<span x-text="status === 'Scheduled' ? `Scheduled: ${formatDate(ctx.available)}` : status"></span>
<svg aria-hidden="true" class="icon is-small"><use href="#svg_chevron_down" /></svg>
</button>
</div>
<div class="dropdown-menu" x-bind:id="`dropdown-menu${index}`" role="menu">
<div class="dropdown-content has-text-left">
<a href="#" class="dropdown-item" @click.prevent="ctx.available = '0001-01-01'">Now</a>
<a href="#" class="dropdown-item" @click.prevent="chooseScheduled">Scheduled</a>
<a href="#" class="dropdown-item" @click.prevent="ctx.available = '9999-12-31'">Hidden</a>
</div>
</div>
</div>
<div class="modal" x-bind:class="{ 'is-active': showModal }" @keydown.escape.window="showModal = false">
<div class="modal-background" @click="showModal = false"></div>
<div class="modal-content">
<div class="box has-text-left">
<h3 class="title is-4">Schedule '<span x-text="ctx.name"></span>'</h3>
<p>When scheduled for a certain date, a context becomes available when that date is reached anywhere on Earth (UTC+12).</p>
<form @submit.prevent="ctx.available = newDate; showModal = false">
<div class="field is-grouped" style="justify-content: center;">
<p class="control">
<input type="date" class="input is-large" x-model="newDate" x-bind:min="min_date_str">
</p>
<p class="control">
<button type="submit" class="button is-large is-link">OK</button>
</p>
<p class="control">
<button type="submit" @click.prevent="showModal = false" class="button is-large">Cancel</button>
</p>
</div>
</form>
</div>
</div>
<button type="button" class="modal-close is-large" aria-label="close" @click="showModal = false"></button>
</div>
</div>
</td>
<td class="has-text-centered">
<form method="post">
<span x-data="{
link_URL: '{{ url_for('helper.help_form', class_id=auth['class_id'], ctx_name='__replace__', _external=True) }}'.replace('__replace__', encodeURIComponent(ctx.name)),
copied: false,
showLinkModal: false,
showModal() {
this.copied = false;
this.showLinkModal = true;
},
copy_url() {
navigator.clipboard.writeText(this.link_URL);
this.copied = true;
},
}">
<button x-bind:title="`link to '${ctx.name}'`" class="button is-white is-small has-text-grey" type="button" @click="showModal">
<svg aria-hidden="true" class="icon is-small"><use href="#svg_link" /></svg>
</button>
<div class="modal" x-bind:class="{'is-active': showLinkModal}" @keydown.escape.window="showLinkModal = false;">
<div class="modal-background" @click="showLinkModal = false;"></div>
<div class="modal-content">
<div class="box has-text-left">
<h3 class="title is-4">Link to '<span x-text="ctx.name"></span>'</h3>
<p>This link will take your students directly to the request page with the '<span x-text="ctx.name"></span>' context pre-selected.</p>
<div style="display: flex; flex-wrap: wrap; gap: 1em;">
<div class="control">
<span class="input is-size-5" style="max-width: 100%; overflow-x: auto;" x-text="link_URL"></span>
</div>
<div class="control">
<button type="button" class="button icon-text is-size-5" x-bind:class="copied ? 'is-success' : 'is-link'" @click="copy_url">
<svg aria-hidden="true" class="icon is-right"><use href="#svg_copy" /></svg>
<span x-text="copied ? 'copied' : 'copy'"></span>
</button>
</div>
</div>
<div class="mt-4">
<p>The link will let students access this context even if it is currently hidden.</p>
<p class="has-text-danger-dark">Students must have joined this class before they use the link.</p>
<p class="has-text-danger-dark">If the class connects using LTI, students must log in via LTI before they use the link.</p>
</div>
</div>
</div>
<button type="button" class="modal-close is-large" aria-label="close" @click="showLinkModal = false;"></button>
</div>
</span>
<button x-bind:title="`copy '${ctx.name}'`" class="button is-white is-small has-text-grey" type="submit" x-bind:formaction="'{{ url_for('context_config.copy_context') }}/' + ctx.id">
<svg aria-hidden="true" class="icon is-small"><use href="#svg_copy" /></svg>
</button>
<button x-bind:title="`delete '${ctx.name}'`" class="button is-white is-small has-text-danger" type="submit" x-bind:formaction="'{{ url_for('context_config.delete_context') }}/' + ctx.id" @click="$event => confirm('Are you sure you want to delete \'' + ctx.name + '\'?') || $event.preventDefault()">
<svg aria-hidden="true" class="icon is-small"><use href="#svg_trash" /></svg>
</button>
</form>
</td>
</tr>
</template>
<tr x-show="items.length == 0"><td colspan=4 class="has-text-centered"><i>No contexts defined.</i></td></tr>
</tbody>
</table>
<div colspan=4 class="has-text-centered">
<a class="button is-light is-link is-small" href="{{ url_for('context_config.new_context_form') }}">
<span class="icon">
<svg aria-hidden="true"><use href="#svg_plus" /></svg>
</span>
<span>Create new context</span>
</a>
</div>
</div>

0 comments on commit 7d94c03

Please sign in to comment.