new questions ui

master
gman 9 months ago
parent 9a54c78f0b
commit 53c1d0e0e1

@ -0,0 +1,236 @@
<multiple-choice
data-correct-label="Верно 😸"
data-incorrect-label="Надо поразмыслить 🐱"
>
<form onsubmit="event.preventDefault()">
<ol class="opt-list not-content"><slot /></ol>
<div class="footer">
<button class="submit" type="submit" disabled> Проверить </button>
<div class="answer sr-only" role="alert"></div>
</div>
</form>
</multiple-choice>
<style>
multiple-choice {
display: block;
padding-bottom: 1rem;
color: var(--sl-color-text-accent);
}
form > * + * {
margin-top: 1rem;
}
.opt-list {
list-style: none;
padding-inline-start: 0;
}
.footer {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
.submit,
.answer {
padding: 0.8rem;
border-radius: 1.5rem;
line-height: 1.4;
}
.submit {
cursor: pointer;
color: var(--sl-color-accent);
background-color: white;
}
.submit:focus {
background-color: hsl(224, 20%, 94%);
outline: 3px solid var(--sl-color-accent);
outline-offset: -3px;
}
.submit:not(:focus-visible) {
outline: none;
}
.submit:hover {
background: var(--sl-color-text-accent);
color: var(--sl-color-black);
border-color: transparent;
}
.submit:disabled,
.submit:active {
transform: translateY(0.25rem);
}
.submit:disabled {
background-color: hsl(224, 20%, 94%);
color: hsl(224, 7%, 36%);
opacity: 0.65;
cursor: not-allowed;
}
.answer {
text-align: center;
transition-property: color, background-color;
transition-duration: 250ms;
transition-timing-function: ease-out;
border-color: transparent;
}
.correct {
color: var(--sl-color-green-low);
background-color: var(--sl-color-green);
animation: tada 1s 0;
}
.wrong {
color: var(--sl-color-red-low);
background-color: var(--sl-color-red);
animation: nope 0.3s 0;
}
@media (prefers-reduced-motion: no-preference) {
.submit {
transition-property: box-shadow, transform;
transition-duration: 0.15s;
transition-timing-function: cubic-bezier(0.4, 2.5, 0.6, 1);
}
.correct,
.wrong {
animation-iteration-count: 1;
}
}
@keyframes tada {
0%,
100% {
transform: scale3d(1, 1, 1);
}
10%,
20% {
transform: scale3d(0.8, 0.8, 0.8) rotate3d(0, 0, 1, -3deg);
}
30%,
50%,
70%,
90% {
transform: scale3d(1.05, 1.05, 1.05) rotate3d(0, 0, 1, 3deg);
}
40%,
60%,
80% {
transform: scale3d(1.05, 1.05, 1.05) rotate3d(0, 0, 1, -3deg);
}
}
@keyframes nope {
0%,
100% {
transform: translate3d(0, 0, 0);
}
20%,
60% {
transform: translate3d(-0.5rem, 0, 0);
}
40%,
80% {
transform: translate3d(0.5rem, 0, 0);
}
}
</style>
<script>
class MultipleChoice extends HTMLElement {
#defaultCorrectLabel: string;
#defaultIncorrectLabel: string;
#key: string;
#submitEl: HTMLButtonElement;
#answerEl: HTMLParagraphElement;
constructor() {
super();
this.#defaultCorrectLabel = this.dataset.correctLabel!;
this.#defaultIncorrectLabel = this.dataset.incorrectLabel!;
this.#key = Math.random().toString();
this.#submitEl = this.querySelector(".submit")!;
this.#answerEl = this.querySelector(".answer")!;
this.querySelectorAll(".opt-list > li").forEach((li) =>
this.#upgradeListItem(li)
);
}
#upgradeListItem(li: Element) {
const option = li.querySelector(
'input[type="radio"]'
) as HTMLInputElement | null;
if (!option) return;
option.removeAttribute("disabled");
option.setAttribute("name", this.#key);
option.addEventListener("change", () => {
this.#clearAnswer();
this.#enableSubmit();
});
if (option.checked) this.#enableSubmit();
}
/** Clear the answer text and hide its container visually. */
#clearAnswer() {
this.#answerEl.innerText = "";
this.#answerEl.classList.remove("wrong", "correct");
this.#answerEl.classList.add("sr-only");
}
/** Show the answer result to the user. */
#setAnswer(isCorrect: boolean) {
const answerTemplate = this.querySelector(
"input:checked ~ template"
) as HTMLTemplateElement | null;
if (answerTemplate) {
this.#answerEl.replaceChildren(answerTemplate.content.cloneNode(true));
} else {
this.#answerEl.innerText = isCorrect
? this.#defaultCorrectLabel
: this.#defaultIncorrectLabel;
}
this.#answerEl.classList.remove("sr-only", "wrong", "correct");
this.#answerEl.classList.add(isCorrect ? "correct" : "wrong");
}
/** Activate the submit button, preparing it to evaluate the form when clicked. */
#enableSubmit() {
this.#submitEl.removeAttribute("disabled");
this.#submitEl.classList.remove("sr-only");
this.#submitEl.onclick = () => this.#submitAnswer();
}
/** Disable the submit button and hide it visually. */
#disableSubmit() {
this.#submitEl.setAttribute("disabled", "");
this.#submitEl.classList.add("sr-only");
this.#submitEl.onclick = null;
}
/** Check if the selected option is correct and display the result. */
#submitAnswer() {
const selection = this.querySelector(
"input:checked"
) as HTMLInputElement | null;
if (!selection) return;
this.#disableSubmit();
const isCorrect =
selection.dataset.isCorrect !== undefined &&
["", "true"].includes(selection.dataset.isCorrect);
this.#setAnswer(isCorrect);
}
}
customElements.define("multiple-choice", MultipleChoice);
</script>

