Skip to content

Commit f5677d1

Browse files
committed
add web-challenges support
1 parent 76f3da2 commit f5677d1

File tree

25 files changed

+526
-5
lines changed

25 files changed

+526
-5
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import ApplicationAdapter from './application'
2+
3+
export default ApplicationAdapter.extend({
4+
urlForCreateRecord(modelName, snapshot) {
5+
const contest_id = snapshot.adapterOptions.contest_id
6+
if (contest_id) {
7+
return this._super(...arguments) + `?contest_id=${contest_id}`
8+
}
9+
return this._super(...arguments)
10+
}
11+
})

app/models/content.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default Model.extend({
1111
problem: DS.belongsTo('problem'),
1212
quiz: DS.belongsTo('quiz'),
1313
project: DS.belongsTo('project'),
14+
webChallenge: DS.belongsTo('web-challenge'),
1415
contest: DS.hasMany('contest'),
1516
submissions:DS.hasMany('submission'),
1617
bookmarkedContent: DS.belongsTo('bookmarked-content'),
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import DS from 'ember-data';
2+
3+
export default DS.Model.extend({
4+
source: DS.attr(),
5+
score: DS.attr(),
6+
isTopSubmission: DS.attr(),
7+
content: DS.belongsTo('content'),
8+
contestAttempt: DS.belongsTo('contest-attempt')
9+
});

app/models/web-challenge.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import DS from 'ember-data';
2+
3+
export default DS.Model.extend({
4+
title: DS.attr(),
5+
description: DS.attr()
6+
})
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import Component from '@ember/component';
2+
import { action } from '@ember/object';
3+
import { timeout } from "ember-concurrency";
4+
import { dropTask } from 'ember-concurrency-decorators';
5+
import { inject as service } from '@ember/service';
6+
7+
export default class FullScreenWebChallenge extends Component{
8+
@service store
9+
@service submission
10+
11+
currentTab= 'problem'
12+
showSubmitted = false
13+
triggerFetchSubmissions = true
14+
15+
didReceiveAttrs() {
16+
this._super(...arguments)
17+
18+
this.submission.initialize(this.contest, this.content)
19+
}
20+
21+
@dropTask newSubmissionTask = function* () {
22+
yield timeout(1000)
23+
24+
const submission = this.store.createRecord('web-challenge-submission', {
25+
contestAttempt: yield this.contest.get('currentAttempt'),
26+
content: this.content,
27+
source: this.source
28+
})
29+
30+
yield submission.save({adapterOptions: { contest_id: this.contest.id}})
31+
this.set('showSubmitted', true)
32+
this.set('triggerFetchSubmissions', true)
33+
}
34+
35+
@action
36+
resetSource() {
37+
this.set('source', { html: '', css: '', js: ''})
38+
}
39+
40+
@action
41+
setSource(source) {
42+
this.set('source', source)
43+
window.scrollTo(0,document.body.scrollHeight);
44+
}
45+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<div class="col-md-11 col-12 offset-md-1 px-xl-5 px-4 position-relative h-100 overflow-y-auto">
2+
<div class="row no-gutters pt-4">
3+
<div class="col-6 word-wrap">
4+
<div class="font-mds">Web Challenge</div>
5+
<h4>{{webChallenge.title}}</h4>
6+
</div>
7+
<div class="col-6">
8+
<ul class="divided-list t-align-r mt-0 pull-right">
9+
<li class="px-3">
10+
<h4 class="orange">{{content.difficultyString}}</h4>
11+
<div class="list-data font-mds">Dificulty</div>
12+
</li>
13+
<li class="px-3">
14+
<h4 class="orange">100</h4>
15+
<div class="list-data font-mds">Max Points</div>
16+
</li>
17+
</ul>
18+
</div>
19+
</div>
20+
<div class="divider-h mt-2"></div>
21+
{{!-- <div class="row align-items-center no-gutters my-4">
22+
<div class="col-6">
23+
<span class="mr-2 font-mds extra-bold">Status:</span>
24+
<span
25+
class="orange font-mds extra-bold">{{or content.topSubmission.resultParams.message 'Not Attempted'}}</span>
26+
</div>
27+
<div class="col-6">
28+
<div class="row no-gutters justify-content-end">
29+
<div class="col-xl-6 col-5">
30+
<progress value="{{or content.topSubmission.score 0}}" max="100" class="d-inline-block v-align-m"></progress>
31+
</div>
32+
<div>
33+
<h5 class="d-inline-block pl-3">{{or content.topSubmission.score 0}}/100 Pts</h5>
34+
</div>
35+
</div>
36+
</div>
37+
</div> --}}
38+
39+
<div class="row no-gutters mb-5">
40+
<div class="tab-nav-underline w-100 justify-content-start bg-white position-sticky">
41+
<div class="tab {{if (eq currentTab 'problem') 'active'}} pr-5 pl-0" {{action (mut currentTab) 'problem'}}>
42+
Problem
43+
</div>
44+
<div class="tab {{if (eq currentTab 'submissions') 'active'}} px-5" {{action (mut currentTab) 'submissions'}}>
45+
Submissions
46+
</div>
47+
</div>
48+
<div class="w-100">
49+
{{#liquid-if (eq currentTab 'problem')}}
50+
<WebChallenge::WebChallengeExplanation @webChallenge={{webChallenge}} />
51+
{{else if (eq currentTab 'submissions')}}
52+
<WebChallenge::WebChallengeSubmissions
53+
@contest={{contest}}
54+
@content={{content}}
55+
@setSource={{action 'setSource'}}
56+
@triggerFetchSubmissions={{triggerFetchSubmissions}}
57+
/>
58+
{{/liquid-if}}
59+
</div>
60+
</div>
61+
62+
<div class="border-card bg-dark-grey my-4">
63+
<div class="d-flex justify-content-between align-items-center white">
64+
<div>
65+
<i class="far fa-question-circle"></i> Your latest submission will be judged for evaluation
66+
</div>
67+
<div>
68+
<WPulse @class="display-inline" @triggered={{showSubmitted}}>
69+
<span class="grey">Submitted</span>
70+
</WPulse>
71+
<button class="button-dashed button-orange mx-3" {{action 'resetSource'}}>Reset</button>
72+
<button class="button-solid button-orange" {{action (perform newSubmissionTask)}} disabled={{newSubmissionTask.isRunning}}>New Submit</button>
73+
</div>
74+
</div>
75+
</div>
76+
77+
<div class="border-card bg-dark-grey">
78+
<WebChallenge::WebChallengeCodeEditor @source={{source}}/>
79+
</div>
80+
</div>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import Component from '@ember/component';
2+
import { action } from '@ember/object';
3+
import { timeout } from "ember-concurrency";
4+
import { restartableTask } from 'ember-concurrency-decorators';
5+
import { later } from '@ember/runloop';
6+
7+
export default class WebChallengeWebChallengeCodeEditorComponent extends Component {
8+
HTMLSource = ''
9+
CSSSource = ''
10+
JSSource = ''
11+
autoRunEnabled = true
12+
13+
didReceiveAttrs() {
14+
this._super(...arguments)
15+
16+
this.set('HTMLSource', atob(this.source.html) || '')
17+
this.set('CSSSource', atob(this.source.css) || '')
18+
this.set('JSSource', atob(this.source.js) || '')
19+
20+
}
21+
22+
didUpdateAttrs() {
23+
this._super(...arguments)
24+
if(this.autoRunEnabled) {
25+
later(() => this.initIframeTask.perform())
26+
}
27+
}
28+
29+
@restartableTask
30+
initIframeTask = function *() {
31+
yield timeout(1800)
32+
33+
this.$('#web-challenge-iframe').remove()
34+
35+
const outputIframe = document.createElement('iframe')
36+
outputIframe.setAttribute('id', 'web-challenge-iframe')
37+
38+
this.$('#web-challenge-output').append(this.$(outputIframe))
39+
40+
this.$('#web-challenge-iframe').addClass('w-100 h-100')
41+
this.$('#web-challenge-iframe').attr('frameborder', '0')
42+
this.$('#web-challenge-iframe').contents().find('head').append(`<style>${this.CSSSource}</style>`)
43+
this.$('#web-challenge-iframe').contents().find('body').append(`${this.HTMLSource}`)
44+
this.$('#web-challenge-iframe').contents().find('body').append(`<script>try {${this.JSSource}} catch (err) {throw err;}</script>`)
45+
}
46+
47+
@restartableTask
48+
handleSourceChangeTask = function *(language, source) {
49+
switch(language) {
50+
case 'html': this.set('HTMLSource', source); break;
51+
case 'css': this.set('CSSSource', source); break;
52+
case 'js': this.set('JSSource', source); break;
53+
}
54+
55+
this.set('source', { html: btoa(this.HTMLSource), css: btoa(this.CSSSource), js: btoa(this.JSSource) })
56+
}
57+
58+
@action
59+
manualRun() {
60+
this.initIframeTask.perform()
61+
}
62+
63+
@action
64+
toggleAutoRunEnabled() {
65+
this.toggleProperty('autoRunEnabled')
66+
67+
if(this.autoRunEnabled) {
68+
this.handleSourceChangeTask.perform()
69+
}
70+
}
71+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<div class="row web-challenge-code-editors-container mb-5">
2+
<div class="col-4">
3+
<h6 class="orange mb-2">HTML</h6>
4+
<MonacoEditor
5+
class="editor monaco-editor"
6+
@language='html'
7+
@code={{HTMLSource}}
8+
@onChange={{action (perform handleSourceChangeTask) 'html'}}
9+
{{!-- @onReady={{action "onEditorReady"}} --}}
10+
/>
11+
</div>
12+
<div class="col-4">
13+
<h6 class="orange mb-2">CSS</h6>
14+
<MonacoEditor
15+
class="editor monaco-editor"
16+
@language='css'
17+
@code={{CSSSource}}
18+
@onChange={{action (perform handleSourceChangeTask) 'css'}}
19+
{{!-- @onReady={{action "onEditorReady"}} --}}
20+
/>
21+
</div>
22+
<div class="col-4">
23+
<h6 class="orange mb-2">JS</h6>
24+
<MonacoEditor
25+
class="editor monaco-editor"
26+
@language='javascript'
27+
@code={{JSSource}}
28+
@onChange={{action (perform handleSourceChangeTask) 'js'}}
29+
{{!-- @onReady={{action "onEditorReady"}} --}}
30+
/>
31+
</div>
32+
</div>
33+
34+
<div class="d-flex justify-content-between align-items-center mb-3">
35+
<h6 class="orange">
36+
OUTPUT
37+
{{#if (or initIframeTask.isRunning handleSourceChangeTask.isRunning)}}
38+
<span class="dot-loader"></span>
39+
{{/if}}
40+
</h6>
41+
42+
<div class="d-flex align-items-center">
43+
<label class="input-checkbox gradient-tick orange mr-2">
44+
<input type="checkbox" checked={{autoRunEnabled}} onClick={{action "toggleAutoRunEnabled"}}>
45+
Auto-run
46+
<span></span>
47+
</label>
48+
49+
<button class="button-dashed button-orange"
50+
disabled={{handleSourceChangeTask.isRunning}}
51+
{{action 'manualRun'}}>
52+
Run
53+
</button>
54+
</div>
55+
</div>
56+
57+
<div id="web-challenge-output" class="w-100 border-grey br-5">
58+
<iframe id="web-challenge-iframe" class="w-100" frameborder="0"></iframe>
59+
</div>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import Component from '@ember/component';
2+
3+
export default Component.extend({
4+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<p>
2+
{{markdown-to-html webChallenge.description}}
3+
</p>

0 commit comments

Comments
 (0)