Skip to content

Commit 62985ba

Browse files
committed
add Paste code to submit feature
setting paste_code file entry_point add newline and remove comment update paste_code container css site optimize submit logic use cookie to record user submit mode Update webapp/templates/team/submit_modal.html.twig Co-authored-by: MCJ Vasseur <[email protected]> css new line Update webapp/templates/team/submit_modal.html.twig Co-authored-by: MCJ Vasseur <[email protected]> fix Submit page error fix submit page error 2
1 parent 270975d commit 62985ba

File tree

7 files changed

+260
-44
lines changed

7 files changed

+260
-44
lines changed

etc/db-config.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,14 @@
201201
- category: Display
202202
description: Options related to the DOMjudge user interface.
203203
items:
204+
- name: default_submission_code_mode
205+
type: int
206+
default_value: 0
207+
public: true
208+
description: Select the default submission method for the team
209+
options:
210+
0: Paste
211+
1: Upload
204212
- name: output_display_limit
205213
type: int
206214
default_value: 2000

webapp/public/style_domjudge.css

+22
Original file line numberDiff line numberDiff line change
@@ -699,3 +699,25 @@ blockquote {
699699
padding: 3px;
700700
border-radius: 5px;
701701
}
702+
703+
#submissionTabs.container {
704+
display: grid;
705+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
706+
gap: 10px;
707+
}
708+
709+
.editor-container {
710+
border: 1px solid #ddd;
711+
border-radius: 4px;
712+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
713+
padding: 10px;
714+
margin-top: 10px;
715+
margin-bottom: 10px;
716+
background-color: #fafafa;
717+
max-height: 400px;
718+
overflow: auto;
719+
}
720+
721+
.editor-container:hover {
722+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
723+
}

webapp/src/Controller/Team/SubmissionController.php

+90-25
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use App\Entity\Submission;
1010
use App\Entity\Testcase;
1111
use App\Form\Type\SubmitProblemType;
12+
use App\Form\Type\SubmitProblemPasteType;
1213
use App\Service\ConfigurationService;
1314
use App\Service\DOMJudgeService;
1415
use App\Service\EventLogService;
@@ -60,47 +61,111 @@ public function createAction(Request $request, ?Problem $problem = null): Respon
6061
if ($problem !== null) {
6162
$data['problem'] = $problem;
6263
}
63-
$form = $this->formFactory
64+
$formUpload = $this->formFactory
6465
->createBuilder(SubmitProblemType::class, $data)
6566
->setAction($this->generateUrl('team_submit'))
6667
->getForm();
6768

68-
$form->handleRequest($request);
69+
$formPaste = $this->formFactory
70+
->createBuilder(SubmitProblemPasteType::class, $data)
71+
->setAction($this->generateUrl('team_submit'))
72+
->getForm();
6973

