codecheck

Форк
0
464 строки · 12.1 Кб
1
<script setup lang="ts">
2
import { Codemirror, install } from "vue-codemirror";
3
import { darcula, darculaInit } from "@uiw/codemirror-theme-darcula";
4
import "@/assets/css/markdown.scss";
5
import hljs from "highlight.js";
6
import "highlight.js/styles/base16/github.css";
7
import Markdown from "markdown-it";
8
import {
9
  lineNumbers,
10
  highlightActiveLineGutter,
11
  highlightSpecialChars,
12
  drawSelection,
13
  dropCursor,
14
  rectangularSelection,
15
  crosshairCursor,
16
  highlightActiveLine,
17
  keymap,
18
  EditorView,
19
} from "@codemirror/view";
20
import { EditorState } from "@codemirror/state";
21
import {
22
  foldGutter,
23
  indentOnInput,
24
  syntaxHighlighting,
25
  defaultHighlightStyle,
26
  bracketMatching,
27
  foldKeymap,
28
} from "@codemirror/language";
29
import { history, defaultKeymap, historyKeymap } from "@codemirror/commands";
30
import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
31
import {
32
  closeBrackets,
33
  autocompletion,
34
  closeBracketsKeymap,
35
  completionKeymap,
36
} from "@codemirror/autocomplete";
37
import { lintKeymap } from "@codemirror/lint";
38

39
useHead({
40
  title: "Добавление задания",
41
});
42

43
definePageMeta({
44
  middleware: "admin",
45
});
46

47
useState("leftTabs").value = "Инструкция";
48
useState("rightTabs").value = "Код";
49

50
const store = useAdminChellengeVariantsStore();
51
const { currentLang, langs, challenges } = storeToRefs(store);
52
const { currentChallenge, initChallenges } = store;
53

54
await initChallenges();
55

56
const selecting = (item: any) => {
57
  currentLang.value = item.name;
58
};
59

60
// const code = ref(`def hello_world():`);
61
const codeExtensions = computed(() => {
62
  let result = [
63
    darculaInit({}),
64
    lineNumbers(),
65
    highlightActiveLineGutter(),
66
    highlightSpecialChars(),
67
    history(),
68
    foldGutter({}),
69
    drawSelection(),
70
    dropCursor(),
71
    indentOnInput(),
72
    EditorState.allowMultipleSelections.of(true),
73
    syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
74
    bracketMatching(),
75
    closeBrackets(),
76
    autocompletion(),
77
    rectangularSelection(),
78
    crosshairCursor(),
79
    highlightActiveLine(),
80
    highlightSelectionMatches(),
81
    keymap.of([
82
      ...closeBracketsKeymap,
83
      ...defaultKeymap,
84
      ...searchKeymap,
85
      ...historyKeymap,
86
      ...foldKeymap,
87
      ...completionKeymap,
88
      ...lintKeymap,
89
    ]),
90
  ];
91
  result.push(langExtension(currentLang.value));
92
  return result;
93
});
94

95
const output = ref(
96
  `Это - вывод. Нажмите кнопку "Проверить код", и здесь появятся резултаты тестов. Сами тесты находятся во вкладке "Тесты`
97
);
98

99
const outputExtensions = [
100
  EditorView.lineWrapping,
101

102
  darculaInit({
103
    settings: {
104
      // fontFamily: "Consolas",
105
    },
106
  }),
107
];
108

109
// const tests = ref(``);
110

111
const testsExtensions = computed(() => {
112
  let result = [
113
    darculaInit({}),
114
    lineNumbers(),
115
    highlightActiveLineGutter(),
116
    highlightSpecialChars(),
117
    history(),
118
    foldGutter({}),
119
    drawSelection(),
120
    dropCursor(),
121
    indentOnInput(),
122
    EditorState.allowMultipleSelections.of(true),
123
    syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
124
    bracketMatching(),
125
    closeBrackets(),
126
    autocompletion(),
127
    rectangularSelection(),
128
    crosshairCursor(),
129
    highlightActiveLine(),
130
    highlightSelectionMatches(),
131
    keymap.of([
132
      ...closeBracketsKeymap,
133
      ...defaultKeymap,
134
      ...searchKeymap,
135
      ...historyKeymap,
136
      ...foldKeymap,
137
      ...completionKeymap,
138
      ...lintKeymap,
139
    ]),
140
  ];
141
  result.push(langExtension(currentLang.value));
142
  return result;
143
});
144