@ -0,0 +1,94 @@
---
export interface Props {
isCorrect?: boolean;
}
const { isCorrect } = Astro.props as Props;
---
<li>
<label>
<input type="radio" disabled data-is-correct={isCorrect} /><span
><slot /></span
>
{
Astro.slots.has("feedback") && (
<template>
<slot name="feedback" />
</template>
)
}
</label>
</li>
<style>
label {
display: grid;
grid-template-columns: auto 1fr;
gap: 1rem;
align-items: center;
border-radius: 1rem;
margin-inline: -0.5rem;
padding: 0.5rem;
cursor: pointer;
}
label:hover {
background-color: var(--sl-color-accent-low);
}
input[type="radio"] {
-webkit-appearance: none;
appearance: none;
cursor: pointer;
}
input[type="radio"] ~ * {
color: var(--sl-color-text-accent);
}
input[type="radio"]:checked ~ * {
color: var(--sl-color-white);
}
input[type="radio"]:focus::after {
outline: 3px solid var(--sl-color-text-accent);
outline-offset: 0.5rem;
}
input[type="radio"]:focus,
input[type="radio"]:not(:focus-visible)::after {
outline: none;
}
input[type="radio"]::after {
display: block;
content: "";
text-align: center;
line-height: 1.5;
width: 1.5em;
height: 1.5em;
font-size: 1.25em;
font-weight: 900;
border: 2px solid var(--sl-color-accent-high);
border-radius: 100%;
color: var(--sl-color-white);
box-shadow: -3px 3px var(--theme-shade-subtle);
}
input[type="radio"]:checked::after {
color: hsl(var(--color-base-white), 100%);
background-image: radial-gradient(
var(--sl-color-white) 50%,
transparent 0%,
transparent
);
border-color: var(--sl-color-white);
}
@media (forced-colors: active) {
input[type="radio"]:checked::after {
background-color: Highlight;
}
}
</style>