70-
if ($form->isSubmitted() && $form->isValid()) {
74+
$formUpload->handleRequest($request);
75+
$formPaste->handleRequest($request);
76+
if ($formUpload->isSubmitted() || $formPaste->isSubmitted()) {
7177
if ($contest === null) {
7278
$this->addFlash('danger', 'No active contest');
7379
} elseif (!$this->dj->checkrole('jury') && !$contest->getFreezeData()->started()) {
7480
$this->addFlash('danger', 'Contest has not yet started');
7581
} else {
76-
/** @var Problem $problem */
77-
$problem = $form->get('problem')->getData();
78-
/** @var Language $language */
79-
$language = $form->get('language')->getData();
80-
/** @var UploadedFile[] $files */
81-
$files = $form->get('code')->getData();
82-
if (!is_array($files)) {
83-
$files = [$files];
82+
$problem = null;
83+
$language = null;
84+
$files = [];
85+
$entryPoint = null;
86+
$message = '';
87+
88+
if ($formUpload->isSubmitted() && $formUpload->isValid()) {
89+
$problem = $formUpload->get('problem')->getData();
90+
$language = $formUpload->get('language')->getData();
91+
$files = $formUpload->get('code')->getData();
92+
if (!is_array($files)) {
93+
$files = [$files];
94+
}
95+
$entryPoint = $formUpload->get('entry_point')->getData() ?: null;
96+
} elseif ($formPaste->isSubmitted() && $formPaste->isValid()) {
97+
$problem = $formPaste->get('problem')->getData();
98+
$language = $formPaste->get('language')->getData();
99+
$codeContent = $formPaste->get('code_content')->getData();
100+
101+
if ($codeContent == null || empty(trim($codeContent))) {
102+
$this->addFlash('danger', 'No code content provided.');
103+
return $this->redirectToRoute('team_index');
104+
}
105+
106+
$tempDir = sys_get_temp_dir();
107+
$tempFileName = sprintf(
108+
'submission_%s_%s_%s.%s',
109+
$user->getUsername(),
110+
$problem->getName(),
111+
date('Y-m-d_H-i-s'),
112+
$language->getExtensions()[0]
113+
);
114+
$tempFileName = preg_replace('/[^a-zA-Z0-9_.-]/', '_', $tempFileName);
115+
$tempFilePath = $tempDir . DIRECTORY_SEPARATOR . $tempFileName;
116+
file_put_contents($tempFilePath, $codeContent);
117+
118+
$uploadedFile = new UploadedFile(
119+
$tempFilePath,
120+
$tempFileName,
121+
'application/octet-stream',
122+
null,
123+
true
124+
);
125+
126+
$files = [$uploadedFile];
127+
$entryPoint = $tempFileName;
84128
}
85-
$entryPoint = $form->get('entry_point')->getData() ?: null;
86-
$submission = $this->submissionService->submitSolution(
87-
$team, $this->dj->getUser(), $problem->getProbid(), $contest, $language, $files, 'team page', null,
88-
null, $entryPoint, null, null, $message
89-
);
90-
91-
if ($submission) {
92-
$this->addFlash(
93-
'success',
94-
'Submission done! Watch for the verdict in the list below.'
129+
130+
if ($problem && $language && !empty($files)) {
131+
$submission = $this->submissionService->submitSolution(
132+
$team,
133+
$this->dj->getUser(),
134+
$problem->getProbid(),
135+
$contest,
136+
$language,
137+
$files,
138+
'team page',
139+
null,
140+
null,
141+
$entryPoint,
142+
null,
143+
null,
144+
$message
95145
);
96-
} else {
97-
$this->addFlash('danger', $message);
146+
147+
if ($submission) {
148+
$this->addFlash('success', 'Submission done! Watch for the verdict in the list below.');
149+
} else {
150+
$this->addFlash('danger', $message);
151+
}
152+
153+
return $this->redirectToRoute('team_index');
98154
}
99-
return $this->redirectToRoute('team_index');
100155
}
101156
}
157+
158+
$active_tab = (bool) $this->config->get('default_submission_code_mode') == 0 ? 'paste' : 'upload';
159+
if ($this->dj->getCookie('active_tab') != null) {
160+
$active_tab = $this->dj->getCookie('active_tab');
161+
}
102162

103-
$data = ['form' => $form->createView(), 'problem' => $problem];
163+
$data = [
164+
'formupload' => $formUpload->createView(),
165+
'formpaste' => $formPaste->createView(),
166+
'active_tab' => $active_tab,
167+
'problem' => $problem,
168+
];
104169
$data['validFilenameRegex'] = SubmissionService::FILENAME_REGEX;
105170

106171
if ($request->isXmlHttpRequest()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace App\Form\Type;
4+
5+
use App\Entity\Language;
6+
use App\Entity\Problem;
7+
use App\Service\ConfigurationService;
8+
use App\Service\DOMJudgeService;
9+
use Doctrine\ORM\EntityManagerInterface;
10+
use Doctrine\ORM\EntityRepository;
11+
use Doctrine\ORM\Query\Expr\Join;
12+
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
13+
use Symfony\Component\Form\AbstractType;
14+
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
15+
use Symfony\Component\Form\Extension\Core\Type\TextType;
16+
use Symfony\Component\Form\Form;
17+
use Symfony\Component\Form\FormBuilderInterface;
18+
use Symfony\Component\Form\FormEvent;
19+
use Symfony\Component\Form\FormEvents;
20+
use Symfony\Component\Validator\Constraints\Callback;
21+
use Symfony\Component\Validator\Context\ExecutionContextInterface;
22+
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
23+
24+
class SubmitProblemPasteType extends AbstractType
25+
{
26+
public function __construct(
27+
protected readonly DOMJudgeService $dj,
28+
protected readonly ConfigurationService $config,
29+
protected readonly EntityManagerInterface $em
30+
) {
31+
}
32+
33+
public function buildForm(FormBuilderInterface $builder, array $options): void
34+
{
35+
$user = $this->dj->getUser();
36+
$contest = $this->dj->getCurrentContest($user->getTeam()->getTeamid());
37+
38+
$builder->add('code_content', HiddenType::class, [
39+
'required' => true,
40+
]);
41+
$problemConfig = [
42+
'class' => Problem::class,
43+
'query_builder' => fn(EntityRepository $er) => $er->createQueryBuilder('p')
44+
->join('p.contest_problems', 'cp', Join::WITH, 'cp.contest = :contest')
45+
->select('p', 'cp')
46+
->andWhere('cp.allowSubmit = 1')
47+
->setParameter('contest', $contest)
48+
->addOrderBy('cp.shortname'),
49+
'choice_label' => fn(Problem $problem) => sprintf(
50+
'%s - %s',
51+
$problem->getContestProblems()->first()->getShortName(),
52+
$problem->getName()
53+
),
54+
'placeholder' => 'Select a problem',
55+
];
56+
$builder->add('problem', EntityType::class, $problemConfig);
57+
58+
$builder->add('language', EntityType::class, [
59+
'class' => Language::class,
60+
'query_builder' => fn(EntityRepository $er) => $er
61+
->createQueryBuilder('l')
62+
->andWhere('l.allowSubmit = 1'),
63+
'choice_label' => 'name',
64+
'placeholder' => 'Select a language',
65+
]);
66+
67+
$builder->add('entry_point', TextType::class, [
68+
'label' => 'Entry point',
69+
'required' => false,
70+
'help' => 'The entry point for your code.',
71+
'row_attr' => ['data-entry-point' => '']
72+
]);
73+
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($problemConfig) {
74+
$data = $event->getData();
75+
if (isset($data['problem'])) {
76+
$problemConfig += ['row_attr' => ['class' => 'd-none']];
77+
$event->getForm()->add('problem', EntityType::class, $problemConfig);
78+
}
79+
});
80+
}
81+
}

webapp/templates/base.html.twig

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
<script src="{{ asset("js/bootstrap.bundle.min.js") }}"></script>
1515

1616
<script src="{{ asset("js/domjudge.js") }}"></script>
17+
<script src="{{ asset('js/ace/ace.js') }}"></script>
18+
1719
{% for file in customAssetFiles('js') %}
1820
<script src="{{ asset('js/custom/' ~ file) }}"></script>
1921
{% endfor %}

webapp/templates/team/submit.html.twig

+6-6
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@
1414
</div>
1515
{% else %}
1616

17-
{{ form_start(form) }}
18-
{{ form_row(form.code) }}
19-
{{ form_row(form.problem) }}
20-
{{ form_row(form.language) }}
21-
{{ form_row(form.entry_point) }}
17+
{{ form_start(formupload) }}
18+
{{ form_row(formupload.code) }}
19+
{{ form_row(formupload.problem) }}
20+
{{ form_row(formupload.language) }}
21+
{{ form_row(formupload.entry_point) }}
2222
<div class="mb-3">
2323
<button type="submit" class="btn-success btn"><i class="fas fa-cloud-upload-alt"></i> Submit
2424
</button>
2525
</div>
26-
{{ form_end(form) }}
26+
{{ form_end(formupload) }}
2727

2828
{% endif %}
2929
{% endif %}

webapp/templates/team/submit_modal.html.twig

+51-13
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,59 @@
1919
{% include 'partials/alert.html.twig' with {'type': 'danger', 'message': 'Submissions (temporarily) disabled.'} %}
2020
</div>
2121
{% else %}
22-
{{ form_start(form) }}
2322
<div class="modal-body">
24-
{{ form_row(form.code) }}
25-
<div class="alert d-none" id="files_selected"></div>
26-
{{ form_row(form.problem) }}
27-
{{ form_row(form.language) }}
28-
{{ form_row(form.entry_point) }}
29-
</div>
30-
<div class="modal-footer">
31-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
32-
<button type="submit" class="btn-success btn">
33-
<i class="fas fa-cloud-upload-alt"></i> Submit
34-
</button>
23+
<ul class="nav nav-tabs container text-center" id="submissionTabs" role="tablist" style="width: 100%">
24+
<li class="nav-item" role="presentation" >
25+
<a class="nav-link {% if active_tab == 'upload' %}active{% endif %}" id="upload-tab" data-bs-toggle="tab" href="#upload" role="tab" aria-controls="upload" aria-selected="{% if active_tab == 'upload' %}true{% else %}false{% endif %}" onclick="setCookie('active_tab', 'upload')" >Upload File</a>
26+
</li>
27+
<li class="nav-item text-center" role="presentation">
28+
<a class="nav-link {% if active_tab == 'paste' %}active{% endif %}" id="paste-tab" data-bs-toggle="tab" href="#paste" role="tab" aria-controls="paste" aria-selected="{% if active_tab == 'paste' %}true{% else %}false{% endif %}" onclick="setCookie('active_tab', 'paste')">Paste Code</a>
29+
</li>
30+
</ul>
31+
<div class="tab-content" id="submissionTabsContent" style="margin-top: 20px;">
32+
<div class="tab-pane fade {% if active_tab == 'upload' %}show active{% endif %}" id="upload" role="tabpanel" aria-labelledby="upload-tab">
33+
{{ form_start(formupload) }}
34+
{{ form_row(formupload.code) }}
35+
<div class="alert d-none" id="files_selected"></div>
36+
{{ form_row(formupload.problem) }}
37+
{{ form_row(formupload.language) }}
38+
{{ form_row(formupload.entry_point) }}
39+
<div class="modal-footer">
40+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
41+
<button type="submit" class="btn btn-success">
42+
<i class="fas fa-cloud-upload-alt"></i> Submit Upload
43+
</button>
44+
</div>
45+
{{ form_end(formupload) }}
46+
</div>
47+
48+
<div class="tab-pane fade {% if active_tab == 'paste' %}show active{% endif %}" id="paste" role="tabpanel" aria-labelledby="paste-tab">
49+
{{ form_start(formpaste) }}
50+
{{ form_widget(formpaste.code_content) }}
51+
<label for="codeInput">Paste your code here:</label>
52+
<div class="editor-container">
53+
{{ "" | codeEditor(
54+
"_team_submission_code",
55+
"c_cpp",
56+
true,
57+
formpaste.code_content.vars.id,
58+
null,
59+
formpaste.language.vars.value
60+
) }}
61+
</div>
62+
{{ form_row(formpaste.problem) }}
63+
{{ form_row(formpaste.language) }}
64+
{{ form_row(formpaste.entry_point) }}
65+
<div class="modal-footer">
66+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
67+
<button type="submit" class="btn btn-primary">
68+
<i class="fas fa-paste"></i> Submit Paste
69+
</button>
70+
</div>
71+
{{ form_end(formpaste) }}
72+
</div>
73+
</div>
3574
</div>
36-
{{ form_end(form) }}
3775
{% endif %}
3876
</div>
3977
</div>

0 commit comments

Comments
 (0)