145
// Codemirror EditorView instance ref
146
const view = shallowRef();
147
const handleReady = (payload: any) => {
148
  view.value = payload.view;
149
};
150

151
const renderedMd = ref();
152
const parser = new Markdown({
153
  // html: true,
154

155
  linkify: true,
156
  typographer: true,
157
  breaks: true,
158
  highlight: function (str, lang) {
159
    if (lang && hljs.getLanguage(lang)) {
160
      try {
161
        return hljs.highlight(str, { language: lang }).value;
162
      } catch (__) {}
163
    }
164

165
    return ""; // use external default escaping
166
  },
167
});
168

169
watch(
170
  () => {
171
    return currentChallenge().mdInstructrion;
172
  },
173
  () => {
174
    renderedMd.value = parser.render(currentChallenge().mdInstructrion);
175
  }
176
);
177

178
currentChallenge().test = `import unittest
179

180
from solution_code import *
181

182
class TestMethods(unittest.TestCase):
183

184
    def test_function(self):
185
        self.assertEqual(addition(1), 2)
186

187

188
if __name__ == "__main__":
189
    unittest.main()`;
190

191
currentChallenge().code = `def addition(n):
192
 `;
193

194
const btnLoading = ref(true);
195

196
const { start, finish } = useLoadingIndicator();
197

198
const check = async () => {
199
  start();
200
  btnLoading.value = false;
201
  const resCheck: any = await $fetch("/api/check", {
202
    method: "POST",
203
    body: {
204
      lang: currentLang.value,
205
      code: currentChallenge().code,
206
      test: currentChallenge().test,
207
    },
208
  });
209
  useState("rightTabs").value = "Вывод";
210
  finish();
211
  output.value = new TextDecoder().decode(
212
    Uint8Array.from(
213
      atob(resCheck.stdout)
214
        .split("")
215
        .map((x) => x.charCodeAt(0))
216
    )
217
  );
218
  btnLoading.value = true;
219
};
220

221
const challengeName = ref("");
222
const challengeDescription = ref("");
223

224
const { data: tags } = useFetch("/api/tags");
225
const selectedTags = ref();
226

227
const addChallenge = async () => {
228
  const data = await $fetch("/api/challenges", {
229
    method: "POST",
230
    body: {
231
      name: challengeName.value,
232
      description: challengeDescription.value,
233
      tags: selectedTags.value,
234
      variants: Array.from(challenges.value, ([name, value]) => ({
235
        name,
236
        value,
237
      })),
238
    },
239
  });
240
  if (!data) return;
241
  navigateTo(`/challenges/${data.id}`);
242
};
243

244
const parseinp = ref("https://api.codechick.io/tasks/");
245

246
const handleParse = async () => {
247
  const data: any = await $fetch("/api/parse", {
248
    method: "POST",
249
    body: {
250
      url: parseinp.value,
251
    },
252
  });
253
  if (!data.title) return;
254
  challengeName.value = data.title;
255
  challengeDescription.value = data.description;
256
  data.subtasks.forEach((element: any) => {
257
    if (element.language.title === currentLang.value) {
258
      currentChallenge().mdInstructrion = element.description;
259
      currentChallenge().code = element.start_code;
260
      currentChallenge().test = element.test_code;
261
    }
262
  });
263
};
264
</script>
265

266
<template>
267
  <div class="challenge">
268
    <div class="challenge__container">
269
      <div class="challenge__body">
270
        <div class="challenge__left left">
271
          <TabsWrapper name="leftTabs">
272
            <Tab class="challenge__instruction" title="Инструкция">
273
              <TextInput
274
                v-model="challengeName"
275
                name="challengeName"
276
                id="challengeName"
277
                placeholder="Название задания"
278
              ></TextInput>
279
              <MultiSelect
280
                empty-message="Теги не найдены"
281
                selected-items-label="{0} тэгов выбрано"
282
                v-model="selectedTags"
283
                :options="tags"
284
                optionLabel="name"
285
                placeholder="Выбрать тэги"
286
                :maxSelectedLabels="5"
287
                display="chip"
288
              />
289
              <AppTextarea
290
                placeholder="Описание задания"
291
                v-model="challengeDescription"
292
                name="challengeDescription"
293
                id="challengeDescription"
294
              ></AppTextarea>
295
              <client-only>
296
                <AppTextarea
297
                  id="md"
298
                  v-model="currentChallenge().mdInstructrion"
299
                  placeholder="Инструкция по выполнению задания (Markdown)"
300
                  name="md"
301
                ></AppTextarea>
302
                <div v-html="renderedMd" class="markdown-body"></div>
