Merge branch 'develop' into dr/settings-page

This commit is contained in:
0x70b1a5 2024-04-30 15:34:08 -04:00 committed by GitHub
commit b9688f5daa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
53 changed files with 3145 additions and 613 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -15,8 +15,8 @@
<meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<link href='https://fonts.googleapis.com/css?family=Montserrat' rel='stylesheet'>
<script type="module" crossorigin src="/main:app_store:sys/assets/index-Lb8jbJl3.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-szfTR2t7.css">
<script type="module" crossorigin src="/main:app_store:sys/assets/index-oEHGQ8XY.js"></script>
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-P4oc20OK.css">
</head>
<body>

View File

@ -3,6 +3,7 @@ import { AppInfo } from "../types/Apps";
import { appId } from "../utils/app";
import { useNavigate } from "react-router-dom";
import classNames from "classnames";
import { FaCircleQuestion } from "react-icons/fa6";
interface AppHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
app: AppInfo;
@ -22,14 +23,11 @@ export default function AppHeader({
className={classNames('flex w-full justify-content-start', size, props.className, { 'cursor-pointer': size !== 'large' })}
onClick={() => navigate(`/app-details/${appId(app)}`)}
>
<img
src={
app.metadata?.image ||
"https://png.pngtree.com/png-vector/20190215/ourmid/pngtree-vector-question-mark-icon-png-image_515448.jpg"
}
{app.metadata?.image && <img
src={app.metadata.image}
alt="app icon"
className={classNames('mr-2', { 'h-32 rounded-md': size === 'large', 'h-12 rounded': size !== 'large' })}
/>
/>}
<div className="flex flex-col w-full">
<div
className={classNames("whitespace-nowrap overflow-hidden text-ellipsis", { 'text-3xl': size === 'large', })}

View File

@ -10,6 +10,7 @@ import {
import { MY_APPS_PATH } from "../constants/path";
import classNames from "classnames";
import { FaHome } from "react-icons/fa";
interface SearchHeaderProps {
value?: string;
@ -17,6 +18,7 @@ interface SearchHeaderProps {
onBack?: () => void;
onlyMyApps?: boolean;
hideSearch?: boolean;
hidePublish?: boolean;
}
export default function SearchHeader({
@ -24,6 +26,7 @@ export default function SearchHeader({
onChange = () => null,
onBack,
hideSearch = false,
hidePublish = false,
}: SearchHeaderProps) {
const navigate = useNavigate();
const location = useLocation();
@ -35,7 +38,7 @@ export default function SearchHeader({
return (
<div className="flex justify-between">
{location.pathname !== '/' ? (
<button className="flex flex-col c mr-1 icon" onClick={() => {
<button className="flex flex-col c mr-2 icon" onClick={() => {
if (onBack) {
onBack()
} else {
@ -46,16 +49,22 @@ export default function SearchHeader({
</button>
) : (
<button
className="flex flex-col c mr-1 alt"
onClick={() => navigate("/publish")}
className="flex flex-col c mr-2 icon"
onClick={() => window.location.href = '/'}
>
<FaUpload />
<FaHome />
</button>
)}
{!hidePublish && <button
className="flex flex-col c mr-2 icon"
onClick={() => navigate("/publish")}
>
<FaUpload />
</button>}
{!hideSearch && (
<div className="flex mx-2 flex-1 rounded-md">
<div className="flex mr-2 flex-1 rounded-md">
<button
className="icon"
className="icon mr-2"
type="button"
onClick={() => inputRef.current?.focus()}
>
@ -67,10 +76,10 @@ export default function SearchHeader({
onChange={(event) => onChange(event.target.value)}
value={value}
placeholder="Search for apps..."
className="w-full ml-2"
className="w-full mr-2"
/>
{value.length > 0 && <button
className="icon ml-2"
className="icon"
onClick={() => onChange("")}
>
<FaX />
@ -79,11 +88,11 @@ export default function SearchHeader({
)}
<div className="flex">
<button
className={classNames("flex ml-1 alt")}
className={classNames("flex alt")}
onClick={() => (isMyAppsPage ? navigate(-1) : navigate(MY_APPS_PATH))}
>
<FaDownload className="mr-1" />
My Apps
<FaDownload className="mr-2" />
<span>My Apps</span>
</button>
</div>
</div>

File diff suppressed because one or more lines are too long

View File

@ -209,7 +209,11 @@ export default function PublishPage({
return (
<div className="max-w-[900px] w-full">
<SearchHeader hideSearch onBack={showMetadataForm ? () => setShowMetadataForm(false) : undefined} />
<SearchHeader
hideSearch
hidePublish
onBack={showMetadataForm ? () => setShowMetadataForm(false) : undefined}
/>
<div className="flex justify-between items-center my-2">
<h4>Publish Package</h4>
{Boolean(account) && <div className="card flex items-center">

View File

@ -1,12 +0,0 @@
<svg width="779" height="514" viewBox="0 0 779 514" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
@media (prefers-color-scheme: dark) {
svg { fill: white; }
}
@media (prefers-color-scheme: light) {
svg { fill: black; }
}
</style>
<path d="M753.092 5.91932C756.557 5.09976 755.962 -0.00012207 752.401 -0.00012207H426.001C424.755 -0.00012207 423.639 0.77027 423.197 1.93535L236.968 492.6C235.729 495.865 240.123 498.255 242.191 495.441L569.357 50.1132C569.778 49.5392 570.391 49.1339 571.084 48.97L753.092 5.91932Z"/>
<path d="M11.9665 40.2288C9.10949 38.777 10.2135 34.4583 13.4167 34.5557L404.273 46.4367C406.334 46.4993 407.719 48.5749 406.986 50.5023L347.438 206.981C346.804 208.647 344.865 209.396 343.275 208.588L11.9665 40.2288Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 826 B

File diff suppressed because one or more lines are too long

View File

@ -2,8 +2,8 @@
use kinode_process_lib::{
await_message, call_init,
http::{
bind_http_path, bind_http_static_path, send_response, HttpServerError, HttpServerRequest,
StatusCode,
bind_http_path, bind_http_static_path, send_response, serve_ui, HttpServerError,
HttpServerRequest, StatusCode,
},
println, Address, Message,
};
@ -38,131 +38,11 @@ wit_bindgen::generate!({
world: "process",
});
const HOME_PAGE: &str = include_str!("index.html");
const APP_TEMPLATE: &str = r#"
<a class="app-link" id="${package_name}" href="/${path}">
<img
src="${base64_icon}" />
<h6>${label}</h6>
</a>"#;
/// bind to root path on http_server (we have special dispensation to do so!)
fn bind_index(our: &str, apps: &BTreeMap<String, String>) {
bind_http_static_path(
"/",
true,
false,
Some("text/html".to_string()),
HOME_PAGE
.replace("${our}", our)
.replace(
"${apps}",
&apps
.values()
.map(String::as_str)
.collect::<Vec<&str>>()
.join("\n"),
)
.to_string()
.as_bytes()
.to_vec(),
)
.expect("failed to bind to /");
}
// // Copied in from process_lib serve_ui. see https://github.com/kinode-dao/process_lib/blob/main/src/http.rs
// fn static_serve_dir(
// our: &Address,
// directory: &str,
// authenticated: bool,
// local_only: bool,
// paths: Vec<&str>,
// ) -> anyhow::Result<()> {
// serve_index_html(our, directory, authenticated, local_only, paths)?;
// let initial_path = format!("{}/pkg/{}", our.package_id(), directory);
// println!("initial path: {}", initial_path);
// let mut queue = VecDeque::new();
// queue.push_back(initial_path.clone());
// while let Some(path) = queue.pop_front() {
// let Ok(directory_response) = KiRequest::to(("our", "vfs", "distro", "sys"))
// .body(serde_json::to_vec(&VfsRequest {
// path,
// action: VfsAction::ReadDir,
// })?)
// .send_and_await_response(5)?
// else {
// return Err(anyhow::anyhow!(
// "serve_ui: no response for path: {}",
// initial_path
// ));
// };
// let directory_body = serde_json::from_slice::<VfsResponse>(directory_response.body())?;
// // Determine if it's a file or a directory and handle appropriately
// match directory_body {
// VfsResponse::ReadDir(directory_info) => {
// for entry in directory_info {
// match entry.file_type {
// // If it's a file, serve it statically
// FileType::File => {
// KiRequest::to(("our", "vfs", "distro", "sys"))
// .body(serde_json::to_vec(&VfsRequest {
// path: entry.path.clone(),
// action: VfsAction::Read,
// })?)
// .send_and_await_response(5)??;
// let Some(blob) = get_blob() else {
// return Err(anyhow::anyhow!(
// "serve_ui: no blob for {}",
// entry.path
// ));
// };
// let content_type = get_mime_type(&entry.path);
// println!("binding {}", entry.path.replace(&initial_path, ""));
// bind_http_static_path(
// entry.path.replace(&initial_path, ""),
// authenticated, // Must be authenticated
// local_only, // Is not local-only
// Some(content_type),
// blob.bytes,
// )?;
// }
// FileType::Directory => {
// // Push the directory onto the queue
// queue.push_back(entry.path);
// }
// _ => {}
// }
// }
// }
// _ => {
// return Err(anyhow::anyhow!(
// "serve_ui: unexpected response for path: {:?}",
// directory_body
// ))
// }
// };
// }
// Ok(())
// }
call_init!(init);
fn init(our: Address) {
let mut apps: BTreeMap<String, String> = BTreeMap::new();
let mut app_data: BTreeMap<String, HomepageApp> = BTreeMap::new();
let mut app_data: BTreeMap<ProcessId, HomepageApp> = BTreeMap::new();
// static_serve_dir(&our, "index.html", true, false, vec!["/"]);
bind_index(&our.node, &apps);
serve_ui(&our, "ui", true, false, vec!["/"]).expect("failed to serve ui");
bind_http_static_path(
"/our",
@ -209,38 +89,20 @@ fn init(our: Address) {
message.source().process.to_string(),
HomepageApp {
package_name: message.source().clone().package().to_string(),
path: path.clone(),
path: format!(
"/{}:{}:{}/{}",
message.source().clone().process().to_string(),
message.source().clone().package().to_string(),
message.source().clone().publisher().to_string(),
path.strip_prefix('/').unwrap_or(&path)
),
label: label.clone(),
base64_icon: icon.clone(),
},
);
apps.insert(
message.source().process.to_string(),
APP_TEMPLATE
.replace(
"${package_name}",
&format!(
"{}:{}",
message.source().package(),
message.source().publisher()
),
)
.replace(
"${path}",
&format!(
"{}/{}",
message.source().process,
path.strip_prefix('/').unwrap_or(&path)
),
)
.replace("${label}", &label)
.replace("${base64_icon}", &icon),
);
bind_index(&our.node, &apps);
}
HomepageRequest::Remove => {
apps.remove(&message.source().process.to_string());
bind_index(&our.node, &apps);
app_data.remove(&message.source().process);
}
}
} else if let Ok(request) = serde_json::from_slice::<HttpServerRequest>(message.body())
@ -248,7 +110,6 @@ fn init(our: Address) {
match request {
HttpServerRequest::Http(incoming) => {
let path = incoming.bound_path(None);
println!("on path: {}", path);
if path == "/apps" {
send_response(
StatusCode::OK,
@ -256,20 +117,24 @@ fn init(our: Address) {
"Content-Type".to_string(),
"application/json".to_string(),
)])),
app_data
.values()
.map(|app| serde_json::to_string(app).unwrap())
.collect::<Vec<String>>()
.join("\n")
.as_bytes()
.to_vec(),
format!(
"[{}]",
app_data
.values()
.map(|app| serde_json::to_string(app).unwrap())
.collect::<Vec<String>>()
.join(",")
)
.as_bytes()
.to_vec(),
);
} else {
send_response(
StatusCode::OK,
Some(HashMap::new()),
"yes hello".as_bytes().to_vec(),
);
}
send_response(
StatusCode::OK,
Some(std::collections::HashMap::new()),
"hello".as_bytes().to_vec(),
);
}
_ => {}
}

View File

@ -5,7 +5,8 @@
"on_exit": "Restart",
"request_networking": false,
"request_capabilities": [
"http_server:distro:sys"
"http_server:distro:sys",
"vfs:distro:sys"
],
"grant_capabilities": [
"http_server:distro:sys"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<!-- TODO our in title <title>${our} - Home</title> -->
<meta charset="utf-8" />
<link rel="icon"
href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzc5IiBoZWlnaHQ9IjUxNCIgdmlld0JveD0iMCAwIDc3OSA1MTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+CiAgICAgICAgQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewogICAgICAgICAgICBzdmcgeyBmaWxsOiB3aGl0ZTsgfQogICAgICAgIH0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBsaWdodCkgewogICAgICAgICAgICBzdmcgeyBmaWxsOiBibGFjazsgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8cGF0aCBkPSJNNzUzLjA5MiA1LjkxOTMyQzc1Ni41NTcgNS4wOTk3NiA3NTUuOTYyIC0wLjAwMDEyMjA3IDc1Mi40MDEgLTAuMDAwMTIyMDdINDI2LjAwMUM0MjQuNzU1IC0wLjAwMDEyMjA3IDQyMy42MzkgMC43NzAyNyA0MjMuMTk3IDEuOTM1MzVMMjM2Ljk2OCA0OTIuNkMyMzUuNzI5IDQ5NS44NjUgMjQwLjEyMyA0OTguMjU1IDI0Mi4xOTEgNDk1LjQ0MUw1NjkuMzU3IDUwLjExMzJDNTY5Ljc3OCA0OS41MzkyIDU3MC4zOTEgNDkuMTMzOSA1NzEuMDg0IDQ4Ljk3TDc1My4wOTIgNS45MTkzMloiLz4KICAgIDxwYXRoIGQ9Ik0xMS45NjY1IDQwLjIyODhDOS4xMDk0OSAzOC43NzcgMTAuMjEzNSAzNC40NTgzIDEzLjQxNjcgMzQuNTU1N0w0MDQuMjczIDQ2LjQzNjdDNDA2LjMzNCA0Ni40OTkzIDQwNy43MTkgNDguNTc0OSA0MDYuOTg2IDUwLjUwMjNMMzQ3LjQzOCAyMDYuOTgxQzM0Ni44MDQgMjA4LjY0NyAzNDQuODY1IDIwOS4zOTYgMzQzLjI3NSAyMDguNTg4TDExLjk2NjUgNDAuMjI4OFoiLz4KPC9zdmc+Cg==">
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/assets/index-jKxRCta5.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BIXROKBk.css">
</head>
<body>
<div id="root"></div>
<script src="/our.js"></script>
</body>
</html>

View File

@ -0,0 +1,4 @@
<svg width="779" height="514" viewBox="0 0 779 514" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M753.092 5.91932C756.557 5.09976 755.962 -0.00012207 752.401 -0.00012207H426.001C424.755 -0.00012207 423.639 0.77027 423.197 1.93535L236.968 492.6C235.729 495.865 240.123 498.255 242.191 495.441L569.357 50.1132C569.778 49.5392 570.391 49.1339 571.084 48.97L753.092 5.91932Z" fill="#FFF5D9"/>
<path d="M11.9665 40.2288C9.10949 38.777 10.2135 34.4583 13.4167 34.5557L404.273 46.4367C406.334 46.4993 407.719 48.5749 406.986 50.5023L347.438 206.981C346.804 208.647 344.865 209.396 343.275 208.588L11.9665 40.2288Z" fill="#FFF5D9"/>
</svg>

After

Width:  |  Height:  |  Size: 644 B

View File

@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}

24
kinode/packages/homepage/ui/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,3 @@
# Kinode Homepage UI
The homepage for Kinode.

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<!-- TODO our in title <title>${our} - Home</title> -->
<meta charset="utf-8" />
<link rel="icon"
href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzc5IiBoZWlnaHQ9IjUxNCIgdmlld0JveD0iMCAwIDc3OSA1MTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+CiAgICAgICAgQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewogICAgICAgICAgICBzdmcgeyBmaWxsOiB3aGl0ZTsgfQogICAgICAgIH0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBsaWdodCkgewogICAgICAgICAgICBzdmcgeyBmaWxsOiBibGFjazsgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8cGF0aCBkPSJNNzUzLjA5MiA1LjkxOTMyQzc1Ni41NTcgNS4wOTk3NiA3NTUuOTYyIC0wLjAwMDEyMjA3IDc1Mi40MDEgLTAuMDAwMTIyMDdINDI2LjAwMUM0MjQuNzU1IC0wLjAwMDEyMjA3IDQyMy42MzkgMC43NzAyNyA0MjMuMTk3IDEuOTM1MzVMMjM2Ljk2OCA0OTIuNkMyMzUuNzI5IDQ5NS44NjUgMjQwLjEyMyA0OTguMjU1IDI0Mi4xOTEgNDk1LjQ0MUw1NjkuMzU3IDUwLjExMzJDNTY5Ljc3OCA0OS41MzkyIDU3MC4zOTEgNDkuMTMzOSA1NzEuMDg0IDQ4Ljk3TDc1My4wOTIgNS45MTkzMloiLz4KICAgIDxwYXRoIGQ9Ik0xMS45NjY1IDQwLjIyODhDOS4xMDk0OSAzOC43NzcgMTAuMjEzNSAzNC40NTgzIDEzLjQxNjcgMzQuNTU1N0w0MDQuMjczIDQ2LjQzNjdDNDA2LjMzNCA0Ni40OTkzIDQwNy43MTkgNDguNTc0OSA0MDYuOTg2IDUwLjUwMjNMMzQ3LjQzOCAyMDYuOTgxQzM0Ni44MDQgMjA4LjY0NyAzNDQuODY1IDIwOS4zOTYgMzQzLjI3NSAyMDguNTg4TDExLjk2NjUgNDAuMjI4OFoiLz4KPC9zdmc+Cg==">
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
<script type="module" crossorigin src="/assets/index-jKxRCta5.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BIXROKBk.css">
</head>
<body>
<div id="root"></div>
<script src="/our.js"></script>
</body>
</html>

View File

@ -0,0 +1,4 @@
<svg width="779" height="514" viewBox="0 0 779 514" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M753.092 5.91932C756.557 5.09976 755.962 -0.00012207 752.401 -0.00012207H426.001C424.755 -0.00012207 423.639 0.77027 423.197 1.93535L236.968 492.6C235.729 495.865 240.123 498.255 242.191 495.441L569.357 50.1132C569.778 49.5392 570.391 49.1339 571.084 48.97L753.092 5.91932Z" fill="#FFF5D9"/>
<path d="M11.9665 40.2288C9.10949 38.777 10.2135 34.4583 13.4167 34.5557L404.273 46.4367C406.334 46.4993 407.719 48.5749 406.986 50.5023L347.438 206.981C346.804 208.647 344.865 209.396 343.275 208.588L11.9665 40.2288Z" fill="#FFF5D9"/>
</svg>

After

Width:  |  Height:  |  Size: 644 B

View File

@ -0,0 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<!-- TODO our in title <title>${our} - Home</title> -->
<meta charset="utf-8" />
<link rel="icon"
href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzc5IiBoZWlnaHQ9IjUxNCIgdmlld0JveD0iMCAwIDc3OSA1MTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+CiAgICAgICAgQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewogICAgICAgICAgICBzdmcgeyBmaWxsOiB3aGl0ZTsgfQogICAgICAgIH0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBsaWdodCkgewogICAgICAgICAgICBzdmcgeyBmaWxsOiBibGFjazsgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8cGF0aCBkPSJNNzUzLjA5MiA1LjkxOTMyQzc1Ni41NTcgNS4wOTk3NiA3NTUuOTYyIC0wLjAwMDEyMjA3IDc1Mi40MDEgLTAuMDAwMTIyMDdINDI2LjAwMUM0MjQuNzU1IC0wLjAwMDEyMjA3IDQyMy42MzkgMC43NzAyNyA0MjMuMTk3IDEuOTM1MzVMMjM2Ljk2OCA0OTIuNkMyMzUuNzI5IDQ5NS44NjUgMjQwLjEyMyA0OTguMjU1IDI0Mi4xOTEgNDk1LjQ0MUw1NjkuMzU3IDUwLjExMzJDNTY5Ljc3OCA0OS41MzkyIDU3MC4zOTEgNDkuMTMzOSA1NzEuMDg0IDQ4Ljk3TDc1My4wOTIgNS45MTkzMloiLz4KICAgIDxwYXRoIGQ9Ik0xMS45NjY1IDQwLjIyODhDOS4xMDk0OSAzOC43NzcgMTAuMjEzNSAzNC40NTgzIDEzLjQxNjcgMzQuNTU1N0w0MDQuMjczIDQ2LjQzNjdDNDA2LjMzNCA0Ni40OTkzIDQwNy43MTkgNDguNTc0OSA0MDYuOTg2IDUwLjUwMjNMMzQ3LjQzOCAyMDYuOTgxQzM0Ni44MDQgMjA4LjY0NyAzNDQuODY1IDIwOS4zOTYgMzQzLjI3NSAyMDguNTg4TDExLjk2NjUgNDAuMjI4OFoiLz4KPC9zdmc+Cg==">
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script src="/our.js"></script>
</body>
</html>

View File

@ -0,0 +1,34 @@
{
"name": "ui",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build:copy": "npm run build && rm -rf ../pkg/ui && cp -r dist ../pkg/ui",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"classnames": "^2.5.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.1.0",
"react-router-dom": "^6.23.0",
"unocss": "^0.59.4",
"zustand": "^4.5.2"
},
"devDependencies": {
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"typescript": "^5.2.2",
"vite": "^5.2.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,4 @@
<svg width="779" height="514" viewBox="0 0 779 514" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M753.092 5.91932C756.557 5.09976 755.962 -0.00012207 752.401 -0.00012207H426.001C424.755 -0.00012207 423.639 0.77027 423.197 1.93535L236.968 492.6C235.729 495.865 240.123 498.255 242.191 495.441L569.357 50.1132C569.778 49.5392 570.391 49.1339 571.084 48.97L753.092 5.91932Z" fill="#FFF5D9"/>
<path d="M11.9665 40.2288C9.10949 38.777 10.2135 34.4583 13.4167 34.5557L404.273 46.4367C406.334 46.4993 407.719 48.5749 406.986 50.5023L347.438 206.981C346.804 208.647 344.865 209.396 343.275 208.588L11.9665 40.2288Z" fill="#FFF5D9"/>
</svg>

After

Width:  |  Height:  |  Size: 644 B

View File

@ -0,0 +1,66 @@
import { useEffect, useState } from 'react'
import BgOrangeBlack from './components/BgOrangeBlack'
import KinodeText from './components/KinodeText'
import KinodeBird from './components/KinodeBird'
import useHomepageStore from './store/homepageStore'
import { FaV } from 'react-icons/fa6'
interface HomepageApp {
package_name: string,
path: string
label: string,
base64_icon: string,
}
function App() {
const [our, setOur] = useState('')
const [apps, setApps] = useState<HomepageApp[]>([])
const { isHosted, fetchHostedStatus } = useHomepageStore()
useEffect(() => {
fetch('/apps')
.then(res => res.json())
.then(data => setApps(data))
fetch('/our')
.then(res => res.text())
.then(data => setOur(data))
.then(() => fetchHostedStatus(our))
}, [our])
return (
<div className="flex-col-center relative h-screen w-screen overflow-hidden">
<div className='absolute w-full h-full top-0 bottom-0 left-0 right-0 -z-20'>
<BgOrangeBlack />
</div>
<div className='absolute w-full h-full top-0 bottom-0 left-0 right-0 -z-10 backdrop-blur-[128px]' />
<h5 className='absolute top-8 left-8'>
{isHosted && <a
href={`https://${our.replace('.os', '')}.hosting.kinode.net/`}
className='button icon'
>
<FaV />
</a>}
{our}
</h5>
<div className="flex-col-center gap-6 mt-8 mx-0 mb-16">
<h3 className='text-center'>Welcome to</h3>
<KinodeText />
<KinodeBird />
</div>
<div className='flex-center flex-wrap gap-8'>
{apps.length === 0 ? <div>Loading apps...</div> : apps.map(app => <a
className="flex-col-center mb-8 cursor-pointer gap-2 hover:opacity-90"
id={app.package_name}
href={app.path}
>
<img
src={app.base64_icon}
className='h-32 w-32'
/>
<h6>{app.label}</h6>
</a>)}
</div>
</div>
)
}
export default App

View File

@ -0,0 +1,17 @@
const BgOrangeBlack = () => <svg className='w-full h-full' width="1440" height="1024" viewBox="0 0 1440 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2204.93 1854.1C2007.12 2361.86 1740.87 3004.89 1458.85 2895.03C683.547 2948.26 -70.0558 2061.27 516.949 1794.74C1103.95 1528.2 1687.89 575.296 2052.46 735.792C2443.3 907.858 2402.74 1346.34 2204.93 1854.1Z" fill="url(#paint0_radial_481_6378)" />
<path d="M356.686 2291.42C97.0266 2481.85 -419.874 2165.71 -613.499 1901.69C-807.123 1637.68 -853.603 1351.19 -593.943 1160.76C-334.284 970.328 582.306 1542.9 775.93 1806.92C969.555 2070.94 616.346 2100.99 356.686 2291.42Z" fill="url(#paint1_linear_481_6378)" />
<path d="M755.921 298.637C911.107 861.964 390.158 1408 0.0280762 1408C-390.102 1408 -706.365 975.022 -706.365 440.916C-706.365 -93.1895 -920.491 -648 -530.361 -648C-140.231 -648 755.921 -235.468 755.921 298.637Z" fill="#F75A29" fill-opacity="0.7" />
<defs>
<radialGradient id="paint0_radial_481_6378" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(1526 2391.65) rotate(-86.9561) scale(699.105 590.175)">
<stop offset="0.635" stop-color="#F35422" stop-opacity="0.7" />
<stop offset="1" stop-color="#E60D16" stop-opacity="0.35" />
</radialGradient>
<linearGradient id="paint1_linear_481_6378" x1="-307.52" y1="1523.67" x2="829.127" y2="1837.64" gradientUnits="userSpaceOnUse">
<stop stop-color="#F35422" />
<stop offset="1" stop-color="#E60D16" stop-opacity="0.1" />
</linearGradient>
</defs>
</svg>
export default BgOrangeBlack;

View File

@ -0,0 +1,15 @@
const KinodeBird = () => <svg className='mt-2' width="67.5" height="48" viewBox="0 0 122 81" fill="none"
xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6_651)">
<path
d="M89.3665 8.06803L121.5 0.35155L66.5111 0.320312L63.7089 7.69502L0.5 5.7032L54.0253 32.9925L36.1529 80.3203L89.3665 8.06803Z"
fill="#FFF5D9" />
</g>
<defs>
<clipPath id="clip0_6_651">
<rect width="121" height="80" fill="white" transform="translate(0.5 0.320312)" />
</clipPath>
</defs>
</svg>
export default KinodeBird;

View File

@ -0,0 +1,33 @@
const KinodeText = () => <svg width="289.5" height="36" viewBox="0 0 580 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6_641)">
<path d="M0.824922 1.07031L0.794922 70.0703H14.7949L14.8049 1.07031H0.824922Z" fill="#FFF5D9" />
<path d="M16.5947 36.8803L41.2547 1.07031H58.2447L33.1647 36.8803L61.2447 70.0703H42.9947L16.5947 36.8803Z"
fill="#FFF5D9" />
<path d="M119.885 1.07031H105.765V70.0703H119.885V1.07031Z" fill="#FFF5D9" />
<path
d="M173.185 1.07031V70.0703H186.775V26.8303L224.045 70.0703H234.825V1.07031H221.325V45.6803L183.445 1.07031H173.185Z"
fill="#FFF5D9" />
<path
d="M342.465 8.86C333.025 0.15 321.645 0 318.535 0C315.475 0 303.575 0.22 294.005 9.52C283.845 19.4 283.805 32.24 283.795 35.66C283.785 39.3 283.895 49.03 290.805 57.99C300.855 71.02 316.695 71.31 318.535 71.32C321.375 71.32 334.185 71 343.965 60.66C353.065 51.04 353.265 39.4 353.275 35.66C353.275 32.49 353.305 18.86 342.455 8.86H342.465ZM318.435 58.01C307.095 58.01 297.895 47.95 297.895 35.54C297.895 23.13 307.085 13.07 318.435 13.07C329.785 13.07 338.975 23.13 338.975 35.54C338.975 47.95 329.785 58.01 318.435 58.01Z"
fill="#FFF5D9" />
<path
d="M450.495 12.0802C444.975 5.46023 437.135 0.990234 427.955 0.990234C417.555 0.990234 405.295 1.07023 402.295 1.07023V69.9802C405.285 69.9802 417.555 70.0602 427.955 70.0602C445.525 70.0602 458.445 53.4102 459.065 36.8602C459.395 28.0102 456.185 18.9002 450.495 12.0802ZM440.085 49.9502C436.895 53.8702 432.705 56.6902 427.665 57.5602C424.025 58.1902 420.095 57.8302 416.405 57.8302C416.405 50.4002 416.405 42.9802 416.405 35.5502V13.2202C423.795 13.2202 430.525 12.7002 436.605 17.6002C440.275 20.5602 442.925 24.7102 444.165 29.2402C444.525 30.5402 444.765 31.8802 444.875 33.2302C445.395 39.3702 443.995 45.1402 440.085 49.9502Z"
fill="#FFF5D9" />
<path
d="M508.135 0.990234V70.0602H552.715V57.9302H522.035V40.4202H547.125V28.0702H521.995V13.3202H552.715V0.990234H508.135Z"
fill="#FFF5D9" />
<path
d="M574.835 66.0398H572.745L571.015 63.0698H569.845V66.0398H567.805V57.5498H571.765C572.845 57.5498 573.865 57.9298 574.425 58.9398C575.205 60.3698 574.665 62.3798 573.105 63.0298C573.725 64.1198 574.225 64.9498 574.845 66.0398H574.835ZM570.375 61.0798H570.845C571.335 61.0798 572.365 61.0798 572.365 60.2898C572.365 59.5598 571.335 59.5598 570.845 59.5598H570.375V61.0798Z"
fill="#FFF5D9" />
<path
d="M570.964 69.0002C574.913 69.0002 578.114 65.799 578.114 61.8502C578.114 57.9014 574.913 54.7002 570.964 54.7002C567.016 54.7002 563.814 57.9014 563.814 61.8502C563.814 65.799 567.016 69.0002 570.964 69.0002Z"
stroke="#FFF5D9" stroke-width="2.2" stroke-miterlimit="10" />
</g>
<defs>
<clipPath id="clip0_6_641">
<rect width="578.41" height="71.32" fill="white" transform="translate(0.794922)" />
</clipPath>
</defs>
</svg>
export default KinodeText;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,12 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import '@unocss/reset/tailwind.css'
import 'uno.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

View File

@ -0,0 +1,41 @@
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
export interface HomepageStore {
get: () => HomepageStore
set: (partial: HomepageStore | Partial<HomepageStore>) => void
isHosted: boolean
setIsHosted: (isHosted: boolean) => void
fetchHostedStatus: (our: string) => Promise<void>
}
const useHomepageStore = create<HomepageStore>()(
persist(
(set, get) => ({
get,
set,
isHosted: false,
setIsHosted: (isHosted: boolean) => set({ isHosted }),
fetchHostedStatus: async (our: string) => {
let hosted = false
try {
const res = await fetch(`https://${our.replace('.os', '')}.hosting.kinode.net/`)
hosted = res.status === 200
} catch (error) {
// do nothing
} finally {
set({ isHosted: hosted })
}
},
}),
{
name: 'homepage_store', // unique name
storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
}
)
)
export default useHomepageStore

View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}

View File

@ -0,0 +1,34 @@
import { defineConfig, presetIcons, presetUno, presetWind, UserConfig, transformerDirectives } from 'unocss'
const config = {
presets: [presetUno(), presetWind(), presetIcons()],
shortcuts: [
{
'flex-center': 'flex justify-center items-center',
'flex-col-center': 'flex flex-col justify-center items-center',
},
],
rules: [
],
theme: {
colors: {
'white': '#FFF5D9',
'black': '#22211F',
'orange': '#F35422',
'transparent': 'transparent',
'gray': '#7E7E7E',
},
font: {
'sans': ['Barlow', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', '"Noto Sans"', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"'],
'serif': ['ui-serif', 'Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'],
'mono': ['ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', '"Liberation Mono"', '"Courier New"', 'monospace'],
'heading': ['OpenSans', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', '"Noto Sans"', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"'],
'display': ['Futura', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', '"Helvetica Neue"', 'Arial', '"Noto Sans"', 'sans-serif', '"Apple Color Emoji"', '"Segoe UI Emoji"', '"Segoe UI Symbol"', '"Noto Color Emoji"'],
},
},
transformers: [
transformerDirectives()
],
}
export default defineConfig(config) as UserConfig<(typeof config)['theme']>

View File

@ -0,0 +1,11 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import UnoCSS from 'unocss/vite'
export default defineConfig({
plugins: [
UnoCSS(), // must be BEFORE react()
react()
],
// ...
})

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long