Files
selfstarter/app/index.tsx
2025-04-19 15:08:24 -04:00

273 lines
7.5 KiB
TypeScript

import {Href, Stack, useRouter} from "expo-router";
import {StyleSheet, Text, View, ColorValue, ListRenderItemInfo, TouchableOpacity, Platform, Modal} from "react-native";
import {RowMap, SwipeListView} from "react-native-swipe-list-view";
import {ChevronDown, Plus} from "lucide-react-native";
import {useEffect, useState} from "react";
import {MenuAction, MenuView, NativeActionEvent} from "@react-native-menu/menu";
import {SQLiteDatabase, SQLiteProvider, useSQLiteContext} from "expo-sqlite";
import CreateComputer from "@/app/createComputer";
export const iOS_HIGHLIGHT = "#007AFF";
export async function migrateDbIfNeeded(db: SQLiteDatabase) {
await db.execAsync(`
PRAGMA journal_mode = WAL;
CREATE TABLE IF NOT EXISTS servers (key INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, domain TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS computers (key INTEGER PRIMARY KEY NOT NULL, server_key INTEGER NOT NULL, name, emoji, background_color, mac_address TEXT NOT NULL);
`);
}
export default function Index() {
const [ currentServerName, setCurrentServerName ] = useState<string>("None");
const [ servers, setServers ] = useState<Server[]>([]);
const [ computers, setComputers ] = useState<Computer[]>([]);
return (
<View>
<SQLiteProvider databaseName="selfstarter.db" onInit={migrateDbIfNeeded}>
<Header servers={servers} setServers={setServers} currentServerName={currentServerName} setCurrentServerName={setCurrentServerName} />
<ComputerList computers={computers} setComputers={setComputers}/>
</SQLiteProvider>
</View>
);
}
function Header(props: { servers: Server[], setServers: (value: (((prevState: Server[]) => Server[]) | Server[])) => void, currentServerName: string, setCurrentServerName: (value: (((prevState: string) => string) | string)) => void }) {
const db = useSQLiteContext();
useEffect(() => {
async function setup() {
const result = await db.getFirstAsync<Server>(`SELECT * FROM servers`);
props.setCurrentServerName(result?.name || "None");
}
if (props.currentServerName === "None") {
setup();
}
}, []);
useEffect(() => {
async function setup() {
const result = await db.getAllAsync<Server>(`SELECT * FROM servers`);
props.setServers(result);
}
setup();
}, []);
const [ menuEvent, setMenuEvent ] = useState("");
const router = useRouter();
useEffect(() => {
if (menuEvent === "addComputer") {
router.navigate('/createComputer');
} else if (menuEvent === "addServer") {
router.navigate('/createServer');
}
setMenuEvent("");
}, [menuEvent]);
const selectActions: MenuAction[] = [];
for (const server of props.servers) {
if (server.name !== props.currentServerName) {
selectActions.push({
id: server.domain,
title: server.name,
titleColor: '#000000',
imageColor: '#000000',
})
}
}
selectActions.push({
id: 'deleteServer',
title: 'Delete Current Server',
attributes: {
destructive: true,
},
image: Platform.select({
ios: 'trash',
android: 'ic_menu_delete',
}),
imageColor: "red",
})
const handleServerSelect = ({nativeEvent}: NativeActionEvent) => {
const newName = props.servers.find((server) => {
return server.name === nativeEvent.event;
})
props.setCurrentServerName(newName?.name ?? "None");
};
const addActions: MenuAction[] = [
{
id: 'addServer',
title: 'Add WoL Server',
titleColor: '#000000',
image: 'globe',
imageColor: '#000000',
}
];
if (props.currentServerName !== "None") {
addActions.push({
id: 'addComputer',
title: 'Add Computer',
titleColor: '#000000',
image: 'desktopcomputer',
imageColor: '#000000',
});
}
return <Stack.Screen
options={{
title: 'Home',
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
headerTitle: _ =>
<View style={{
display: "flex",
flexDirection: "row",
gap: 10,
}}>
<Text style={{
fontSize: 20,
fontWeight: 'bold',
}}>{props.currentServerName}</Text>
{props.servers.length > 0 &&
<View style={{
backgroundColor: iOS_HIGHLIGHT,
borderRadius: 100,
}}>
<MenuView onPressAction={handleServerSelect} actions={selectActions}>
<ChevronDown color={"#FFFFFF"}/>
</MenuView>
</View>
}
</View>,
headerRight: props =>
<View>
<MenuView
onPressAction={({nativeEvent}) => { setMenuEvent(nativeEvent.event); }}
actions={addActions}
shouldOpenOnLongPress={false}
>
<Plus color={iOS_HIGHLIGHT}/>
</MenuView>
</View>
}}
/>
}
const styles = StyleSheet.create({
computerElement: {
display: "flex",
flexDirection: "row",
paddingHorizontal: 24,
paddingVertical: 14,
gap: 32,
backgroundColor: '#FFFFFF'
},
computerName: {
fontSize: 18,
justifyContent: "flex-start",
},
computerSubtitle: {
fontSize: 12,
justifyContent: "flex-start",
color: '#979797'
},
backTextWhite: {
color: '#FFF',
},
rowBack: {
alignItems: 'center',
backgroundColor: '#DDD',
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
paddingLeft: 15,
},
backRightBtn: {
alignItems: 'center',
bottom: 0,
justifyContent: 'center',
position: 'absolute',
top: 0,
width: 75,
},
backRightBtnRight: {
backgroundColor: 'red',
right: 0,
},
});
function ComputerList(props: {computers: Computer[], setComputers: (value: (((prevState: Computer[]) => Computer[]) | Computer[])) => void }) {
const db = useSQLiteContext();
useEffect(() => {
async function setup() {
const result = await db.getAllAsync<Computer>(`SELECT * FROM computers`);
props.setComputers(result);
}
setup();
}, []);
const renderHiddenItem = (data: ListRenderItemInfo<Computer>, rowMap: RowMap<Computer>) => (
<View style={styles.rowBack}>
<TouchableOpacity
style={[styles.backRightBtn, styles.backRightBtnRight]}
onPress={() => {
//TODO: remove it from storage
}}>
<Text style={styles.backTextWhite}>Delete</Text>
</TouchableOpacity>
</View>
);
return (
<SwipeListView
data={props.computers}
renderItem={({item}: ListRenderItemInfo<Computer>) =>
<View style={styles.computerElement}>
<View style={{
width: 65,
height: 65,
borderRadius: 100,
backgroundColor: item.background_color,
alignItems: "center",
justifyContent: "center",
}}> //TODO: Make Button
<Text style={{
fontSize: 28
}}>{item.emoji}</Text>
</View>
<View style={{
flexDirection: "column",
gap: 3,
padding: 0,
justifyContent: "center",
}}>
<Text style={styles.computerName}>{item.name}</Text>
<Text style={styles.computerSubtitle}>{item.mac_address}</Text>
</View>
</View>
} rightOpenValue={-75} renderHiddenItem={renderHiddenItem}
/>
)
}
type Server = {
key: number,
name: string,
domain: string,
}
type Computer = {
key: number,
server_key: number,
name: string,
emoji: string,
background_color: ColorValue,
mac_address: string,
}