303
              </client-only>
304
            </Tab>
305
          </TabsWrapper>
306
        </div>
307
        <div class="challenge__right right">
308
          <TabsWrapper name="rightTabs">
309
            <template v-slot:top>
310
              <LanguageSelect
311
                @selectedLanguage="selecting"
312
                class="challenge__languages"
313
                :languages="langs"
314
              ></LanguageSelect>
315
            </template>
316

317
            <template v-slot:default>
318
              <Tab title="Код"
319
                ><codemirror
320
                  v-model="currentChallenge().code"
321
                  placeholder="Здесь пишем код"
322
                  :style="{
323
                    height: '300px',
324
                    'font-size': '1rem',
325
                    'border-radius': 'var(--border-radius)',
326
                  }"
327
                  :autofocus="true"
328
                  :indent-with-tab="true"
329
                  :tab-size="2"
330
                  :extensions="codeExtensions"
331
                />
332
              </Tab>
333
              <Tab title="Вывод">
334
                <codemirror
335
                  v-model="output"
336
                  :style="{
337
                    height: '300px',
338
                    'font-size': '14px',
339
                    'border-radius': 'var(--border-radius)',
340
                  }"
341
                  :autofocus="true"
342
                  :indent-with-tab="true"
343
                  :tab-size="2"
344
                  :extensions="outputExtensions"
345
                  disabled
346
                />
347
              </Tab>
348
              <Tab title="Тесты">
349
                <codemirror
350
                  v-model="currentChallenge().test"
351
                  placeholder="Здесь пишем тесты"
352
                  :style="{
353
                    height: '300px',
354
                    'font-size': '1rem',
355
                    'border-radius': 'var(--border-radius)',
356
                  }"
357
                  :autofocus="true"
358
                  :indent-with-tab="true"
359
                  :tab-size="2"
360
                  :extensions="testsExtensions"
361
                />
362
              </Tab>
363
            </template>
364
            <template v-slot:bottom>
365
              <div class="right__btns">
366
                <FormButton
367
                  @click="check()"
368
                  background="var(--color-warning)"
369
                  color="var(--color-text-primary)"
370
                  class="right__btn"
371
                  :loading="btnLoading"
372
                  >Проверить код</FormButton
373
                >
374
                <FormButton
375
                  @click="addChallenge"
376
                  background="var(--color-success)"
377
                  color="var(--color-text-priamary)"
378
                  class="right__btn"
379
                  >Добавить задачу</FormButton
380
                >
381
              </div>
382
              <div class="parse">
383
                <TextInput
384
                  placeholder="Ссылка на задачу, которую надо спарсить"
385
                  name="parseinp"
386
                  v-model="parseinp"
387
                  id="parseinp"
388
                ></TextInput>
389
                <FormButton
390
                  @click="handleParse"
391
                  background="var(--color-primary)"
392
                  color="var(--color-text-primary)"
393
                  class="right__btn"
394
                  >Спарсить задачу</FormButton
395
                >
396
              </div>
397
            </template>
398
          </TabsWrapper>
399
        </div>
400
      </div>
401
      <!-- {{ $route.params.id }} -->
402
    </div>
403
  </div>
404
</template>
405

406
<style lang="scss">
407
:deep(.cm-editor) {
408
  border-radius: 5px;
409
}
410

411
.parse {
412
  display: flex;
413
  flex-direction: column;
414
  gap: 0.5rem;
415
  align-items: flex-end;
416
}
417

418
.challenge {
419
  margin-top: 2rem;
420
  &__instruction {
421
    display: flex;
422
    flex-direction: column;
423
    gap: 1rem;
424
  }
425
  &__languages {
426
    display: flex;
427
    justify-content: flex-end;
428
  }
429
  &__container {
430
    margin: 0 auto;
431
    max-width: var(--width-container);
432
    padding-right: 1rem;
433
    padding-left: 1rem;
434
  }
435
  &__body {
436
    display: grid;
437
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
438
    @media (max-width: 768px) {
439
      grid-template-columns: 1fr;
440
    }
441
    gap: 2rem;
442
  }
443
}
444

445
.left {
446
  &__body {
447
    margin-top: 2rem;
448
  }
449
}
450
.right {
451
  &__body {
452
    margin-top: 2rem;
453
  }
454
  &__btns {
455
    display: flex;
456
    gap: 1rem;
457
    justify-content: flex-end;
458
    margin-top: 1rem;
459
  }
460
  &__btn {
461
    max-width: 200px;
462
  }
463
}
464
</style>
465

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.