interactivity!!!̆
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -42,3 +42,4 @@ next-env.d.ts
|
||||
|
||||
# Large images
|
||||
/public/tiles
|
||||
/public/fonts
|
6
bun.lock
6
bun.lock
@ -13,10 +13,12 @@
|
||||
"leaflet": "^1.9.4",
|
||||
"next": "15.5.4",
|
||||
"next-auth": "^5.0.0-beta.29",
|
||||
"next-safe-action": "^8.0.11",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-leaflet": "^5.0.0",
|
||||
"react-modal": "^3.16.3",
|
||||
"zod": "^4.1.11",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
@ -612,6 +614,8 @@
|
||||
|
||||
"next-auth": ["next-auth@5.0.0-beta.29", "", { "dependencies": { "@auth/core": "0.40.0" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", "@simplewebauthn/server": "^9.0.2", "next": "^14.0.0-0 || ^15.0.0-0", "nodemailer": "^6.6.5", "react": "^18.2.0 || ^19.0.0-0" }, "optionalPeers": ["@simplewebauthn/browser", "@simplewebauthn/server", "nodemailer"] }, "sha512-Ukpnuk3NMc/LiOl32njZPySk7pABEzbjhMUFd5/n10I0ZNC7NCuVv8IY2JgbDek2t/PUOifQEoUiOOTLy4os5A=="],
|
||||
|
||||
"next-safe-action": ["next-safe-action@8.0.11", "", { "peerDependencies": { "next": ">= 14.0.0", "react": ">= 18.2.0", "react-dom": ">= 18.2.0" } }, "sha512-gqJLmnQLAoFCq1kRBopN46New+vx1n9J9Y/qDQLXpv/VqU40AWxDakvshwwnWAt8R0kLvlakNYNLX5PqlXWSMg=="],
|
||||
|
||||
"oauth4webapi": ["oauth4webapi@3.8.1", "", {}, "sha512-olkZDELNycOWQf9LrsELFq8n05LwJgV8UkrS0cburk6FOwf8GvLam+YB+Uj5Qvryee+vwWOfQVeI5Vm0MVg7SA=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
@ -832,6 +836,8 @@
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
@ -18,10 +18,12 @@
|
||||
"leaflet": "^1.9.4",
|
||||
"next": "15.5.4",
|
||||
"next-auth": "^5.0.0-beta.29",
|
||||
"next-safe-action": "^8.0.11",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-leaflet": "^5.0.0",
|
||||
"react-modal": "^3.16.3"
|
||||
"react-modal": "^3.16.3",
|
||||
"zod": "^4.1.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
|
@ -1,9 +1,64 @@
|
||||
"use client";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useMemo } from "react";
|
||||
import type {MapData} from "@/app/map"
|
||||
|
||||
export function Map(props: {data: MapData}) {
|
||||
import dynamic from "next/dynamic";
|
||||
import { ReactNode, RefObject, useMemo, useRef, useState } from "react";
|
||||
import { Coords, EditStates, MapData, Setter } from "@/app/types"
|
||||
import { addPOI } from "@/app/db-actions";
|
||||
import { useAction } from "next-safe-action/hooks";
|
||||
import { signIn } from "@/auth";
|
||||
|
||||
export function Dialog({
|
||||
ref,
|
||||
title,
|
||||
buttons,
|
||||
children,
|
||||
}: {
|
||||
ref: RefObject<HTMLDialogElement | null>
|
||||
title: ReactNode,
|
||||
children?: ReactNode,
|
||||
buttons: {
|
||||
label: ReactNode,
|
||||
action?: () => any,
|
||||
}[]
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<dialog ref={ref} className="dialog-main">
|
||||
<div className='dialog-title'>
|
||||
{title}
|
||||
</div>
|
||||
<div className='dialog-body'>
|
||||
<div className='dialog-elems'>
|
||||
{children}
|
||||
</div>
|
||||
<div className='dialog-buttons'>
|
||||
{buttons.map(({ label, action }, index) =>
|
||||
<button key={index}
|
||||
onClick={async () => {
|
||||
if (action) {
|
||||
const a = action()
|
||||
if (a instanceof Promise) await a
|
||||
}
|
||||
ref.current?.close()
|
||||
}}
|
||||
>{label}</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function Map(props: {
|
||||
data: MapData,
|
||||
coords: Coords,
|
||||
setCoords: Setter<Coords>,
|
||||
curPath?: Coords[],
|
||||
setCurPath: Setter<Coords[]>,
|
||||
editState?: EditStates,
|
||||
setEditState: Setter<EditStates>
|
||||
}) {
|
||||
const It = useMemo(() => dynamic(
|
||||
() => import("@/app/map"),
|
||||
{
|
||||
@ -13,3 +68,36 @@ export function Map(props: {data: MapData}) {
|
||||
), [])
|
||||
return <It {...props} />;
|
||||
}
|
||||
|
||||
export function Toolbar(props: { editState: EditStates, setEditState: Setter<EditStates> }) {
|
||||
const addPOIAction = useAction(addPOI, { onSuccess: () => { alert("Doing stuff I guess"); addPOIAction.reset() } })
|
||||
return <>
|
||||
<div className='toolbar'>
|
||||
{
|
||||
[
|
||||
{ label: "Add PoI", state: EditStates.ADD_POI },
|
||||
{ label: "Add Settlement", state: EditStates.ADD_SETTLEMENT },
|
||||
{ label: "Add Road", state: EditStates.ADD_ROAD },
|
||||
{ label: "Add Rail", state: EditStates.ADD_TRAIN },
|
||||
{ label: "Add Transit Station", state: EditStates.ADD_STOP },
|
||||
{ label: "Add Country Part", state: EditStates.ADD_COUNTRY },
|
||||
].map(it =>
|
||||
<button key={it.state} onClick={() => props.setEditState(it.state)}>{it.label}</button>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<p>Current State: {EditStates[props.editState]}</p>
|
||||
</>
|
||||
}
|
||||
|
||||
export function Client({ data, id }: { data: MapData, id?: string }) {
|
||||
const [coords, setCoords]: [Coords, Setter<Coords>] = useState([0, 0])
|
||||
const [editState, setEditState]: [EditStates | undefined, Setter<EditStates>] = useState()
|
||||
const [curPath, setCurPath]: [Coords[] | undefined, Setter<Coords[]>] = useState()
|
||||
return <>
|
||||
<Map {...{ data, coords, setCoords, curPath, setCurPath, editState, setEditState }} />
|
||||
{
|
||||
(!id) ? null : <Toolbar {...{editState: editState ?? EditStates.NONE, setEditState}}/>
|
||||
}
|
||||
</>
|
||||
}
|
31
src/app/db-actions.ts
Normal file
31
src/app/db-actions.ts
Normal file
@ -0,0 +1,31 @@
|
||||
"use server";
|
||||
|
||||
import { z } from "zod";
|
||||
import { returnValidationErrors } from "next-safe-action";
|
||||
import { actionClient } from "@/app/safe-action";
|
||||
import { auth } from "@/auth";
|
||||
import { notFound } from "next/navigation";
|
||||
import { sql } from "bun";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
const addPOISchema = z.object({
|
||||
label: z.string().trim().min(1).max(256),
|
||||
coordinates: z.object({
|
||||
x: z.number().min(-8000).max(8000),
|
||||
y: z.number().min(-8000).max(8000)
|
||||
})
|
||||
})
|
||||
|
||||
export const addPOI = actionClient
|
||||
.inputSchema(addPOISchema)
|
||||
.action(async ({parsedInput: { label, coordinates }}) => {
|
||||
console.log("Arrived in the server action")
|
||||
const id = (await auth())?.discord_id
|
||||
if (!id) notFound();
|
||||
const coords_formatted = `(${coordinates.x},${coordinates.y})`
|
||||
await sql.begin(async (tx) =>
|
||||
await tx`INSERT INTO points_of_interest (label, coordinates, last_editor) VALUES (${label}, ${coords_formatted}, ${id}) RETURNING *`
|
||||
)
|
||||
revalidatePath("/");
|
||||
return {};
|
||||
})
|
@ -1,12 +1,45 @@
|
||||
@font-face {
|
||||
font-family: Andika;
|
||||
font-feature-settings: "ss13" on;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
src: url("/fonts/Andika-Regular.ttf");
|
||||
}
|
||||
@font-face {
|
||||
font-family: Andika;
|
||||
font-feature-settings: "ss13" on;
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
src: url("/fonts/Andika-Italic.ttf");
|
||||
}
|
||||
@font-face {
|
||||
font-family: Andika;
|
||||
font-feature-settings: "ss13" on;
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
src: url("/fonts/Andika-Bold.ttf");
|
||||
}
|
||||
@font-face {
|
||||
font-family: Andika;
|
||||
font-feature-settings: "ss13" on;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
src: url("/fonts/Andika-BoldItalic.ttf");
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: #fff;
|
||||
--foreground: #000;
|
||||
--background-accent: #ccc;
|
||||
--background-accent2: #bbb;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #000;
|
||||
--foreground: #fff;
|
||||
--background-accent: #444;
|
||||
--background-accent2: #555;
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +57,7 @@ body {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: 'Andika', sans-serif;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@ -31,3 +65,54 @@ body {
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-main {
|
||||
position: absolute;
|
||||
inset: 50%;
|
||||
transform: translate(-50%);
|
||||
width: 40ch;
|
||||
max-width: 60ch;
|
||||
height: 10rem;
|
||||
color: var(--foreground);
|
||||
&:open {
|
||||
display: flex;
|
||||
}
|
||||
background-color: var(--background);
|
||||
flex-direction: column;
|
||||
}
|
||||
.dialog-title {
|
||||
width: 100%;
|
||||
font-size: 1.25rem;
|
||||
text-align: center;
|
||||
background-color: var(--background-accent);
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dialog-elems {
|
||||
padding: .5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.toolbar, .dialog-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: auto;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
|
||||
.toolbar button, dialog button {
|
||||
min-width: 10ch;
|
||||
background-color: var(--background-accent);
|
||||
&:hover {
|
||||
background-color: var(--background-accent2);
|
||||
}
|
||||
}
|
163
src/app/map.tsx
163
src/app/map.tsx
@ -1,91 +1,60 @@
|
||||
"use client";
|
||||
import { MapContainer, Marker, Rectangle, TileLayer, Tooltip, Polyline, Polygon } from "react-leaflet";
|
||||
import L, { Icon } from "leaflet";
|
||||
import { CountryEntry, MapData, Coords, Setter, EditStates } from "@/app/types";
|
||||
import { ReactNode, RefObject, useCallback, useRef, useState } from "react";
|
||||
import { useAction } from "next-safe-action/hooks";
|
||||
import { addPOI } from "@/app/db-actions";
|
||||
import { Dialog } from "@/app/client";
|
||||
|
||||
export type CountryEntry = {
|
||||
code2: string;
|
||||
code3: string;
|
||||
common_name: string;
|
||||
name?: string;
|
||||
ruler?: string | string[];
|
||||
ruler_title?: string;
|
||||
ruler_link?: string;
|
||||
founded?: string;
|
||||
capital?: string;
|
||||
ung?: "MEMBER" | "OBSERVER" | "FORMER";
|
||||
ung_joined?: string;
|
||||
ung_demoted?: string;
|
||||
ung_left?: string;
|
||||
dissolved?: string | true;
|
||||
dissolved_date?: string;
|
||||
disputed?: string | true;
|
||||
not_ngation?: true;
|
||||
condominium?: string[]
|
||||
};
|
||||
|
||||
export type POI = {
|
||||
coordinates: string;
|
||||
label: string;
|
||||
id: number;
|
||||
last_editor: number | string
|
||||
};
|
||||
|
||||
export type Road = {
|
||||
id: number;
|
||||
path: string;
|
||||
network: string;
|
||||
name: string;
|
||||
last_editor: number | string
|
||||
}
|
||||
|
||||
export type CountryPart = {
|
||||
id: number;
|
||||
country: string;
|
||||
shape: string;
|
||||
last_editor: number | string;
|
||||
}
|
||||
|
||||
export type RailLine = {
|
||||
id: String;
|
||||
path: String;
|
||||
last_editor: number | string;
|
||||
}
|
||||
|
||||
export type Settlement = {
|
||||
id: number;
|
||||
coordinates: string;
|
||||
name: string;
|
||||
last_editor: number | string
|
||||
}
|
||||
export type TransitStop = {
|
||||
id: string;
|
||||
coordinates: string;
|
||||
last_editor: number | string;
|
||||
}
|
||||
|
||||
export type MapData = {
|
||||
countries: CountryEntry[];
|
||||
parts: CountryPart[];
|
||||
pois: POI[];
|
||||
farms: POI[];
|
||||
rails: RailLine[];
|
||||
roads: Road[];
|
||||
settlements: Settlement[];
|
||||
stops: TransitStop[];
|
||||
}
|
||||
|
||||
|
||||
function find_entry(code: string, entries: CountryEntry[]) : CountryEntry|undefined {
|
||||
function find_entry(code: string, entries: CountryEntry[]): CountryEntry | undefined {
|
||||
return entries.find((it) => it.code2 === code || it.code3 === code)
|
||||
}
|
||||
|
||||
function parseCoords(desc: string) : [number, number][] {
|
||||
console.log(desc);
|
||||
function parseCoords(desc: string): [number, number][] {
|
||||
const foo = [...desc.matchAll(/\((-?\d+),(-?\d+)\)/g)]
|
||||
return foo.map(it => [-+it[2] - .5, +it[1] + .5])
|
||||
}
|
||||
|
||||
export default function Map(props: { data: MapData }) {
|
||||
const { data } = props;
|
||||
export default function Map(props: {
|
||||
data: MapData,
|
||||
coords: Coords,
|
||||
setCoords: Setter<Coords>,
|
||||
curPath?: Coords[],
|
||||
setCurPath: Setter<Coords[]>,
|
||||
editState?: EditStates,
|
||||
setEditState: Setter<EditStates>
|
||||
}) {
|
||||
const {
|
||||
data,
|
||||
coords,
|
||||
setCoords,
|
||||
curPath,
|
||||
setCurPath,
|
||||
editState,
|
||||
setEditState
|
||||
} = props;
|
||||
// refs to dialogs
|
||||
const poiDialog = useRef<HTMLDialogElement>(null);
|
||||
const poiDialogLabel = useRef<HTMLInputElement>(null);
|
||||
const addPOIAction = useAction(addPOI, { onSuccess: () => { console.log("running the action’s callback"); addPOIAction.reset() } })
|
||||
const map = useCallback((it: L.Map | null) => {
|
||||
|
||||
if (!it) return;
|
||||
it.off("click");
|
||||
it.on("click", e => {
|
||||
setCoords([Math.round(e.latlng.lng), Math.round(-e.latlng.lat)])
|
||||
|
||||
console.log("running the on click handler of the map", coords)
|
||||
//switch (editState) {
|
||||
//case EditStates.NONE: return;
|
||||
//case EditStates.ADD_POI: {
|
||||
poiDialog.current?.showModal();
|
||||
return;
|
||||
//}
|
||||
//}
|
||||
})
|
||||
}, [editState, curPath])
|
||||
return (
|
||||
<>
|
||||
<MapContainer
|
||||
@ -93,7 +62,8 @@ export default function Map(props: { data: MapData }) {
|
||||
center={[0, 0]}
|
||||
maxBounds={[[-8192, -8192], [8192, 8192]]}
|
||||
crs={L.CRS.Simple}
|
||||
zoom={0}>
|
||||
zoom={0}
|
||||
ref={map}>
|
||||
<TileLayer
|
||||
url="/tiles/{z}/{x}_{y}.png"
|
||||
tileSize={512}
|
||||
@ -102,14 +72,14 @@ export default function Map(props: { data: MapData }) {
|
||||
<Rectangle bounds={[[-8000, -8000], [8000, 8000]]} pathOptions={{ color: "#000", stroke: true, fill: false, weight: 2 }} />
|
||||
{
|
||||
data.pois.map(it =>
|
||||
<Marker key={it.id} position={parseCoords(it.coordinates)[0]} icon={new Icon({iconUrl: "/legend/poi.svg", iconAnchor: [6, 6]})}>
|
||||
<Marker key={it.id} position={parseCoords(it.coordinates)[0]} icon={new Icon({ iconUrl: "/legend/poi.svg", iconAnchor: [6, 6] })}>
|
||||
<Tooltip>{it.label}</Tooltip>
|
||||
</Marker>
|
||||
)
|
||||
}
|
||||
{
|
||||
data.settlements.map(it =>
|
||||
<Marker key={it.id} position={parseCoords(it.coordinates)[0]} icon={new Icon({iconUrl: "/legend/city.svg", iconAnchor: [6, 6]})}>
|
||||
<Marker key={it.id} position={parseCoords(it.coordinates)[0]} icon={new Icon({ iconUrl: "/legend/city.svg", iconAnchor: [6, 6] })}>
|
||||
<Tooltip>{it.name}</Tooltip>
|
||||
</Marker>
|
||||
)
|
||||
@ -124,12 +94,37 @@ export default function Map(props: { data: MapData }) {
|
||||
{
|
||||
data.parts.map(it =>
|
||||
<Polygon key={it.id} positions={parseCoords(it.shape)} stroke={true} color="#A00" fill={true} fillOpacity={.5} fillColor="#A00">
|
||||
<Tooltip position={parseCoords(it.shape)[0]}>{find_entry(it.country, data.countries)?.common_name}</Tooltip>
|
||||
<Tooltip>{find_entry(it.country, data.countries)?.common_name}</Tooltip>
|
||||
</Polygon>
|
||||
)
|
||||
}
|
||||
{data.stops.map(it => <Marker key={it.id} position={parseCoords(it.coordinates)[0]}><Tooltip>{it.id}</Tooltip></Marker>)}
|
||||
{
|
||||
data.stops.map(it =>
|
||||
<Marker key={it.id} position={parseCoords(it.coordinates)[0]}>
|
||||
<Tooltip>{it.id}</Tooltip>
|
||||
</Marker>
|
||||
)
|
||||
}
|
||||
</MapContainer>
|
||||
|
||||
<Dialog
|
||||
title="Add Point of Interest"
|
||||
ref={poiDialog}
|
||||
buttons={[
|
||||
{
|
||||
label: "Add", action: () => {
|
||||
if (!poiDialogLabel.current) throw Error("WHAT THE FUCK????")
|
||||
console.log("running the dialog’s action")
|
||||
console.log(poiDialogLabel.current.value, coords[0], coords[1])
|
||||
addPOIAction.execute({
|
||||
label: poiDialogLabel.current.value,
|
||||
coordinates: { x: coords[0], y: coords[1] }
|
||||
})
|
||||
}
|
||||
},
|
||||
{ label: "Cancel" },
|
||||
]}
|
||||
>
|
||||
<input ref={poiDialogLabel} type="text" placeholder="Point of Interest" />
|
||||
</Dialog>
|
||||
</>)
|
||||
}
|
@ -1,16 +1,13 @@
|
||||
import { auth, signIn } from "@/auth";
|
||||
import { Map } from "@/app/client";
|
||||
import type {MapData, CountryEntry, CountryPart, POI, RailLine, Settlement, TransitStop, Road} from "@/app/map"
|
||||
import { Client, /*Map,*/ Toolbar } from "@/app/client";
|
||||
import { MapData, CountryEntry, CountryPart, POI, RailLine, Settlement, TransitStop, Road, EditStates } from "@/app/types"
|
||||
import { sql } from "bun"
|
||||
import { JSDOM } from "jsdom"
|
||||
|
||||
|
||||
|
||||
|
||||
export async function getCountries () : Promise<CountryEntry[]> {
|
||||
export async function getCountries(): Promise<CountryEntry[]> {
|
||||
let data_raw = await fetch("https://mc.nguh.org/w/api.php?action=parse&page=Data:UŊCDSO%2FCountries&prop=text&format=json")
|
||||
let data = await data_raw.json()
|
||||
let parsed_text : string = data.parse.text["*"];
|
||||
let parsed_text: string = data.parse.text["*"];
|
||||
let parsed_xml = new JSDOM(parsed_text);
|
||||
let json_text = parsed_xml.window.document.querySelector("#jsondata")?.textContent
|
||||
if (json_text != undefined)
|
||||
@ -18,8 +15,9 @@ export async function getCountries () : Promise<CountryEntry[]> {
|
||||
else return [];
|
||||
}
|
||||
|
||||
async function getMapData() : Promise<MapData> {
|
||||
const [countries, parts, pois, farms, rails, roads, settlements, stops] :
|
||||
export async function getMapData(): Promise<MapData> {
|
||||
console.log("Fetched the data");
|
||||
const [countries, parts, pois, farms, rails, roads, settlements, stops]:
|
||||
[CountryEntry[], CountryPart[], POI[], POI[], RailLine[], Road[], Settlement[], TransitStop[]] = await Promise.all([
|
||||
getCountries(),
|
||||
sql`SELECT * from country_parts`,
|
||||
@ -36,11 +34,14 @@ async function getMapData() : Promise<MapData> {
|
||||
|
||||
export default async function Home() {
|
||||
const session = await auth();
|
||||
const data = await getMapData()
|
||||
return <>
|
||||
<Map data={await getMapData()}/>
|
||||
{(session) ? null : <form action={async () => {
|
||||
<Client data={data} id={session?.discord_id} />
|
||||
{
|
||||
(session) ? null : <form action={async () => {
|
||||
'use server';
|
||||
await signIn("discord")
|
||||
}}><button type="submit">Log In</button></form>}
|
||||
}}><button type="submit">Log In</button></form>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
3
src/app/safe-action.ts
Normal file
3
src/app/safe-action.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { createSafeActionClient } from "next-safe-action";
|
||||
|
||||
export const actionClient = createSafeActionClient();
|
89
src/app/types.ts
Normal file
89
src/app/types.ts
Normal file
@ -0,0 +1,89 @@
|
||||
|
||||
export type CountryEntry = {
|
||||
code2: string;
|
||||
code3: string;
|
||||
common_name: string;
|
||||
name?: string;
|
||||
ruler?: string | string[];
|
||||
ruler_title?: string;
|
||||
ruler_link?: string;
|
||||
founded?: string;
|
||||
capital?: string;
|
||||
ung?: "MEMBER" | "OBSERVER" | "FORMER";
|
||||
ung_joined?: string;
|
||||
ung_demoted?: string;
|
||||
ung_left?: string;
|
||||
dissolved?: string | true;
|
||||
dissolved_date?: string;
|
||||
disputed?: string | true;
|
||||
not_ngation?: true;
|
||||
condominium?: string[]
|
||||
};
|
||||
|
||||
export type POI = {
|
||||
coordinates: string;
|
||||
label: string;
|
||||
id: number;
|
||||
last_editor: number | string
|
||||
};
|
||||
|
||||
export type Road = {
|
||||
id: number;
|
||||
path: string;
|
||||
network: string;
|
||||
name: string;
|
||||
last_editor: number | string
|
||||
}
|
||||
|
||||
export type CountryPart = {
|
||||
id: number;
|
||||
country: string;
|
||||
shape: string;
|
||||
last_editor: number | string;
|
||||
}
|
||||
|
||||
export type RailLine = {
|
||||
id: String;
|
||||
path: String;
|
||||
last_editor: number | string;
|
||||
}
|
||||
|
||||
export type Settlement = {
|
||||
id: number;
|
||||
coordinates: string;
|
||||
name: string;
|
||||
last_editor: number | string
|
||||
}
|
||||
export type TransitStop = {
|
||||
id: string;
|
||||
coordinates: string;
|
||||
last_editor: number | string;
|
||||
}
|
||||
|
||||
export type MapData = {
|
||||
countries: CountryEntry[];
|
||||
parts: CountryPart[];
|
||||
pois: POI[];
|
||||
farms: POI[];
|
||||
rails: RailLine[];
|
||||
roads: Road[];
|
||||
settlements: Settlement[];
|
||||
stops: TransitStop[];
|
||||
}
|
||||
|
||||
export type Coords = [number, number];
|
||||
export type Setter<T> = (it: T) => void;
|
||||
|
||||
export enum EditStates {
|
||||
NONE,
|
||||
ADD_POI,
|
||||
ADD_SETTLEMENT,
|
||||
ADD_FARM,
|
||||
ADD_STOP,
|
||||
ADD_COUNTRY,
|
||||
ADD_COUNTRY_PARTIAL,
|
||||
ADD_ROAD,
|
||||
ADD_ROAD_PARTIAL,
|
||||
ADD_TRAIN,
|
||||
ADD_TRAIN_PARTIAL,
|
||||
}
|
Reference in New Issue
Block a user