feat: add fresh
This commit is contained in:
parent
c678f7e14a
commit
a6635ccee6
12
components/Button.tsx
Normal file
12
components/Button.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { JSX } from "preact";
|
||||||
|
import { IS_BROWSER } from "$fresh/runtime.ts";
|
||||||
|
|
||||||
|
export function Button(props: JSX.HTMLAttributes<HTMLButtonElement>) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
{...props}
|
||||||
|
disabled={!IS_BROWSER || props.disabled}
|
||||||
|
class="px-2 py-1 border-gray-500 border-2 rounded bg-white hover:bg-gray-200 transition-colors"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
30
deno.json
Normal file
30
deno.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"lock": false,
|
||||||
|
"tasks": {
|
||||||
|
"start": "deno run -A --watch=static/,routes/ dev.ts",
|
||||||
|
"update": "deno run -A -r https://fresh.deno.dev/update ."
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"rules": {
|
||||||
|
"tags": [
|
||||||
|
"fresh",
|
||||||
|
"recommended"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"imports": {
|
||||||
|
"$fresh/": "https://deno.land/x/fresh@1.3.1/",
|
||||||
|
"preact": "https://esm.sh/preact@10.15.1",
|
||||||
|
"preact/": "https://esm.sh/preact@10.15.1/",
|
||||||
|
"preact-render-to-string": "https://esm.sh/*preact-render-to-string@6.2.0",
|
||||||
|
"@preact/signals": "https://esm.sh/*@preact/signals@1.1.3",
|
||||||
|
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.2.3",
|
||||||
|
"twind": "https://esm.sh/twind@0.16.19",
|
||||||
|
"twind/": "https://esm.sh/twind@0.16.19/",
|
||||||
|
"$std/": "https://deno.land/std@0.193.0/"
|
||||||
|
},
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "preact"
|
||||||
|
}
|
||||||
|
}
|
5
dev.ts
Executable file
5
dev.ts
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env -S deno run -A --watch=static/,routes/
|
||||||
|
|
||||||
|
import dev from "$fresh/dev.ts";
|
||||||
|
|
||||||
|
await dev(import.meta.url, "./main.ts");
|
38
fresh.gen.ts
Normal file
38
fresh.gen.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// DO NOT EDIT. This file is generated by fresh.
|
||||||
|
// This file SHOULD be checked into source version control.
|
||||||
|
// This file is automatically updated during development when running `dev.ts`.
|
||||||
|
|
||||||
|
import * as $0 from "./routes/_404.tsx";
|
||||||
|
import * as $1 from "./routes/_app.tsx";
|
||||||
|
import * as $2 from "./routes/api/joke.ts";
|
||||||
|
import * as $3 from "./routes/api/kosdaq.ts";
|
||||||
|
import * as $4 from "./routes/api/kospi.ts";
|
||||||
|
import * as $5 from "./routes/api/pages/[name].ts";
|
||||||
|
import * as $6 from "./routes/api/pages/index.ts";
|
||||||
|
import * as $7 from "./routes/greet/[name].tsx";
|
||||||
|
import * as $8 from "./routes/index.tsx";
|
||||||
|
import * as $9 from "./routes/pages/[name].tsx";
|
||||||
|
import * as $$0 from "./islands/Counter.tsx";
|
||||||
|
import * as $$1 from "./islands/StockList.tsx";
|
||||||
|
|
||||||
|
const manifest = {
|
||||||
|
routes: {
|
||||||
|
"./routes/_404.tsx": $0,
|
||||||
|
"./routes/_app.tsx": $1,
|
||||||
|
"./routes/api/joke.ts": $2,
|
||||||
|
"./routes/api/kosdaq.ts": $3,
|
||||||
|
"./routes/api/kospi.ts": $4,
|
||||||
|
"./routes/api/pages/[name].ts": $5,
|
||||||
|
"./routes/api/pages/index.ts": $6,
|
||||||
|
"./routes/greet/[name].tsx": $7,
|
||||||
|
"./routes/index.tsx": $8,
|
||||||
|
"./routes/pages/[name].tsx": $9,
|
||||||
|
},
|
||||||
|
islands: {
|
||||||
|
"./islands/Counter.tsx": $$0,
|
||||||
|
"./islands/StockList.tsx": $$1,
|
||||||
|
},
|
||||||
|
baseUrl: import.meta.url,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default manifest;
|
16
islands/Counter.tsx
Normal file
16
islands/Counter.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import type { Signal } from "@preact/signals";
|
||||||
|
import { Button } from "../components/Button.tsx";
|
||||||
|
|
||||||
|
interface CounterProps {
|
||||||
|
count: Signal<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Counter(props: CounterProps) {
|
||||||
|
return (
|
||||||
|
<div class="flex gap-8 py-6">
|
||||||
|
<Button onClick={() => props.count.value -= 1}>-1</Button>
|
||||||
|
<p class="text-3xl">{props.count}</p>
|
||||||
|
<Button onClick={() => props.count.value += 1}>+1</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
126
islands/StockList.tsx
Normal file
126
islands/StockList.tsx
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { Button } from "../components/Button.tsx";
|
||||||
|
import { useEffect } from "preact/hooks";
|
||||||
|
import { ComponentChildren } from "preact";
|
||||||
|
import { Signal, useSignal } from "@preact/signals";
|
||||||
|
|
||||||
|
interface StockProps {
|
||||||
|
pageName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToggleButtonProps {
|
||||||
|
children?: ComponentChildren;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToggleButton(props: ToggleButtonProps) {
|
||||||
|
return (
|
||||||
|
<Button {...props}>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryStatus<T> = {
|
||||||
|
type: "loading";
|
||||||
|
} | {
|
||||||
|
type: "complete";
|
||||||
|
data: T;
|
||||||
|
} | {
|
||||||
|
type: "error";
|
||||||
|
err: Error;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cacheMap = new Map<string, unknown>();
|
||||||
|
function useQuery<T>(url: string): Signal<QueryStatus<T>> {
|
||||||
|
const state = useSignal({
|
||||||
|
type: "loading",
|
||||||
|
} as QueryStatus<T>);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!cacheMap.has(url)) {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(url);
|
||||||
|
const data = await res.json();
|
||||||
|
cacheMap.set(url, data);
|
||||||
|
state.value = {
|
||||||
|
type: "complete",
|
||||||
|
data: data,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
state.value = {
|
||||||
|
type: "error",
|
||||||
|
err: err,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
} else {
|
||||||
|
state.value = {
|
||||||
|
type: "complete",
|
||||||
|
data: cacheMap.get(url) as T,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},[]);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Coperation {
|
||||||
|
Name: string;
|
||||||
|
Code: string;
|
||||||
|
Sector: string;
|
||||||
|
Product: string;
|
||||||
|
ListingDay: string;
|
||||||
|
ClosingMonth: string;
|
||||||
|
Representative: string;
|
||||||
|
Homepage: string;
|
||||||
|
AddressArea: string;
|
||||||
|
LastUpdate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageCorpsInfo {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
corpListByDate: Record<string, Coperation[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function StockList({data}: {data: PageCorpsInfo}){
|
||||||
|
console.log("data")
|
||||||
|
const keys = Object.keys(data.corpListByDate).sort().reverse().slice(0,5).reverse();
|
||||||
|
//const rows = data.corpListbyDate;
|
||||||
|
|
||||||
|
return <div class="flex">
|
||||||
|
{keys.map((x,i)=>{
|
||||||
|
const rows = data.corpListByDate[x];
|
||||||
|
return <div key={x}>
|
||||||
|
{rows.map(row=>{
|
||||||
|
return <div>
|
||||||
|
{row.Name}
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StockListUI(props: StockProps) {
|
||||||
|
const sig = useQuery<PageCorpsInfo>("/api/pages/" + props.pageName);
|
||||||
|
return (
|
||||||
|
<div class="my-2">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<ToggleButton>Kospi</ToggleButton>
|
||||||
|
<ToggleButton>Kosdaq</ToggleButton>
|
||||||
|
<ToggleButton>Otherwise</ToggleButton>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-8 py-6 flex-col">
|
||||||
|
{sig.value.type == "loading"
|
||||||
|
? (new Array(20).fill(0).map((_) => (
|
||||||
|
<div class="animate-pulse bg-gray-300 p-2"></div>
|
||||||
|
)))
|
||||||
|
: <div>
|
||||||
|
{
|
||||||
|
sig.value.type == "error" ? (<div>
|
||||||
|
<p>File Loading Failed</p>
|
||||||
|
</div>) : <StockList data={sig.value.data}></StockList>
|
||||||
|
}
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
15
main.ts
Normal file
15
main.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/// <reference no-default-lib="true" />
|
||||||
|
/// <reference lib="dom" />
|
||||||
|
/// <reference lib="dom.iterable" />
|
||||||
|
/// <reference lib="dom.asynciterable" />
|
||||||
|
/// <reference lib="deno.ns" />
|
||||||
|
|
||||||
|
import "$std/dotenv/load.ts";
|
||||||
|
|
||||||
|
import { start } from "$fresh/server.ts";
|
||||||
|
import manifest from "./fresh.gen.ts";
|
||||||
|
|
||||||
|
import twindPlugin from "$fresh/plugins/twind.ts";
|
||||||
|
import twindConfig from "./twind.config.ts";
|
||||||
|
|
||||||
|
await start(manifest, { plugins: [twindPlugin(twindConfig)] });
|
46
pages.ts
Normal file
46
pages.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { parse } from "https://deno.land/std@0.195.0/yaml/mod.ts";
|
||||||
|
import { join, fromFileUrl } from "https://deno.land/std@0.193.0/path/mod.ts";
|
||||||
|
|
||||||
|
export const PAGES_PATH = join(fromFileUrl(import.meta.url), "../pages.yaml");
|
||||||
|
|
||||||
|
export interface PageDescription {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readPagesDescription() {
|
||||||
|
const pagesText = await Deno.readTextFile(PAGES_PATH);
|
||||||
|
const pages = parse(pagesText) as PageDescription[];
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchFile(
|
||||||
|
path: string,
|
||||||
|
callback: () => void | Promise<void>,
|
||||||
|
) {
|
||||||
|
const watcherRef = Deno.watchFs(path);
|
||||||
|
(async () => {
|
||||||
|
for await (const event of watcherRef) {
|
||||||
|
if (event.kind == "modify") {
|
||||||
|
await callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
Deno.addSignalListener("SIGINT", () => {
|
||||||
|
watcherRef.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let pages_meta: PageDescription[] = [];
|
||||||
|
let mtime = 0;
|
||||||
|
export async function get_pages_meta(): Promise<[PageDescription[],number]>{
|
||||||
|
if (pages_meta) {
|
||||||
|
pages_meta = await readPagesDescription();
|
||||||
|
mtime = Date.now();
|
||||||
|
watchFile(PAGES_PATH, async () => {
|
||||||
|
pages_meta = await readPagesDescription();
|
||||||
|
mtime = Date.now();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [pages_meta, mtime];
|
||||||
|
}
|
@ -58,6 +58,6 @@
|
|||||||
- name: 240일 증가
|
- name: 240일 증가
|
||||||
description: 240일선이 증가하는 것.
|
description: 240일선이 증가하는 것.
|
||||||
- name: 볼린저 밴드 25
|
- name: 볼린저 밴드 25
|
||||||
description: '볼린저 밴드(25일선 ,표준편차 2배)의 위 밴드 값을 넘었을 때 표시. 시장 상황이 않 좋으면 평균 59개'
|
description: '볼린저 밴드(25일선 ,표준편차 2배)의 위 밴드 값을 넘었을 때 표시.'
|
||||||
- name: 양봉사이20일선
|
- name: 양봉사이20일선
|
||||||
description: Open과 Close 사이 20일 선
|
description: Open과 Close 사이 20일 선
|
||||||
|
28
routes/_404.tsx
Normal file
28
routes/_404.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
import { Head } from "$fresh/runtime.ts";
|
||||||
|
|
||||||
|
export default function Error404() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>404 - Page not found</title>
|
||||||
|
</Head>
|
||||||
|
<div class="px-4 py-8 mx-auto bg-[#86efac]">
|
||||||
|
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
|
||||||
|
<img
|
||||||
|
class="my-6"
|
||||||
|
src="/logo.svg"
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
alt="the fresh logo: a sliced lemon dripping with juice"
|
||||||
|
/>
|
||||||
|
<h1 class="text-4xl font-bold">404 - Page not found</h1>
|
||||||
|
<p class="my-4">
|
||||||
|
The page you were looking for doesn't exist.
|
||||||
|
</p>
|
||||||
|
<a href="/" class="underline">Go back home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
9
routes/_app.tsx
Normal file
9
routes/_app.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { AppProps } from "$fresh/server.ts";
|
||||||
|
|
||||||
|
export default function App({ Component }: AppProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Component />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
21
routes/api/joke.ts
Normal file
21
routes/api/joke.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { HandlerContext } from "$fresh/server.ts";
|
||||||
|
|
||||||
|
// Jokes courtesy of https://punsandoneliners.com/randomness/programmer-jokes/
|
||||||
|
const JOKES = [
|
||||||
|
"Why do Java developers often wear glasses? They can't C#.",
|
||||||
|
"A SQL query walks into a bar, goes up to two tables and says “can I join you?”",
|
||||||
|
"Wasn't hard to crack Forrest Gump's password. 1forrest1.",
|
||||||
|
"I love pressing the F5 key. It's refreshing.",
|
||||||
|
"Called IT support and a chap from Australia came to fix my network connection. I asked “Do you come from a LAN down under?”",
|
||||||
|
"There are 10 types of people in the world. Those who understand binary and those who don't.",
|
||||||
|
"Why are assembly programmers often wet? They work below C level.",
|
||||||
|
"My favourite computer based band is the Black IPs.",
|
||||||
|
"What programme do you use to predict the music tastes of former US presidential candidates? An Al Gore Rhythm.",
|
||||||
|
"An SEO expert walked into a bar, pub, inn, tavern, hostelry, public house.",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const handler = (_req: Request, _ctx: HandlerContext): Response => {
|
||||||
|
const randomIndex = Math.floor(Math.random() * JOKES.length);
|
||||||
|
const body = JOKES[randomIndex];
|
||||||
|
return new Response(body);
|
||||||
|
};
|
17
routes/api/kosdaq.ts
Normal file
17
routes/api/kosdaq.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Handlers } from "$fresh/server.ts";
|
||||||
|
import {DB} from "https://deno.land/x/sqlite/mod.ts";
|
||||||
|
|
||||||
|
export const handler: Handlers = {
|
||||||
|
async GET(req, _ctx): Promise<Response> {
|
||||||
|
const headers = new Headers({
|
||||||
|
"content-type": "application/json"
|
||||||
|
});
|
||||||
|
const db = new DB("stock.db");
|
||||||
|
const conn = db.query("SELECT Code,Name FROM KOSDAQ");
|
||||||
|
const body = conn.map(row=>({
|
||||||
|
code: row[0],
|
||||||
|
name: row[1]
|
||||||
|
}))
|
||||||
|
return new Response(JSON.stringify(body), {headers});
|
||||||
|
},
|
||||||
|
}
|
17
routes/api/kospi.ts
Normal file
17
routes/api/kospi.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Handlers } from "$fresh/server.ts";
|
||||||
|
import {DB} from "https://deno.land/x/sqlite/mod.ts";
|
||||||
|
|
||||||
|
export const handler: Handlers = {
|
||||||
|
async GET(req, _ctx): Promise<Response> {
|
||||||
|
const headers = new Headers({
|
||||||
|
"content-type": "application/json"
|
||||||
|
});
|
||||||
|
const db = new DB("stock.db");
|
||||||
|
const conn = db.query("SELECT Code,Name FROM KOSPI");
|
||||||
|
const body = conn.map(row=>({
|
||||||
|
code: row[0],
|
||||||
|
name: row[1]
|
||||||
|
}))
|
||||||
|
return new Response(JSON.stringify(body), {headers});
|
||||||
|
},
|
||||||
|
}
|
40
routes/api/pages/[name].ts
Normal file
40
routes/api/pages/[name].ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Handlers } from "$fresh/server.ts";
|
||||||
|
import { Status, STATUS_TEXT } from "https://deno.land/std@0.195.0/http/mod.ts";
|
||||||
|
import { fromFileUrl, join } from "$std/path/mod.ts";
|
||||||
|
|
||||||
|
|
||||||
|
export const handler: Handlers = {
|
||||||
|
async GET(req, ctx): Promise<Response> {
|
||||||
|
const headers = new Headers({
|
||||||
|
"content-type": "application/json"
|
||||||
|
});
|
||||||
|
const path = join(fromFileUrl(import.meta.url), "../../../../dist", `${ctx.params.name}.json`);
|
||||||
|
console.log("path : ",path)
|
||||||
|
let stat;
|
||||||
|
try {
|
||||||
|
stat = await Deno.stat(path);
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
if(err instanceof Deno.errors.NotFound){
|
||||||
|
return await ctx.renderNotFound();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mtime = stat.mtime ?? new Date(0);
|
||||||
|
const body = await Deno.readTextFile(path);
|
||||||
|
headers.set("last-modified", mtime.toUTCString());
|
||||||
|
|
||||||
|
const ifModifiedSinceValue = req.headers.get("if-modified-since");
|
||||||
|
if ( ifModifiedSinceValue &&
|
||||||
|
mtime.getTime() != new Date(ifModifiedSinceValue).getTime()
|
||||||
|
){
|
||||||
|
return new Response(null, {
|
||||||
|
status: Status.NotModified,
|
||||||
|
statusText: STATUS_TEXT[Status.NotModified]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return new Response(body, {headers});
|
||||||
|
},
|
||||||
|
};
|
24
routes/api/pages/index.ts
Normal file
24
routes/api/pages/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Handlers } from "$fresh/server.ts";
|
||||||
|
import { get_pages_meta } from "../../../pages.ts";
|
||||||
|
import { Status, STATUS_TEXT } from "https://deno.land/std@0.195.0/http/mod.ts";
|
||||||
|
|
||||||
|
export const handler: Handlers = {
|
||||||
|
async GET(req, _ctx): Promise<Response> {
|
||||||
|
const headers = new Headers({
|
||||||
|
"content-type": "application/json"
|
||||||
|
});
|
||||||
|
const [body, mtime] = await get_pages_meta();
|
||||||
|
headers.set("last-modified", new Date(mtime).toUTCString());
|
||||||
|
console.log("aaa");
|
||||||
|
const ifModifiedSinceValue = req.headers.get("if-modified-since");
|
||||||
|
if ( ifModifiedSinceValue &&
|
||||||
|
mtime != new Date(ifModifiedSinceValue).getTime()
|
||||||
|
){
|
||||||
|
return new Response(null, {
|
||||||
|
status: Status.NotModified,
|
||||||
|
statusText: STATUS_TEXT[Status.NotModified]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return new Response(JSON.stringify(body), {headers});
|
||||||
|
},
|
||||||
|
};
|
5
routes/greet/[name].tsx
Normal file
5
routes/greet/[name].tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { PageProps } from "$fresh/server.ts";
|
||||||
|
|
||||||
|
export default function Greet(props: PageProps) {
|
||||||
|
return <div>Hello {props.params.name}</div>;
|
||||||
|
}
|
48
routes/index.tsx
Normal file
48
routes/index.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Head } from "$fresh/runtime.ts";
|
||||||
|
import { useSignal } from "@preact/signals";
|
||||||
|
import {Button} from "../components/Button.tsx";
|
||||||
|
import { PageDescription, get_pages_meta } from "../pages.ts";
|
||||||
|
import { Handlers, PageProps } from "$fresh/server.ts";
|
||||||
|
|
||||||
|
export const handler: Handlers = {
|
||||||
|
async GET(_req, ctx){
|
||||||
|
const [pages,_] = await get_pages_meta();
|
||||||
|
return await ctx.render(pages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Home({data}: PageProps<PageDescription[]>) {
|
||||||
|
const count = useSignal(3);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>stock-front</title>
|
||||||
|
</Head>
|
||||||
|
<div class="px-4 py-8 mx-auto bg-[#86efac]">
|
||||||
|
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
|
||||||
|
<img
|
||||||
|
class="my-6"
|
||||||
|
src="/logo.svg"
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
alt="the fresh logo: a sliced lemon dripping with juice"
|
||||||
|
/>
|
||||||
|
<h1 class="text-4xl font-bold">Stock</h1>
|
||||||
|
<div class="my-4">
|
||||||
|
<ul>
|
||||||
|
{
|
||||||
|
data.map(x=><li class="my-2">
|
||||||
|
<a class="p-2 block hover:bg-gray-300 bg-white rounded" href={`/pages/${x.name}`}>
|
||||||
|
{x.name}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
25
routes/pages/[name].tsx
Normal file
25
routes/pages/[name].tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { PageProps } from "$fresh/server.ts";
|
||||||
|
import { Head } from "$fresh/runtime.ts";
|
||||||
|
import StockList from "../../islands/StockList.tsx";
|
||||||
|
|
||||||
|
|
||||||
|
export default function Pages(props: PageProps) {
|
||||||
|
return <>
|
||||||
|
<Head>
|
||||||
|
<title>Stock: {props.params.name}</title>
|
||||||
|
</Head>
|
||||||
|
<div class="px-4 py-8 mx-auto bg-[#86efac]">
|
||||||
|
<div class="max-w-screen-md mx-auto flex flex-col items-center justify-center">
|
||||||
|
<img
|
||||||
|
class="my-6"
|
||||||
|
src="/stockgraph.svg"
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
alt="stock graph"
|
||||||
|
/>
|
||||||
|
<h1>{props.params.name}</h1>
|
||||||
|
<StockList pageName={props.params.name}></StockList>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
BIN
static/favicon.ico
Normal file
BIN
static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
6
static/logo.svg
Normal file
6
static/logo.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg width="40" height="40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M34.092 8.845C38.929 20.652 34.092 27 30 30.5c1 3.5-2.986 4.222-4.5 2.5-4.457 1.537-13.512 1.487-20-5C2 24.5 4.73 16.714 14 11.5c8-4.5 16-7 20.092-2.655Z" fill="#FFDB1E"/>
|
||||||
|
<path d="M14 11.5c6.848-4.497 15.025-6.38 18.368-3.47C37.5 12.5 21.5 22.612 15.5 25c-6.5 2.587-3 8.5-6.5 8.5-3 0-2.5-4-5.183-7.75C2.232 23.535 6.16 16.648 14 11.5Z" fill="#fff" stroke="#FFDB1E"/>
|
||||||
|
<path d="M28.535 8.772c4.645 1.25-.365 5.695-4.303 8.536-3.732 2.692-6.606 4.21-7.923 4.83-.366.173-1.617-2.252-1.617-1 0 .417-.7 2.238-.934 2.326-1.365.512-4.223 1.29-5.835 1.29-3.491 0-1.923-4.754 3.014-9.122.892-.789 1.478-.645 2.283-.645-.537-.773-.534-.917.403-1.546C17.79 10.64 23 8.77 25.212 8.42c.366.014.82.35.82.629.41-.14 2.095-.388 2.503-.278Z" fill="#FFE600"/>
|
||||||
|
<path d="M14.297 16.49c.985-.747 1.644-1.01 2.099-2.526.566.121.841-.08 1.29-.701.324.466 1.657.608 2.453.701-.715.451-1.057.852-1.452 2.106-1.464-.611-3.167-.302-4.39.42Z" fill="#fff"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
13
static/stockgraph.svg
Normal file
13
static/stockgraph.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
// 16pxls (c) by Paul mackenzie <paul@whatspauldoing.com>
|
||||||
|
//
|
||||||
|
// 16pxls is licensed under a
|
||||||
|
// Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the license along with this
|
||||||
|
// work. If not, see <http://creativecommons.org/licenses/by-sa/4.0/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0 14h16v2H0v-2zm8.5-8l4-4H11V0h5v5h-2V3.5L9.5 8l-1 1-2-2-5 5L0 10.5 6.5 4 8 5.5l.5.5z" fill-rule="evenodd"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 590 B |
7
tailwind.config.js
Normal file
7
tailwind.config.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default {
|
||||||
|
content: ["./**/*.{html,tsx}"],
|
||||||
|
theme:{
|
||||||
|
extend:{},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
5
twind.config.ts
Normal file
5
twind.config.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Options } from "$fresh/plugins/twind.ts";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
selfURL: import.meta.url,
|
||||||
|
} as Options;
|
Loading…
Reference in New Issue
Block a user