File manager frontend
This commit is contained in:
@@ -74,4 +74,12 @@ object DBService {
|
|||||||
val dateStr: String = date.format(formatter)
|
val dateStr: String = date.format(formatter)
|
||||||
readFileNames(dataPath).map(_.filter(_.startsWith(dateStr)).sorted)
|
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("<br/>\n"))).handleErrorWith { error =>
|
||||||
|
IO(println(s"Failed to read file: $error")) *> IO.pure("")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ object Server {
|
|||||||
Ok(fileNames.asJson.pretty)
|
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
|
// http://0.0.0.0:8080/api/help
|
||||||
case GET -> Root / "help" => {
|
case GET -> Root / "help" => {
|
||||||
val host = "weather-tool.fly.dev"
|
val host = "weather-tool.fly.dev"
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@ import type { Component } from 'solid-js';
|
|||||||
|
|
||||||
import logo from './assets/logo.svg';
|
import logo from './assets/logo.svg';
|
||||||
import styles from './css/App.module.css';
|
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("env:", import.meta.env.MODE);
|
||||||
console.log("api host:", import.meta.env.VITE_API_HOST);
|
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 = () => {
|
const App: Component = () => {
|
||||||
return (
|
return (
|
||||||
<div class={styles.App}>
|
<div class={styles.App}>
|
||||||
|
<FileManager />
|
||||||
<header class={styles.header}>
|
<header class={styles.header}>
|
||||||
<FetchedDates />
|
|
||||||
<img src={logo} class={styles.logo} alt="logo" />
|
<img src={logo} class={styles.logo} alt="logo" />
|
||||||
<p>
|
<p>
|
||||||
Weather tool web
|
Weather tool web
|
||||||
|
|||||||
@@ -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 (
|
|
||||||
<div>
|
|
||||||
{ datesResource.loading && (
|
|
||||||
<div>Loading dates</div>
|
|
||||||
)}
|
|
||||||
{ datesResource.error && (
|
|
||||||
<div>Error while loading dates: ${datesResource.error}</div>
|
|
||||||
)}
|
|
||||||
{ datesResource() && (
|
|
||||||
<ul>
|
|
||||||
{ (datesResource() as any).map((date: any) => item(date))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function item(date: any) {
|
|
||||||
return (
|
|
||||||
<li>{date}</li>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export const apiHost = import.meta.env.VITE_API_HOST;
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<string>;}> = (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 (
|
||||||
|
<div>
|
||||||
|
{ datesResource.loading && (
|
||||||
|
<div>
|
||||||
|
<span class="spinner"></span>
|
||||||
|
<span style={{ "padding-left": "16px" }}>Loading dates</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{ datesResource.error && (
|
||||||
|
<div>Error while loading dates: ${datesResource.error}</div>
|
||||||
|
)}
|
||||||
|
{ datesResource() && (
|
||||||
|
<ul>
|
||||||
|
{ (datesResource() as any).map((date: any) =>
|
||||||
|
<li onClick={() => clickDate(date)}>{date}</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { Accessor, Component, createResource, JSXElement } from "solid-js";
|
||||||
|
import { apiHost } from "../consts";
|
||||||
|
|
||||||
|
export const FileContent: Component<{getFileName: Accessor<string>}> = (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 (
|
||||||
|
<div>
|
||||||
|
{ contentResource.loading && (
|
||||||
|
<div>
|
||||||
|
<span class="spinner"></span>
|
||||||
|
<span style={{ "padding-left": "16px" }}>load content</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{ contentResource.error && (
|
||||||
|
<div>Error while loading file content: ${contentResource.error}</div>
|
||||||
|
)}
|
||||||
|
{ contentResource() && (
|
||||||
|
<div>{splitLines(contentResource())}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitLines(str?: string): JSXElement {
|
||||||
|
const lines = str?.split("<br/>") ?? ["empty..."]
|
||||||
|
return lines.map(line =>
|
||||||
|
<div>{line}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<div class="fileManager">
|
||||||
|
<h2>File manager</h2>
|
||||||
|
<div class="container">
|
||||||
|
<div class="column">
|
||||||
|
<DateList setDate={setDate} />
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<h3>{getDate()}</h3>
|
||||||
|
<FileNameList getDate={getDate} setFileName={setFileName} />
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<h3>{getFileName()}</h3>
|
||||||
|
<FileContent getFileName={getFileName} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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<string>,
|
||||||
|
setFileName: Setter<string>,
|
||||||
|
}> = (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 (
|
||||||
|
<div>
|
||||||
|
{ fileNameResource.loading && (
|
||||||
|
<div>
|
||||||
|
<span class="spinner"></span>
|
||||||
|
<span style={{ "padding-left": "16px" }}>loading {props.getDate()}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{ fileNameResource.error && (
|
||||||
|
<div>Error while loading file names: ${fileNameResource.error}</div>
|
||||||
|
)}
|
||||||
|
{ fileNameResource() && (
|
||||||
|
<ul>
|
||||||
|
{ (fileNameResource() as any).map((fileName: any) =>
|
||||||
|
<li onClick={() => clickFileName(fileName)}>{fileName}</li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
return <div>yoyoyoy</div>
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user