diff options
author | Alexander Neonxp Kiryukhin <i@neonxp.ru> | 2024-12-09 01:07:15 +0300 |
---|---|---|
committer | Alexander Neonxp Kiryukhin <i@neonxp.ru> | 2024-12-09 01:07:15 +0300 |
commit | 34ccc98a942098faefb5f4211b215ff9ccc7ad0e (patch) | |
tree | 7696ab4d7c8d9fb09c7e2575d482517f68824ae3 /frontend |
Начальный
Diffstat (limited to 'frontend')
-rw-r--r-- | frontend/app.css | 4 | ||||
-rw-r--r-- | frontend/app.jsx | 207 | ||||
-rw-r--r-- | frontend/index.jsx | 8 |
3 files changed, 219 insertions, 0 deletions
diff --git a/frontend/app.css b/frontend/app.css new file mode 100644 index 0000000..2ffd64f --- /dev/null +++ b/frontend/app.css @@ -0,0 +1,4 @@ +html, body {height: 100%;padding:0;margin:0} +#app, .full { + height: 100%; +}
\ No newline at end of file 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; diff --git a/frontend/index.jsx b/frontend/index.jsx new file mode 100644 index 0000000..1153d89 --- /dev/null +++ b/frontend/index.jsx @@ -0,0 +1,8 @@ +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from "./app"; + +import "./app.css"; +import "maplibre-gl/dist/maplibre-gl.css"; + +createRoot(document.getElementById("app")).render(<App />); |