diff --git a/src/main/scala/db/DBService.scala b/src/main/scala/db/DBService.scala index 2ca4736..56cf8f3 100644 --- a/src/main/scala/db/DBService.scala +++ b/src/main/scala/db/DBService.scala @@ -74,4 +74,12 @@ object DBService { val dateStr: String = date.format(formatter) readFileNames(dataPath).map(_.filter(_.startsWith(dateStr)).sorted) } + + def getFileContent(fileName: String): IO[String] = { + val file = new File(dataPath, fileName) + val sourceResource = Resource.fromAutoCloseable(IO(Source.fromFile(file))) + sourceResource.use(source => IO(source.getLines().mkString("
\n"))).handleErrorWith { error => + IO(println(s"Failed to read file: $error")) *> IO.pure("") + } + } } diff --git a/src/main/scala/server/Server.scala b/src/main/scala/server/Server.scala index eb51deb..a365325 100644 --- a/src/main/scala/server/Server.scala +++ b/src/main/scala/server/Server.scala @@ -71,6 +71,10 @@ object Server { Ok(fileNames.asJson.pretty) ) + // http://0.0.0.0:8080/api/show/file/20230423_12:30.csv + case GET -> Root / "show" / "file" / (fileName: String) => + DBService.getFileContent(fileName).flatMap(Ok(_)) + // http://0.0.0.0:8080/api/help case GET -> Root / "help" => { val host = "weather-tool.fly.dev" diff --git a/web/src/App.tsx b/web/src/App.tsx index bab8dcf..00cd43c 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -2,7 +2,7 @@ import type { Component } from 'solid-js'; import logo from './assets/logo.svg'; import styles from './css/App.module.css'; -import { FetchedDates } from './FetchedDates'; +import { FileManager } from './fileManager/FileManager'; console.log("env:", import.meta.env.MODE); console.log("api host:", import.meta.env.VITE_API_HOST); @@ -10,8 +10,8 @@ console.log("api host:", import.meta.env.VITE_API_HOST); const App: Component = () => { return (
+
- logo

Weather tool web diff --git a/web/src/FetchedDates.tsx b/web/src/FetchedDates.tsx deleted file mode 100644 index 6be6a55..0000000 --- a/web/src/FetchedDates.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { createResource } from "solid-js"; - -const apiHost = import.meta.env.VITE_API_HOST; - -export function FetchedDates() { - const fetchDates = async () => { - const response = await fetch(`${apiHost}/api/show/all_dates`); - const json = await response.json(); - return json; - } - - const [datesResource] = createResource(fetchDates); - - return ( -

- { datesResource.loading && ( -
Loading dates
- )} - { datesResource.error && ( -
Error while loading dates: ${datesResource.error}
- )} - { datesResource() && ( -
    - { (datesResource() as any).map((date: any) => item(date))} -
- )} -
- ); -} - -function item(date: any) { - return ( -
  • {date}
  • - ) -} \ No newline at end of file diff --git a/web/src/consts.ts b/web/src/consts.ts new file mode 100644 index 0000000..239a460 --- /dev/null +++ b/web/src/consts.ts @@ -0,0 +1 @@ +export const apiHost = import.meta.env.VITE_API_HOST; diff --git a/web/src/css/FileManager.css b/web/src/css/FileManager.css new file mode 100644 index 0000000..95c46b9 --- /dev/null +++ b/web/src/css/FileManager.css @@ -0,0 +1,22 @@ +.container { + display: flex; + height: 100vh; /* Adjust to desired height */ + } + + .column { + flex: 1; + padding: 10px; + box-sizing: border-box; + text-align: left; + } + + .column:nth-child(1), + .column:nth-child(2) { + flex-basis: 15%; + background-color: #f2f2f2; + } + + .column:nth-child(3) { + flex-basis: 70%; + background-color: #e6e6e6; + } \ No newline at end of file diff --git a/web/src/css/spinner.css b/web/src/css/spinner.css new file mode 100644 index 0000000..06725d2 --- /dev/null +++ b/web/src/css/spinner.css @@ -0,0 +1,24 @@ +.spinner { + display: inline-block; + width: 16px; + height: 16px; + } + .spinner:after { + content: " "; + display: block; + width: 14px; + height: 14px; + margin: 0px; + border-radius: 50%; + border: 6px solid #333; + border-color: #333 transparent #333 transparent; + animation: spinner 1.2s linear infinite; + } + @keyframes spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } \ No newline at end of file diff --git a/web/src/fileManager/DateList.tsx b/web/src/fileManager/DateList.tsx new file mode 100644 index 0000000..fa74f46 --- /dev/null +++ b/web/src/fileManager/DateList.tsx @@ -0,0 +1,41 @@ +import { Component, createResource, Setter } from "solid-js"; +import { apiHost } from "../consts"; + +import "../css/spinner.css"; + +export const DateList: Component<{setDate: Setter;}> = (props) => { + const fetchDates = async () => { + await new Promise(resolve => setTimeout(resolve, 500)) + const response = await fetch(`${apiHost}/api/show/all_dates`); + const json = await response.json(); + return json; + } + + const [datesResource] = createResource(fetchDates); + + const clickDate = (date: string) => { + console.log("setDate", date); + props.setDate(date); + } + + return ( +
    + { datesResource.loading && ( +
    + + Loading dates +
    + )} + { datesResource.error && ( +
    Error while loading dates: ${datesResource.error}
    + )} + { datesResource() && ( +
      + { (datesResource() as any).map((date: any) => +
    • clickDate(date)}>{date}
    • + )} +
    + )} +
    + ); +} diff --git a/web/src/fileManager/FileContent.tsx b/web/src/fileManager/FileContent.tsx new file mode 100644 index 0000000..210fc5f --- /dev/null +++ b/web/src/fileManager/FileContent.tsx @@ -0,0 +1,38 @@ +import { Accessor, Component, createResource, JSXElement } from "solid-js"; +import { apiHost } from "../consts"; + +export const FileContent: Component<{getFileName: Accessor}> = (props) => { + const fetchFileContent = async (fileName: string) => { + if (fileName === "") return; + await new Promise(resolve => setTimeout(resolve, 500)) + const response = await fetch(`${apiHost}/api/show/file/${fileName}`); + const text = await response.text(); + return text; + } + + const [contentResource] = createResource(props.getFileName, fetchFileContent); + + return ( +
    + { contentResource.loading && ( +
    + + load content +
    + )} + { contentResource.error && ( +
    Error while loading file content: ${contentResource.error}
    + )} + { contentResource() && ( +
    {splitLines(contentResource())}
    + )} +
    + ) +} + +function splitLines(str?: string): JSXElement { + const lines = str?.split("
    ") ?? ["empty..."] + return lines.map(line => +
    {line}
    + ); +} \ No newline at end of file diff --git a/web/src/fileManager/FileManager.tsx b/web/src/fileManager/FileManager.tsx new file mode 100644 index 0000000..81b490d --- /dev/null +++ b/web/src/fileManager/FileManager.tsx @@ -0,0 +1,29 @@ +import { createSignal } from "solid-js"; +import "../css/FileManager.css" +import { DateList } from "./DateList"; +import { FileContent } from "./FileContent"; +import { FileNameList } from "./FileNameList"; + +export function FileManager() { + const [getDate, setDate] = createSignal(""); + const [getFileName, setFileName] = createSignal(""); + + return ( +
    +

    File manager

    +
    +
    + +
    +
    +

    {getDate()}

    + +
    +
    +

    {getFileName()}

    + +
    +
    +
    + ); +} \ No newline at end of file diff --git a/web/src/fileManager/FileNameList.tsx b/web/src/fileManager/FileNameList.tsx new file mode 100644 index 0000000..42b5c23 --- /dev/null +++ b/web/src/fileManager/FileNameList.tsx @@ -0,0 +1,46 @@ +import { Accessor, Component, createResource, Setter } from "solid-js"; +import { apiHost } from "../consts"; + +import "../css/spinner.css"; + +export const FileNameList: Component<{ + getDate: Accessor, + setFileName: Setter, +}> = (props) => { + const fetchFileNames = async (date: string) => { + if (props.getDate() === "") return; + await new Promise(resolve => setTimeout(resolve, 500)) + const response = await fetch(`${apiHost}/api/show/date/${date.replace(/-/g, "")}`); + const json = await response.json(); + return json; + } + + const [fileNameResource] = createResource(props.getDate, fetchFileNames); + + function clickFileName(fileName: string) { + console.log(fileName); + props.setFileName(fileName); + } + + return ( +
    + { fileNameResource.loading && ( +
    + + loading {props.getDate()} +
    + )} + { fileNameResource.error && ( +
    Error while loading file names: ${fileNameResource.error}
    + )} + { fileNameResource() && ( +
      + { (fileNameResource() as any).map((fileName: any) => +
    • clickFileName(fileName)}>{fileName}
    • + )} +
    + )} +
    + ) + return
    yoyoyoy
    +} \ No newline at end of file