summaryrefslogtreecommitdiff
path: root/frontend/app.jsx
diff options
context:
space:
mode:
authorAlexander Neonxp Kiryukhin <i@neonxp.ru>2024-12-09 01:07:15 +0300
committerAlexander Neonxp Kiryukhin <i@neonxp.ru>2024-12-09 01:07:15 +0300
commit34ccc98a942098faefb5f4211b215ff9ccc7ad0e (patch)
tree7696ab4d7c8d9fb09c7e2575d482517f68824ae3 /frontend/app.jsx
Начальный
Diffstat (limited to 'frontend/app.jsx')
-rw-r--r--frontend/app.jsx207
1 files changed, 207 insertions, 0 deletions
diff --git a/frontend/app.jsx b/frontend/app.jsx
new file mode 100644
index 0000000..60bdcf4
--- /dev/null
+++ b/frontend/app.jsx
@@ -0,0 +1,207 @@
+import React, { useState, useEffect } from "react";
+import GlMap, { Source, Marker, Layer } from "react-map-gl/maplibre";
+import {
+ Flex,
+ Layout,
+ Col,
+ Row,
+ Modal,
+ Form,
+ Input,
+ Button,
+ Image,
+ Card,
+} from "antd";
+
+const { Header, Content } = Layout;
+
+const layout = {
+ labelCol: { span: 8 },
+ wrapperCol: { span: 16 },
+};
+
+const geostyle = {
+ id: "data",
+ type: "line",
+ paint: {
+ "line-color": "red",
+ "line-width": 3,
+ },
+};
+
+const App = () => {
+ const [state, setState] = useState({});
+ const [selected, setSelected] = useState(null);
+ const [guess, setGuess] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [form] = Form.useForm();
+
+ useEffect(() => {
+ fetch("/api/state")
+ .then((x) => x.json())
+ .then(setState)
+ .then(() => setLoading(false));
+ }, []);
+
+ useEffect(() => {
+ if (guess != null) {
+ setState(guess.state);
+ }
+ }, [guess]);
+
+ const onFinish = (values) => {
+ fetch("/api/state", {
+ method: "POST",
+ body: JSON.stringify(values),
+ headers: { "content-type": "application/json" },
+ })
+ .then((x) => x.json())
+ .then(setState);
+ };
+
+ const onReset = () => {
+ form.resetFields();
+ };
+
+ const onNext = () => {
+ setSelected(null);
+ setGuess(null);
+ setLoading(true);
+ fetch("/api/next", {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ })
+ .then((x) => x.json())
+ .then(setState)
+ .then(() => setLoading(false));
+ };
+
+ const onMapClick = (e) => {
+ if (state.current_guid) {
+ setSelected(e.lngLat);
+ }
+ };
+
+ const onGuess = () => {
+ setLoading(true);
+ fetch("/api/guess", {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify(selected),
+ })
+ .then((x) => x.json())
+ .then(setGuess)
+ .then(() => setLoading(false));
+ };
+
+ return (
+ <>
+ <Layout>
+ <Content>
+ <Row style={{ height: "100vh" }}>
+ <Col sm={16}>
+ <GlMap
+ initialViewState={{
+ longitude: 49.106414,
+ latitude: 55.796127,
+ zoom: 11,
+ }}
+ onClick={onMapClick}
+ style={{ width: "100%", height: "100%" }}
+ mapStyle="https://tiles.openfreemap.org/styles/liberty"
+ >
+ {selected ? (
+ <Marker
+ latitude={selected.lat}
+ longitude={selected.lng}
+ />
+ ) : null}
+ {guess ? (
+ <Source
+ type="geojson"
+ data={JSON.parse(guess.geojson)}
+ >
+ <Layer {...geostyle} />
+ </Source>
+ ) : null}
+ </GlMap>
+ </Col>
+ <Col sm={8}>
+ {state.username ? (
+ <Card>
+ <h1>{state.username}</h1>
+ <h2>{state.points} очков</h2>
+ </Card>
+ ) : null}
+ <Card>
+ {!loading && !state.current_guid ? (
+ <Button
+ type="primary"
+ onClick={onNext}
+ block
+ size="large"
+ >
+ Новое задание
+ </Button>
+ ) : null}
+ {!loading && state.current_guid ? (
+ <Image src={state.image} />
+ ) : null}
+ {!loading && guess ? (
+ <>
+ <h2>{guess.name}</h2>
+ <Image src={guess.image} />
+ <h3>Расстояние: {guess.distance / 1000}км.</h3>
+ </>
+ ) : null}
+ {state.current_guid && !selected ? (
+ <p>
+ Нажмите на карте на точку, где по вашему
+ мнение находится то, что на фотографии
+ </p>
+ ) : null}
+ {state.current_guid && selected ? (
+ <Button
+ type="primary"
+ onClick={onGuess}
+ block
+ size="large"
+ >
+ Проверить
+ </Button>
+ ) : null}
+ </Card>
+ <p>Сделал <a href="https://neonxp.ru">Александр Кирюхин</a> в 2024 году</p>
+ </Col>
+ </Row>
+ </Content>
+ </Layout>
+ <Modal
+ title="Представьтесь"
+ open={!loading && !state.username}
+ onOk={form.submit}
+ onCancel={onReset}
+ >
+ <p>Для начала игры необходимо представиться</p>
+ <Form
+ {...layout}
+ form={form}
+ name="control-hooks"
+ onFinish={onFinish}
+ style={{ maxWidth: 600 }}
+ >
+ <Form.Item
+ name="username"
+ label="Имя"
+
+ rules={[{ required: true }]}
+ >
+ <Input />
+ </Form.Item>
+ </Form>
+ </Modal>
+ </>
+ );
+};
+
+export default App;