@ -5,6 +5,9 @@ draft: true
import { Card, LinkCard } from '@astrojs/starlight/components';
import Question from '../../../components/Question.astro';
import MultipleChoice from '../../../components/MultipleChoice.astro';
import Option from '../../../components/Option.astro';
В этой главе мы совершим краткий *теоретический обзор* сферы веб-картографии
@ -46,11 +49,28 @@ import Question from '../../../components/Question.astro';
Веб-технологии расширяют круг **выразительных средств**: появляется возможность использовать мультимедийные материалы (фото, аудио, видео), давать ссылки на другие веб-страницы, создавать интерактивные картографические анимации. Технически это реализуемо и на электронных картах, локально размещаемых на компьютере пользователя, однако активное использование новых выразительных средств характерно именно для веб-карт. Карта в веб-среде становится динамичной интерактивной моделью.
<Card title='Главная особенность веб-картографрования это'>
{/* <Card title='Главная особенность веб-картографрования это'>
<Question answer="cетевая среда распространения" ballast={['интерактивность', 'использование стандартов']} explanation="Особенности веб-картографирования проистекают из сетевой среды распространения картографических материалов"/>
</Card>
</Card> */}
Среди выделенных особенностей ключевой является сетевая среда распространения. Остальные особенности можно назвать производными от ключевой.
<Card title='Главная особенность веб-картографрования это'>
<MultipleChoice>
<Option>
интерактивность
</Option>
<Option>
использование стандартов
</Option>
<Option isCorrect>
cетевая среда распространения
</Option>
</MultipleChoice>
<details>
<summary>Развёрнутый ответ</summary>
Особенности веб-картографирования проистекают из сетевой среды распространения картографических материалов. Другими словами, сетевая среда распространения является ключевой особенностью веб-картографирования, а остальные производными от неё.
</details>
</Card>
## Веб-картография

@ -4,6 +4,8 @@ title: Веб-карта
import { Card, FileTree, LinkCard } from '@astrojs/starlight/components';
import Question from '../../../components/Question.astro';
import MultipleChoice from '../../../components/MultipleChoice.astro';
import Option from '../../../components/Option.astro';
В этой главе мы рассмотрим
@ -80,8 +82,27 @@ import Question from '../../../components/Question.astro';
Разметка, стили, логика (программный код) веб-карты, а также наборы пространственных данных веб-карты хранятся на сервере. Пользователь вводит адрес карты. Браузер запрашивает с сервера разметку веб-карты (html). Разметка приходит в браузер. Разметра содержит запросы к стилям и логике веб-карты. Логика веб-карты приходит в браузер и запрашивает наборы пространственных данных для создания веб-карты. Пространственные данные приходят [Взаимодействие с сервером закончено!] и кодом веб-карты превращаются в картографические слои.
<Card title='В клиент-серверной архитектуре браузер это'>
{/* <Card title='В клиент-серверной архитектуре браузер это'>
<Question answer="клиент" ballast={['сервер', 'пользователь']} explanation="Пользователь использует браузер (клиент), чтобы выполнить запрос к серверу"/>
</Card> */}
<Card title='В клиент-серверной архитектуре браузер это'>
<MultipleChoice>
<Option>
сервер
</Option>
<Option isCorrect>
клиент
</Option>
<Option >
пользователь
</Option>
</MultipleChoice>
<details>
<summary>Развёрнутый ответ</summary>
Пользователь использует браузер (клиент), чтобы выполнить запрос к серверу
</details>
</Card>
## Создание первой веб-карты

@ -4,6 +4,8 @@ title: API
import { Card, FileTree, LinkCard, TabItem, Tabs } from '@astrojs/starlight/components';
import Question from '../../../components/Question.astro';
import MultipleChoice from '../../../components/MultipleChoice.astro';
import Option from '../../../components/Option.astro';
@ -89,8 +91,27 @@ API обычно переводят как прикладной программ
</TabItem>
</Tabs>
{/* <Card title={'Строка <code>&lt;div id="map">&lt;/div></code> это'}> */}
{/* <Question answer="контейнер для карты" ballast={['инициализация карты', 'стиль карты']} explanation="Это разметка контейнера карты"/> */}
{/* </Card> */}
<Card title={'Строка <code>&lt;div id="map">&lt;/div></code> это'}>
<Question answer="контейнер для карты" ballast={['инициализация карты', 'стиль карты']} explanation="Это разметка контейнера карты"/>
<MultipleChoice>
<Option isCorrect>
контейнер для карты
</Option>
<Option>
инициализация карты
</Option>
<Option>
стиль карты
</Option>
</MultipleChoice>
<details>
<summary>Развёрнутый ответ</summary>
Этой строкой мы размечаем контейнер для карты в HTML-файле. В JavaScript-файле мы обращаемся к этому контейнеру по `id`
</details>
</Card>
Удостоверимся, что карта отображается на локальном сервере.

Loading…
Cancel
Save