Merge branch 'develop' into dr/settings-page
@ -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>
|
||||
|
@ -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', })}
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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 |
@ -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(),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -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"
|
||||
|
56
kinode/packages/homepage/pkg/ui/assets/index-jKxRCta5.js
Normal file
BIN
kinode/packages/homepage/pkg/ui/background.jpg
Normal file
After Width: | Height: | Size: 14 KiB |
21
kinode/packages/homepage/pkg/ui/index.html
Normal 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>
|
4
kinode/packages/homepage/pkg/ui/kinode.svg
Normal 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 |
18
kinode/packages/homepage/ui/.eslintrc.cjs
Normal 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
@ -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?
|
3
kinode/packages/homepage/ui/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Kinode Homepage UI
|
||||
|
||||
The homepage for Kinode.
|
1
kinode/packages/homepage/ui/dist/assets/index-BIXROKBk.css
vendored
Normal file
BIN
kinode/packages/homepage/ui/dist/background.jpg
vendored
Normal file
After Width: | Height: | Size: 14 KiB |
21
kinode/packages/homepage/ui/dist/index.html
vendored
Normal 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>
|
4
kinode/packages/homepage/ui/dist/kinode.svg
vendored
Normal 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 |
20
kinode/packages/homepage/ui/index.html
Normal 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>
|
34
kinode/packages/homepage/ui/package.json
Normal 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"
|
||||
}
|
||||
}
|
BIN
kinode/packages/homepage/ui/public/background.jpg
Normal file
After Width: | Height: | Size: 14 KiB |
4
kinode/packages/homepage/ui/public/kinode.svg
Normal 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 |
66
kinode/packages/homepage/ui/src/App.tsx
Normal 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
|
17
kinode/packages/homepage/ui/src/components/BgOrangeBlack.tsx
Normal 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;
|
15
kinode/packages/homepage/ui/src/components/KinodeBird.tsx
Normal 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;
|
33
kinode/packages/homepage/ui/src/components/KinodeText.tsx
Normal 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;
|
137
kinode/packages/homepage/ui/src/index.css
Normal file
12
kinode/packages/homepage/ui/src/main.tsx
Normal 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>,
|
||||
)
|
41
kinode/packages/homepage/ui/src/store/homepageStore.ts
Normal 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
|
1
kinode/packages/homepage/ui/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
25
kinode/packages/homepage/ui/tsconfig.json
Normal 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" }]
|
||||
}
|
11
kinode/packages/homepage/ui/tsconfig.node.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
34
kinode/packages/homepage/ui/uno.config.ts
Normal 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']>
|
11
kinode/packages/homepage/ui/vite.config.ts
Normal 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()
|
||||
],
|
||||
// ...
|
||||
})
|