optimize a bit code, re-order imports

This commit is contained in:
Stef-00012 2025-02-11 18:15:54 +01:00
parent 7336de10f6
commit 044de8ce34
No known key found for this signature in database
GPG key ID: 28BE9A9E4EF0E6BF
36 changed files with 1368 additions and 1040 deletions

3
.gitignore vendored
View file

@ -35,7 +35,10 @@ yarn-error.*
# typescript
*.tsbuildinfo
# expo prebuild
/android
/ios
# misc
biome.json
package-lock.json

View file

@ -36,12 +36,4 @@ This will create an apk but won't automatically install
# TODO
- [x] Fix keyboard covering TOTP input on login screen
- [ ] Optimize pages by re-rendering only the components that use certain variables and not other unrelated components (~~this is not Spotify~~)
- app/(app)/admin/invites.ts: remove useless settings fetch, move header out of the condition
- app/(app)/admin/settings.ts: re-render individual selects/inputs/switchs instead of the whole page, move header out of the condition
- app/(app)/admin/users.ts: remove useless settings fetch, move header out of the condition
- app/(app)/folders.ts: move header out of the condition
- app/(app)/metrics.ts: move header out of the condition
- app/(app)/settings.ts: re-render individual selects/inputs/switchs instead of the whole page, move header out of the condition
- app/(app)/urls.ts: move header out of the condition
- app/index.ts: move header out of the condition
- [x] Optimize pages by re-rendering only the components that use certain variables and not other unrelated components (~~this is not Spotify~~)

View file

@ -1,11 +1,10 @@
import type { ExpoConfig, ConfigContext } from "expo/config";
const IS_DEV = process.env.APP_VARIANT === "development";
const IS_RELEASE = process.env.APP_VARIANT === "devrelease";
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: `Zipline${IS_DEV ? " (Dev)" : IS_RELEASE ? " (Dev Release)" : ""}`,
name: `Zipline${IS_DEV ? " (Dev)" : ""}`,
slug: "zipline",
version: "1.0.2",
orientation: "portrait",
@ -15,7 +14,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
userInterfaceStyle: "automatic",
platforms: ["android"],
ios: {
bundleIdentifier: `com.stefdp.zipline${IS_DEV ? ".dev" : IS_RELEASE ? ".devrelease" : ""}`
bundleIdentifier: `com.stefdp.zipline${IS_DEV ? ".dev" : ""}`,
},
android: {
adaptiveIcon: {
@ -23,7 +22,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
monochromeImage: "./assets/images/monochromatic-adaptive-icon.png",
backgroundColor: "#121317",
},
package: `com.stefdp.zipline${IS_DEV ? ".dev" : IS_RELEASE ? ".devrelease" : ""}`
package: `com.stefdp.zipline${IS_DEV ? ".dev" : ""}`,
},
androidStatusBar: {
barStyle: "light-content",

View file

@ -468,7 +468,10 @@ export default function Files() {
}}
icon={favorites ? "star" : "star-border"}
color="transparent"
iconColor={favorites ? "#f1d01f" : "#2d3f70"}
iconColor={favorites
? files ? "#f1d01f" : "#f1d01f55"
: files ? "#2d3f70" : "#2d3f7055"
}
borderColor="#222c47"
borderWidth={2}
iconSize={30}
@ -489,7 +492,7 @@ export default function Files() {
}}
icon="sell"
color="transparent"
iconColor="#2d3f70"
iconColor={files ? "#2d3f70" : "#2d3f7055"}
borderColor="#222c47"
borderWidth={2}
iconSize={30}
@ -508,7 +511,7 @@ export default function Files() {
}}
icon="upload-file"
color="transparent"
iconColor="#2d3f70"
iconColor={files ? "#2d3f70" : "#2d3f7055"}
borderColor="#222c47"
borderWidth={2}
iconSize={30}

View file

@ -1,5 +1,4 @@
import { ScrollView, Text, View, ToastAndroid } from "react-native";
import { getSettings } from "@/functions/zipline/settings";
import { Row, Table } from "react-native-reanimated-table";
import { useShareIntent } from "@/hooks/useShareIntent";
import { timeDifference } from "@/functions/util";
@ -20,7 +19,6 @@ import {
} from "@/functions/zipline/invites";
import type {
APIInvites,
APISettings,
DashURL,
} from "@/types/zipline";
@ -29,7 +27,6 @@ export default function Invites() {
useShareIntent();
const [invites, setInvites] = useState<APIInvites | null>(null);
const [settings, setSettings] = useState<APISettings | null>(null);
const [createNewInvite, setCreateNewInvite] = useState<boolean>(false);
@ -43,10 +40,8 @@ export default function Invites() {
useEffect(() => {
(async () => {
const invites = await getInvites();
const settings = await getSettings();
setInvites(typeof invites === "string" ? null : invites);
setSettings(typeof settings === "string" ? null : settings);
})();
}, []);
@ -136,28 +131,30 @@ export default function Invites() {
</View>
</Popup>
{invites && settings && dashUrl ? (
<View style={{ flex: 1 }}>
<View style={styles.header}>
<Text style={styles.headerText}>Invites</Text>
<View style={styles.headerButtons}>
<Button
onPress={() => {
setCreateNewInvite(true);
}}
icon="add"
color="transparent"
iconColor="#2d3f70"
borderColor="#222c47"
borderWidth={2}
iconSize={30}
padding={4}
rippleColor="#283557"
/>
</View>
</View>
<View style={styles.header}>
<Text style={styles.headerText}>Invites</Text>
<View style={styles.headerButtons}>
<Button
onPress={() => {
setCreateNewInvite(true);
}}
icon="add"
color="transparent"
// iconColor="#2d3f70"
iconColor={(invites && dashUrl) ? "#2d3f70" : "#2d3f7055"}
borderColor="#222c47"
borderWidth={2}
iconSize={30}
padding={4}
rippleColor="#283557"
disabled={!invites || !dashUrl}
/>
</View>
</View>
<View style={{ ...styles.invitesContainer, flex: 1 }}>
<View style={{ flex: 1 }}>
<View style={{ ...styles.invitesContainer, flex: 1 }}>
{invites && dashUrl ? (
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
<View>
<Table>
@ -337,13 +334,13 @@ export default function Invites() {
</ScrollView>
</View>
</ScrollView>
</View>
) : (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading...</Text>
</View>
)}
</View>
) : (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading...</Text>
</View>
)}
</View>
</View>
</View>
);

View file

@ -635,22 +635,22 @@ export default function ServerSettings() {
return (
<View style={styles.mainContainer}>
<View style={styles.mainContainer}>
<View style={styles.header}>
<Text style={styles.headerText}>Server Settings</Text>
{saveError && (
<View>
{saveError.map((error) => (
<Text style={styles.errorText} key={error}>
{error}
</Text>
))}
</View>
)}
</View>
{settings ? (
<View style={styles.settingsContainer}>
<View style={styles.header}>
<Text style={styles.headerText}>Server Settings</Text>
{saveError && (
<View>
{saveError.map((error) => (
<Text style={styles.errorText} key={error}>
{error}
</Text>
))}
</View>
)}
</View>
<KeyboardAwareScrollView style={styles.scrollView}>
{/* Core */}
<View style={styles.settingGroup}>

View file

@ -2,7 +2,6 @@ import { ScrollView, Text, View, ToastAndroid } from "react-native";
import { getFileDataURI, timeDifference } from "@/functions/util";
import { fileQuotaTypes, userRoles } from "@/constants/users";
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import { getSettings } from "@/functions/zipline/settings";
import { Row, Table } from "react-native-reanimated-table";
import { useShareIntent } from "@/hooks/useShareIntent";
import * as DocumentPicker from "expo-document-picker";
@ -25,7 +24,6 @@ import {
getUsers,
} from "@/functions/zipline/users";
import type {
APISettings,
APIUser,
APIUserQuota,
APIUsersNoIncl,
@ -37,7 +35,6 @@ export default function Users() {
useShareIntent();
const [users, setUsers] = useState<APIUsersNoIncl | null>(null);
const [settings, setSettings] = useState<APISettings | null>(null);
const [userToEdit, setUserToEdit] = useState<APIUsersNoIncl[0] | null>(null);
@ -88,10 +85,8 @@ export default function Users() {
useEffect(() => {
(async () => {
const users = await getUsers(true);
const settings = await getSettings();
setUsers(typeof users === "string" ? null : users);
setSettings(typeof settings === "string" ? null : settings);
})();
}, []);
@ -651,190 +646,191 @@ export default function Users() {
</View>
</Popup>
{users && settings && dashUrl ? (
<View style={styles.header}>
<Text style={styles.headerText}>Users</Text>
<View style={styles.headerButtons}>
<Button
onPress={() => {
setCreateNewUser(true);
}}
icon="person-add"
color="transparent"
iconColor={(users && dashUrl) ? "#2d3f70" : "#2d3f7055"}
borderColor="#222c47"
borderWidth={2}
iconSize={30}
padding={4}
rippleColor="#283557"
disabled={!users || !dashUrl}
/>
</View>
</View>
<View style={{ flex: 1 }}>
<View style={styles.header}>
<Text style={styles.headerText}>Users</Text>
<View style={styles.headerButtons}>
<Button
onPress={() => {
setCreateNewUser(true);
}}
icon="person-add"
color="transparent"
iconColor="#2d3f70"
borderColor="#222c47"
borderWidth={2}
iconSize={30}
padding={4}
rippleColor="#283557"
/>
</View>
</View>
<View style={{ ...styles.usersContainer, flex: 1 }}>
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
<View>
<Table>
<Row
data={[
"Avatar",
"Username",
"Role",
"Created",
"Last Updated",
"Actions",
]}
widthArr={[80, 100, 100, 130, 130, 130]}
style={styles.tableHeader}
textStyle={{
...styles.rowText,
...styles.headerRow,
}}
/>
</Table>
<ScrollView
showsVerticalScrollIndicator={false}
style={styles.tableVerticalScroll}
>
{users && dashUrl ? (
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
<View>
<Table>
{users.map((user, index) => {
const avatar = user.avatar ? (
<Image
source={{ uri: user.avatar }}
style={styles.userAvatar}
alt={`${user.username}'s avatar`}
/>
) : (
<View style={styles.userAvatar}>
<MaterialIcons
name="person"
size={30}
color={"white"}
/>
</View>
);
const username = (
<Text key={user.id} style={styles.rowText}>
{user.username}
</Text>
);
const role = (
<Text key={user.id} style={styles.rowText}>
{user.role.charAt(0).toUpperCase() +
user.role.slice(1).toLowerCase()}
</Text>
);
const created = (
<Text style={styles.rowText}>
{timeDifference(
new Date(),
new Date(user.createdAt),
)}
</Text>
);
const lastUpdated = (
<Text style={styles.rowText}>
{timeDifference(
new Date(),
new Date(user.updatedAt),
)}
</Text>
);
const actions = (
<View style={styles.actionsContainer}>
<Button
icon="folder-open"
color="#323ea8"
onPress={async () => {
const userId = user.id;
router.replace(`/files?id=${userId}`);
}}
iconSize={20}
width={32}
height={32}
padding={6}
/>
<Button
icon="edit"
color="#323ea8"
onPress={() => {
const userId = user.id;
setUserToEdit(
users.find((usr) => usr.id === userId) ||
null,
);
}}
iconSize={20}
width={32}
height={32}
padding={6}
/>
<Button
icon="delete"
color="#CF4238"
onPress={async () => {
setUserToDelete(user);
}}
iconSize={20}
width={32}
height={32}
padding={6}
/>
</View>
);
let rowStyle = styles.row;
if (index === 0)
rowStyle = {
...styles.row,
...styles.firstRow,
};
if (index === users.length - 1)
rowStyle = {
...styles.row,
...styles.lastRow,
};
return (
<Row
key={user.id}
data={[
avatar,
username,
role,
created,
lastUpdated,
actions,
]}
widthArr={[80, 100, 100, 130, 130, 130]}
style={rowStyle}
textStyle={styles.rowText}
/>
);
})}
<Row
data={[
"Avatar",
"Username",
"Role",
"Created",
"Last Updated",
"Actions",
]}
widthArr={[80, 100, 100, 130, 130, 130]}
style={styles.tableHeader}
textStyle={{
...styles.rowText,
...styles.headerRow,
}}
/>
</Table>
</ScrollView>
<ScrollView
showsVerticalScrollIndicator={false}
style={styles.tableVerticalScroll}
>
<Table>
{users.map((user, index) => {
const avatar = user.avatar ? (
<Image
source={{ uri: user.avatar }}
style={styles.userAvatar}
alt={`${user.username}'s avatar`}
/>
) : (
<View style={styles.userAvatar}>
<MaterialIcons
name="person"
size={30}
color={"white"}
/>
</View>
);
const username = (
<Text key={user.id} style={styles.rowText}>
{user.username}
</Text>
);
const role = (
<Text key={user.id} style={styles.rowText}>
{user.role.charAt(0).toUpperCase() +
user.role.slice(1).toLowerCase()}
</Text>
);
const created = (
<Text style={styles.rowText}>
{timeDifference(
new Date(),
new Date(user.createdAt),
)}
</Text>
);
const lastUpdated = (
<Text style={styles.rowText}>
{timeDifference(
new Date(),
new Date(user.updatedAt),
)}
</Text>
);
const actions = (
<View style={styles.actionsContainer}>
<Button
icon="folder-open"
color="#323ea8"
onPress={async () => {
const userId = user.id;
router.replace(`/files?id=${userId}`);
}}
iconSize={20}
width={32}
height={32}
padding={6}
/>
<Button
icon="edit"
color="#323ea8"
onPress={() => {
const userId = user.id;
setUserToEdit(
users.find((usr) => usr.id === userId) ||
null,
);
}}
iconSize={20}
width={32}
height={32}
padding={6}
/>
<Button
icon="delete"
color="#CF4238"
onPress={async () => {
setUserToDelete(user);
}}
iconSize={20}
width={32}
height={32}
padding={6}
/>
</View>
);
let rowStyle = styles.row;
if (index === 0)
rowStyle = {
...styles.row,
...styles.firstRow,
};
if (index === users.length - 1)
rowStyle = {
...styles.row,
...styles.lastRow,
};
return (
<Row
key={user.id}
data={[
avatar,
username,
role,
created,
lastUpdated,
actions,
]}
widthArr={[80, 100, 100, 130, 130, 130]}
style={rowStyle}
textStyle={styles.rowText}
/>
);
})}
</Table>
</ScrollView>
</View>
</ScrollView>
) : (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading...</Text>
</View>
</ScrollView>
)}
</View>
</View>
) : (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading...</Text>
</View>
)}
</View>
</View>
);

View file

@ -107,230 +107,231 @@ export default function Folders() {
</View>
</Popup>
{folders && dashUrl ? (
<View style={styles.header}>
<Text style={styles.headerText}>Folders</Text>
<View style={styles.headerButtons}>
<Button
onPress={() => {
setCreateNewFolder(true);
}}
icon="create-new-folder"
color="transparent"
iconColor={(folders && dashUrl) ? "#2d3f70" : "#2d3f7055"}
borderColor="#222c47"
borderWidth={2}
iconSize={30}
padding={4}
rippleColor="#283557"
disabled={!folders || !dashUrl}
/>
</View>
</View>
<View style={{ flex: 1 }}>
<View style={styles.header}>
<Text style={styles.headerText}>Folders</Text>
<View style={styles.headerButtons}>
<Button
onPress={() => {
setCreateNewFolder(true);
}}
icon="create-new-folder"
color="transparent"
iconColor="#2d3f70"
borderColor="#222c47"
borderWidth={2}
iconSize={30}
padding={4}
rippleColor="#283557"
/>
</View>
</View>
<View style={{ ...styles.foldersContainer, flex: 1 }}>
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
<View>
<Table>
<Row
data={["Name", "Public", "Created", "Actions"]}
widthArr={[80, 50, 130, 150]}
style={styles.tableHeader}
textStyle={{
...styles.rowText,
...styles.headerRow,
}}
/>
</Table>
<ScrollView
showsVerticalScrollIndicator={false}
style={styles.tableVerticalScroll}
>
{folders && dashUrl ? (
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
<View>
<Table>
{folders.map((folder, index) => {
const name = folder.public ? (
<Link
key={folder.id}
href={
`${dashUrl}/folder/${folder.id}` as ExternalPathString
}
style={{
...styles.rowText,
...styles.link,
}}
>
{folder.name}
</Link>
) : (
<Text key={folder.id} style={styles.rowText}>
{folder.name}
</Text>
);
const isPublic = (
<Text key={folder.id} style={styles.rowText}>
{folder.public ? "Yes" : "No"}
</Text>
);
const created = (
<Text style={styles.rowText}>
{timeDifference(
new Date(),
new Date(folder.createdAt),
)}
</Text>
);
const actions = (
<View style={styles.actionsContainer}>
<Button
icon="folder-open"
color="#323ea8"
onPress={() => {
const folderId = folder.id;
router.replace(`/files?folderId=${folderId}`);
}}
iconSize={20}
width={32}
height={32}
padding={6}
/>
<Button
icon="content-copy"
color={folder.public ? "#323ea8" : "#181c28"}
iconColor={folder.public ? "white" : "#2a3952"}
onPress={async () => {
const urlDest = `${dashUrl}/folder/${folder.id}`;
const saved =
await Clipboard.setStringAsync(urlDest);
if (saved)
return ToastAndroid.show(
"Folder URL copied to clipboard",
ToastAndroid.SHORT,
);
return ToastAndroid.show(
"Failed to paste to the clipboard",
ToastAndroid.SHORT,
);
}}
disabled={!folder.public}
iconSize={20}
width={32}
height={32}
padding={6}
/>
<Button
onPress={async () => {
const folderId = folder.id;
const success = await editFolder(
folderId,
!folder.public,
);
if (typeof success === "string")
return ToastAndroid.show(
`Failed to update the folder "${folder.name}"`,
ToastAndroid.SHORT,
);
ToastAndroid.show(
`Updated the folder "${folder.name}"'s visibility`,
ToastAndroid.SHORT,
);
const folderIndex = folders.findIndex(
(fold) => folder.id === fold.id,
);
const newFolders = [...folders];
newFolders[folderIndex].public = !folder.public;
setFolders(newFolders);
}}
color={folder.public ? "#323ea8" : "#343a40"}
icon={folder.public ? "lock-open" : "lock"}
iconSize={20}
width={32}
height={32}
padding={6}
/>
<Button
onPress={async () => {
const folderId = folder.id;
const success = await deleteFolder(folderId);
if (typeof success === "string")
return ToastAndroid.show(
`Failed to delete the folder "${folder.name}"`,
ToastAndroid.SHORT,
);
const newFolders = folders.filter(
(fold) => fold.id !== folder.id,
);
setFolders(newFolders);
ToastAndroid.show(
`Deleted the folder "${folder.name}"`,
ToastAndroid.SHORT,
);
}}
color="#CF4238"
icon="delete"
iconSize={20}
width={32}
height={32}
padding={6}
/>
</View>
);
let rowStyle = styles.row;
if (index === 0)
rowStyle = {
...styles.row,
...styles.firstRow,
};
if (index === folders.length - 1)
rowStyle = {
...styles.row,
...styles.lastRow,
};
return (
<Row
key={folder.id}
data={[name, isPublic, created, actions]}
widthArr={[80, 50, 130, 150]}
style={rowStyle}
textStyle={styles.rowText}
/>
);
})}
<Row
data={["Name", "Public", "Created", "Actions"]}
widthArr={[80, 50, 130, 150]}
style={styles.tableHeader}
textStyle={{
...styles.rowText,
...styles.headerRow,
}}
/>
</Table>
</ScrollView>
<ScrollView
showsVerticalScrollIndicator={false}
style={styles.tableVerticalScroll}
>
<Table>
{folders.map((folder, index) => {
const name = folder.public ? (
<Link
key={folder.id}
href={
`${dashUrl}/folder/${folder.id}` as ExternalPathString
}
style={{
...styles.rowText,
...styles.link,
}}
>
{folder.name}
</Link>
) : (
<Text key={folder.id} style={styles.rowText}>
{folder.name}
</Text>
);
const isPublic = (
<Text key={folder.id} style={styles.rowText}>
{folder.public ? "Yes" : "No"}
</Text>
);
const created = (
<Text style={styles.rowText}>
{timeDifference(
new Date(),
new Date(folder.createdAt),
)}
</Text>
);
const actions = (
<View style={styles.actionsContainer}>
<Button
icon="folder-open"
color="#323ea8"
onPress={() => {
const folderId = folder.id;
router.replace(`/files?folderId=${folderId}`);
}}
iconSize={20}
width={32}
height={32}
padding={6}
/>
<Button
icon="content-copy"
color={folder.public ? "#323ea8" : "#181c28"}
iconColor={folder.public ? "white" : "#2a3952"}
onPress={async () => {
const urlDest = `${dashUrl}/folder/${folder.id}`;
const saved =
await Clipboard.setStringAsync(urlDest);
if (saved)
return ToastAndroid.show(
"Folder URL copied to clipboard",
ToastAndroid.SHORT,
);
return ToastAndroid.show(
"Failed to paste to the clipboard",
ToastAndroid.SHORT,
);
}}
disabled={!folder.public}
iconSize={20}
width={32}
height={32}
padding={6}
/>
<Button
onPress={async () => {
const folderId = folder.id;
const success = await editFolder(
folderId,
!folder.public,
);
if (typeof success === "string")
return ToastAndroid.show(
`Failed to update the folder "${folder.name}"`,
ToastAndroid.SHORT,
);
ToastAndroid.show(
`Updated the folder "${folder.name}"'s visibility`,
ToastAndroid.SHORT,
);
const folderIndex = folders.findIndex(
(fold) => folder.id === fold.id,
);
const newFolders = [...folders];
newFolders[folderIndex].public = !folder.public;
setFolders(newFolders);
}}
color={folder.public ? "#323ea8" : "#343a40"}
icon={folder.public ? "lock-open" : "lock"}
iconSize={20}
width={32}
height={32}
padding={6}
/>
<Button
onPress={async () => {
const folderId = folder.id;
const success = await deleteFolder(folderId);
if (typeof success === "string")
return ToastAndroid.show(
`Failed to delete the folder "${folder.name}"`,
ToastAndroid.SHORT,
);
const newFolders = folders.filter(
(fold) => fold.id !== folder.id,
);
setFolders(newFolders);
ToastAndroid.show(
`Deleted the folder "${folder.name}"`,
ToastAndroid.SHORT,
);
}}
color="#CF4238"
icon="delete"
iconSize={20}
width={32}
height={32}
padding={6}
/>
</View>
);
let rowStyle = styles.row;
if (index === 0)
rowStyle = {
...styles.row,
...styles.firstRow,
};
if (index === folders.length - 1)
rowStyle = {
...styles.row,
...styles.lastRow,
};
return (
<Row
key={folder.id}
data={[name, isPublic, created, actions]}
widthArr={[80, 50, 130, 150]}
style={rowStyle}
textStyle={styles.rowText}
/>
);
})}
</Table>
</ScrollView>
</View>
</ScrollView>
) : (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading...</Text>
</View>
</ScrollView>
)}
</View>
</View>
) : (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading...</Text>
</View>
)}
</View>
</View>
);

View file

@ -146,38 +146,42 @@ export default function Metrics() {
/>
</DatePicker>
<View style={styles.header}>
<Text style={styles.headerText}>Metrics</Text>
<Text style={styles.dateRangeText}>
{allTime
? "All Time"
: `${new Date(
range.startDate as string,
).toLocaleDateString()}${
range.endDate
? ` to ${new Date(range.endDate as string).toLocaleDateString()}`
: ""
}`}
</Text>
<Button
onPress={() => {
setDatePickerOpen(true);
}}
color="transparent"
text="Change Date Range"
borderWidth={2}
borderColor="#222c47"
margin={{
right: 10,
top: 10,
}}
rippleColor="#283557"
disabled={!filteredStats || !mainStat}
textColor={(filteredStats && mainStat) ? "white" : "gray"}
/>
</View>
{filteredStats && mainStat ? (
<View>
<ScrollView>
<View style={styles.header}>
<Text style={styles.headerText}>Metrics</Text>
<Text style={styles.dateRangeText}>
{allTime
? "All Time"
: `${new Date(
range.startDate as string,
).toLocaleDateString()}${
range.endDate
? ` to ${new Date(range.endDate as string).toLocaleDateString()}`
: ""
}`}
</Text>
<Button
onPress={() => {
setDatePickerOpen(true);
}}
color="transparent"
text="Change Date Range"
borderWidth={2}
borderColor="#222c47"
margin={{
right: 10,
top: 10,
}}
rippleColor="#283557"
/>
</View>
<ScrollView
horizontal
style={{

View file

@ -470,67 +470,69 @@ export default function UserSettings() {
</Text>
</Popup>
{user && token && exports ? (
<View style={styles.header}>
<Text style={styles.headerText}>User Settings</Text>
{saveError && (
<Text style={styles.errorText} key={saveError}>
{saveError}
</Text>
)}
</View>
{user ? (
<View style={styles.settingsContainer}>
<View style={styles.header}>
<Text style={styles.headerText}>User Settings</Text>
{saveError && (
<Text style={styles.errorText} key={saveError}>
{saveError}
</Text>
)}
</View>
<KeyboardAwareScrollView style={styles.scrollView}>
{/* User Info */}
<View style={styles.settingGroup}>
<Text style={styles.headerText}>User Info</Text>
<Text style={styles.subHeaderText}>{user.id}</Text>
{token && (
<View style={styles.settingGroup}>
<Text style={styles.headerText}>User Info</Text>
<Text style={styles.subHeaderText}>{user.id}</Text>
<Pressable
onPress={() => {
setTokenVisible(true);
}}
>
<TextInput
title="Token:"
showDisabledStyle={false}
disabled
disableContext
value={tokenVisible ? token : "[Click to Reveal]"}
onSideButtonPress={() => {
Clipboard.setStringAsync(token);
<Pressable
onPress={() => {
setTokenVisible(true);
}}
sideButtonIcon="content-copy"
>
<TextInput
title="Token:"
showDisabledStyle={false}
disabled
disableContext
value={tokenVisible ? token : "[Click to Reveal]"}
onSideButtonPress={() => {
Clipboard.setStringAsync(token);
}}
sideButtonIcon="content-copy"
/>
</Pressable>
<TextInput
title="Username:"
onValueChange={(content) => setUsername(content)}
placeholder="My Cool Username"
value={username || ""}
/>
</Pressable>
<TextInput
title="Username:"
onValueChange={(content) => setUsername(content)}
placeholder="My Cool Username"
value={username || ""}
/>
<TextInput
title="Password:"
onValueChange={(content) => setPassword(content)}
placeholder="myPassword123"
value={password || ""}
password
/>
<TextInput
title="Password:"
onValueChange={(content) => setPassword(content)}
placeholder="myPassword123"
value={password || ""}
password
/>
<Button
onPress={() => handleSave("userInfo")}
color="#323ea8"
text="Save"
icon="save"
margin={{
top: 10,
}}
/>
</View>
<Button
onPress={() => handleSave("userInfo")}
color="#323ea8"
text="Save"
icon="save"
margin={{
top: 10,
}}
/>
</View>
)}
{/* Avatar */}
<View style={styles.settingGroup}>
@ -781,247 +783,249 @@ export default function UserSettings() {
</View>
{/* Export Files */}
<View style={styles.settingGroup}>
<Text style={styles.headerText}>Export Files</Text>
{(exports && token) && (
<View style={styles.settingGroup}>
<Text style={styles.headerText}>Export Files</Text>
<Button
onPress={async () => {
const success = await createUserExport();
<Button
onPress={async () => {
const success = await createUserExport();
if (typeof success === "string")
return setSaveError(success);
if (typeof success === "string")
return setSaveError(success);
const newExports = await getUserExports();
const newExports = await getUserExports();
setExports(
typeof newExports === "string" ? null : newExports,
);
setExports(
typeof newExports === "string" ? null : newExports,
);
ToastAndroid.show(
"Successfully started creating the export",
ToastAndroid.SHORT,
);
}}
color="#323ea8"
text="New Export"
margin={{
top: 10,
}}
/>
ToastAndroid.show(
"Successfully started creating the export",
ToastAndroid.SHORT,
);
}}
color="#323ea8"
text="New Export"
margin={{
top: 10,
}}
/>
<View style={styles.exportsContainer}>
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
<View>
<Table>
<Row
data={[
"ID",
"Started On",
"Files",
"Size",
"Actions",
]}
widthArr={[150, 130, 50, 70, 90]}
style={styles.tableHeader}
textStyle={{
...styles.rowText,
...styles.headerRow,
}}
/>
</Table>
<ScrollView
showsVerticalScrollIndicator={false}
style={styles.tableVerticalScroll}
>
<View style={styles.exportsContainer}>
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
<View>
<Table>
{exports.map((zlExport, index) => {
const id = (
<Text style={styles.rowText}>{zlExport.id}</Text>
);
<Row
data={[
"ID",
"Started On",
"Files",
"Size",
"Actions",
]}
widthArr={[150, 130, 50, 70, 90]}
style={styles.tableHeader}
textStyle={{
...styles.rowText,
...styles.headerRow,
}}
/>
</Table>
<ScrollView
showsVerticalScrollIndicator={false}
style={styles.tableVerticalScroll}
>
<Table>
{exports.map((zlExport, index) => {
const id = (
<Text style={styles.rowText}>{zlExport.id}</Text>
);
const startedOn = (
<Text style={styles.rowText}>
{new Date(zlExport.createdAt).toLocaleString()}
</Text>
);
const startedOn = (
<Text style={styles.rowText}>
{new Date(zlExport.createdAt).toLocaleString()}
</Text>
);
const files = (
<Text style={styles.rowText}>
{zlExport.files}
</Text>
);
const files = (
<Text style={styles.rowText}>
{zlExport.files}
</Text>
);
const size = (
<Text style={styles.rowText}>
{convertToBytes(
Number.parseInt(zlExport.size),
{
unitSeparator: " ",
},
)}
</Text>
);
const size = (
<Text style={styles.rowText}>
{convertToBytes(
Number.parseInt(zlExport.size),
{
unitSeparator: " ",
},
)}
</Text>
);
const actions = (
<View style={styles.actionsContainer}>
<Button
icon="delete"
color="#CF4238"
onPress={async () => {
setSaveError(null);
const actions = (
<View style={styles.actionsContainer}>
<Button
icon="delete"
color="#CF4238"
onPress={async () => {
setSaveError(null);
const exportId = zlExport.id;
const exportId = zlExport.id;
const success =
await deleteUserExport(exportId);
const success =
await deleteUserExport(exportId);
if (typeof success === "string")
return setSaveError(success);
if (typeof success === "string")
return setSaveError(success);
const newExports = await getUserExports();
const newExports = await getUserExports();
setExports(
typeof newExports === "string"
? null
: newExports,
);
return ToastAndroid.show(
"Successfully deleted the export",
ToastAndroid.SHORT,
);
}}
iconSize={20}
width={32}
height={32}
padding={6}
/>
<Button
icon="download"
color={
zlExport.completed ? "#323ea8" : "#181c28"
}
iconColor={
zlExport.completed ? "white" : "gray"
}
disabled={!zlExport.completed}
onPress={async () => {
const exportId = zlExport.id;
const downloadUrl = `${url}/api/user/export?id=${exportId}`;
let savedExportDownloadUri =
db.get("exportDownloadPath");
if (!savedExportDownloadUri) {
const permissions =
await FileSystem.StorageAccessFramework.requestDirectoryPermissionsAsync();
if (!permissions.granted)
return ToastAndroid.show(
"The permission to save the file was not granted",
ToastAndroid.SHORT,
);
db.set(
"exportDownloadPath",
permissions.directoryUri,
);
savedExportDownloadUri =
permissions.directoryUri;
}
ToastAndroid.show(
"Downloading...",
ToastAndroid.SHORT,
);
const saveUri =
await FileSystem.StorageAccessFramework.createFileAsync(
savedExportDownloadUri,
zlExport.path,
"application/zip",
setExports(
typeof newExports === "string"
? null
: newExports,
);
const downloadResult =
await FileSystem.downloadAsync(
downloadUrl,
`${FileSystem.cacheDirectory}/${zlExport.path}`,
{
headers: {
Authorization: token,
},
},
);
if (!downloadResult.uri)
return ToastAndroid.show(
"Something went wrong while downloading the file",
"Successfully deleted the export",
ToastAndroid.SHORT,
);
}}
iconSize={20}
width={32}
height={32}
padding={6}
/>
<Button
icon="download"
color={
zlExport.completed ? "#323ea8" : "#181c28"
}
iconColor={
zlExport.completed ? "white" : "gray"
}
disabled={!zlExport.completed}
onPress={async () => {
const exportId = zlExport.id;
const downloadUrl = `${url}/api/user/export?id=${exportId}`;
let savedExportDownloadUri =
db.get("exportDownloadPath");
if (!savedExportDownloadUri) {
const permissions =
await FileSystem.StorageAccessFramework.requestDirectoryPermissionsAsync();
if (!permissions.granted)
return ToastAndroid.show(
"The permission to save the file was not granted",
ToastAndroid.SHORT,
);
db.set(
"exportDownloadPath",
permissions.directoryUri,
);
savedExportDownloadUri =
permissions.directoryUri;
}
ToastAndroid.show(
"Downloading...",
ToastAndroid.SHORT,
);
const base64Export =
await FileSystem.readAsStringAsync(
downloadResult.uri,
const saveUri =
await FileSystem.StorageAccessFramework.createFileAsync(
savedExportDownloadUri,
zlExport.path,
"application/zip",
);
const downloadResult =
await FileSystem.downloadAsync(
downloadUrl,
`${FileSystem.cacheDirectory}/${zlExport.path}`,
{
headers: {
Authorization: token,
},
},
);
if (!downloadResult.uri)
return ToastAndroid.show(
"Something went wrong while downloading the file",
ToastAndroid.SHORT,
);
const base64Export =
await FileSystem.readAsStringAsync(
downloadResult.uri,
{
encoding:
FileSystem.EncodingType.Base64,
},
);
await FileSystem.writeAsStringAsync(
saveUri,
base64Export,
{
encoding:
FileSystem.EncodingType.Base64,
},
);
await FileSystem.writeAsStringAsync(
saveUri,
base64Export,
{
encoding:
FileSystem.EncodingType.Base64,
},
);
ToastAndroid.show(
"Successfully downloaded the export",
ToastAndroid.SHORT,
);
}}
iconSize={20}
width={32}
height={32}
padding={6}
/>
</View>
);
ToastAndroid.show(
"Successfully downloaded the export",
ToastAndroid.SHORT,
);
}}
iconSize={20}
width={32}
height={32}
padding={6}
let rowStyle = styles.row;
if (index === 0)
rowStyle = {
...styles.row,
...styles.firstRow,
};
if (index === exports.length - 1)
rowStyle = {
...styles.row,
...styles.lastRow,
};
return (
<Row
key={zlExport.id}
data={[id, startedOn, files, size, actions]}
widthArr={[150, 130, 50, 70, 90]}
style={rowStyle}
textStyle={styles.rowText}
/>
</View>
);
let rowStyle = styles.row;
if (index === 0)
rowStyle = {
...styles.row,
...styles.firstRow,
};
if (index === exports.length - 1)
rowStyle = {
...styles.row,
...styles.lastRow,
};
return (
<Row
key={zlExport.id}
data={[id, startedOn, files, size, actions]}
widthArr={[150, 130, 50, 70, 90]}
style={rowStyle}
textStyle={styles.rowText}
/>
);
})}
</Table>
</ScrollView>
</View>
</ScrollView>
);
})}
</Table>
</ScrollView>
</View>
</ScrollView>
</View>
</View>
</View>
)}
{/* Server Actions */}
<View style={styles.settingGroup}>

View file

@ -328,28 +328,29 @@ export default function Urls() {
</View>
</Popup>
{urls && settings && dashUrl ? (
<View style={{ flex: 1 }}>
<View style={styles.header}>
<Text style={styles.headerText}>URLs</Text>
<View style={styles.headerButtons}>
<Button
onPress={() => {
setCreateNewUrl(true);
}}
icon="add-link"
color="transparent"
iconColor="#2d3f70"
borderColor="#222c47"
borderWidth={2}
iconSize={30}
padding={4}
rippleColor="#283557"
/>
</View>
</View>
<View style={styles.header}>
<Text style={styles.headerText}>URLs</Text>
<View style={styles.headerButtons}>
<Button
onPress={() => {
setCreateNewUrl(true);
}}
icon="add-link"
color="transparent"
iconColor={(urls && settings && dashUrl) ? "#2d3f70" : "#2d3f7055"}
borderColor="#222c47"
borderWidth={2}
iconSize={30}
padding={4}
rippleColor="#283557"
disabled={!urls || !dashUrl || !settings}
/>
</View>
</View>
<View style={{ ...styles.urlsContainer, flex: 1 }}>
<View style={{ flex: 1 }}>
<View style={{ ...styles.urlsContainer, flex: 1 }}>
{urls && settings && dashUrl ? (
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
<View>
<Table>
@ -562,13 +563,13 @@ export default function Urls() {
</ScrollView>
</View>
</ScrollView>
</View>
) : (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading...</Text>
</View>
)}
</View>
) : (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading...</Text>
</View>
)}
</View>
</View>
</View>
);

View file

@ -79,112 +79,121 @@ export default function Home() {
/>
)}
{user && stats && recentFiles ? (
{user ? (
<ScrollView>
<View>
<Text style={styles.headerText}>Recent Files</Text>
<ScrollView horizontal style={styles.scrollView}>
{recentFiles.map((file) => (
<View key={file.id} style={styles.recentFileContainer}>
<FileDisplay
uri={`${url}/raw/${file.name}`}
originalName={file.originalName}
name={file.name}
width={200}
height={200}
passwordProtected={file.password}
onPress={() => setFocusedFile(file)}
/>
</View>
))}
</ScrollView>
</View>
{recentFiles && (
<View>
<Text style={styles.headerText}>Recent Files</Text>
<View>
<Text style={styles.headerText}>Stats</Text>
<ScrollView
horizontal
style={{
...styles.scrollView,
...styles.statsContainer,
}}
>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>Files Uploaded:</Text>
<Text style={styles.statText}>{stats.filesUploaded}</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>Favorite Files:</Text>
<Text style={styles.statText}>{stats.favoriteFiles}</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>Storage Used:</Text>
<Text style={styles.statText}>
{convertToBytes(stats.storageUsed, {
unitSeparator: " ",
})}
</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>Average Storage Used:</Text>
<Text style={styles.statText}>
{convertToBytes(stats.avgStorageUsed, {
unitSeparator: " ",
})}
</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>File Views:</Text>
<Text style={styles.statText}>{stats.views}</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>File Average Views:</Text>
<Text style={styles.statText}>
{Math.round(stats.avgViews)}
</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>Links Created:</Text>
<Text style={styles.statText}>{stats.urlsCreated}</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>Total Link View:</Text>
<Text style={styles.statText}>{stats.urlViews}</Text>
</View>
</ScrollView>
</View>
<View>
<Text style={styles.headerText}>File Types</Text>
<View
style={{
...styles.scrollView,
...styles.fileTypesContainer,
}}
>
<Table>
<Row
data={["File Type", "Count"]}
widthArr={[260, 150]}
textStyle={styles.tableHeadText}
/>
<Rows
data={Object.entries(stats.sortTypeCount).sort(
(a, b) => b[1] - a[1],
)}
widthArr={[260, 150]}
textStyle={styles.tableText}
/>
</Table>
<ScrollView horizontal style={styles.scrollView}>
{recentFiles.map((file) => (
<View key={file.id} style={styles.recentFileContainer}>
<FileDisplay
uri={`${url}/raw/${file.name}`}
originalName={file.originalName}
name={file.name}
width={200}
height={200}
passwordProtected={file.password}
onPress={() => setFocusedFile(file)}
/>
</View>
))}
</ScrollView>
</View>
</View>
)}
{stats && (
<>
<View>
<Text style={styles.headerText}>Stats</Text>
<ScrollView
horizontal
style={{
...styles.scrollView,
...styles.statsContainer,
}}
>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>Files Uploaded:</Text>
<Text style={styles.statText}>{stats.filesUploaded}</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>Favorite Files:</Text>
<Text style={styles.statText}>{stats.favoriteFiles}</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>Storage Used:</Text>
<Text style={styles.statText}>
{convertToBytes(stats.storageUsed, {
unitSeparator: " ",
})}
</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>Average Storage Used:</Text>
<Text style={styles.statText}>
{convertToBytes(stats.avgStorageUsed, {
unitSeparator: " ",
})}
</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>File Views:</Text>
<Text style={styles.statText}>{stats.views}</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>File Average Views:</Text>
<Text style={styles.statText}>
{Math.round(stats.avgViews)}
</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>Links Created:</Text>
<Text style={styles.statText}>{stats.urlsCreated}</Text>
</View>
<View style={styles.statContainer}>
<Text style={styles.subHeaderText}>Total Link View:</Text>
<Text style={styles.statText}>{stats.urlViews}</Text>
</View>
</ScrollView>
</View>
<View>
<Text style={styles.headerText}>File Types</Text>
<View
style={{
...styles.scrollView,
...styles.fileTypesContainer,
}}
>
<Table>
<Row
data={["File Type", "Count"]}
widthArr={[260, 150]}
textStyle={styles.tableHeadText}
/>
<Rows
data={Object.entries(stats.sortTypeCount).sort(
(a, b) => b[1] - a[1],
)}
widthArr={[260, 150]}
textStyle={styles.tableText}
/>
</Table>
</View>
</View>
</>
)}
</ScrollView>
) : (
<View style={styles.loadingContainer}>

View file

@ -1,26 +1,26 @@
import type { SelectProps } from "@/components/Select";
import type { APISettings, ShortenEmbed, UploadEmbed } from "@/types/zipline";
import type { SelectProps } from "@/components/Select";
export const defaultUploadEmbed: UploadEmbed = {
url: false,
color: null,
title: null,
footer: null,
thumbnail: false,
timestamp: false,
description: null,
imageOrVideo: false,
description: null,
timestamp: false,
thumbnail: false,
footer: null,
title: null,
color: null,
url: false,
};
export const defaultShortenEmbed: ShortenEmbed = {
url: false,
color: null,
title: null,
footer: null,
thumbnail: false,
timestamp: false,
description: null,
imageOrVideo: false,
description: null,
timestamp: false,
thumbnail: false,
footer: null,
title: null,
color: null,
url: false,
};
export type SettingPath<T extends keyof APISettings = keyof APISettings> =
@ -162,9 +162,24 @@ export const settingNames: Partial<Record<SettingPath, string>> = {
};
export const formats: SelectProps["data"] = [
{ label: "Random", value: "random" },
{ label: "Date", value: "date" },
{ label: "UUID", value: "uuid" },
{ label: "Use File Name", value: "name" },
{ label: "Gfycat-style Name", value: "gfycat" },
{
label: "Random",
value: "random",
},
{
label: "Date",
value: "date",
},
{
label: "UUID",
value: "uuid",
},
{
label: "Use File Name",
value: "name",
},
{
label: "Gfycat-style Name",
value: "gfycat",
},
];

View file

@ -5,12 +5,44 @@ export const millisecondsHour = 60 * millisecondsMinute;
export const millisecondsDay = 24 * millisecondsHour;
export const dates: SelectProps["data"] = [
{ label: "Never", value: "never", milliseconds: null },
{ label: "30 minutes", value: "30m", milliseconds: 30 * millisecondsMinute },
{ label: "1 hour", value: "1h", milliseconds: millisecondsHour },
{ label: "12 hours", value: "12h", milliseconds: 12 * millisecondsHour },
{ label: "1 day", value: "1d", milliseconds: millisecondsDay },
{ label: "3 days", value: "3d", milliseconds: 3 * millisecondsDay },
{ label: "5 days", value: "5d", milliseconds: 5 * millisecondsDay },
{ label: "7 days", value: "7d", milliseconds: 7 * millisecondsDay },
{
label: "Never",
value: "never",
milliseconds: null,
},
{
label: "30 minutes",
value: "30m",
milliseconds: 30 * millisecondsMinute,
},
{
label: "1 hour",
value: "1h",
milliseconds: millisecondsHour,
},
{
label: "12 hours",
value: "12h",
milliseconds: 12 * millisecondsHour,
},
{
label: "1 day",
value: "1d",
milliseconds: millisecondsDay,
},
{
label: "3 days",
value: "3d",
milliseconds: 3 * millisecondsDay,
},
{
label: "5 days",
value: "5d",
milliseconds: 5 * millisecondsDay,
},
{
label: "7 days",
value: "7d",
milliseconds: 7 * millisecondsDay,
},
];

View file

@ -2,23 +2,23 @@ import type { IconProps } from "@react-native-material/core";
import type MaterialIcons from "@expo/vector-icons/MaterialIcons";
interface SidebarOptionButton {
icon: keyof typeof MaterialIcons.glyphMap;
invitesRoute: boolean;
adminOnly: boolean;
type: "button";
route: string;
name: string;
icon: keyof typeof MaterialIcons.glyphMap;
adminOnly: boolean;
invitesRoute: boolean;
type: "button";
subMenus: [];
}
interface SidebarOptionSelect {
route: null;
name: string;
icon: keyof typeof MaterialIcons.glyphMap;
adminOnly: boolean;
invitesRoute: boolean;
type: "select";
subMenus: Array<SidebarOption>;
invitesRoute: boolean;
adminOnly: boolean;
type: "select";
name: string;
route: null;
}
export type SidebarOption = SidebarOptionButton | SidebarOptionSelect;

View file

@ -8,100 +8,375 @@ export const millisecondsMonth = 30 * millisecondsDay;
export const millisecondsYear = 365 * millisecondsDay;
export const dates: SelectProps["data"] = [
{ label: "Never", value: "never", milliseconds: null },
{ label: "5 minutes", value: "5m", milliseconds: 5 * millisecondsMinute },
{ label: "10 minutes", value: "10m", milliseconds: 10 * millisecondsMinute },
{ label: "15 minutes", value: "15m", milliseconds: 15 * millisecondsMinute },
{ label: "30 minutes", value: "30m", milliseconds: 30 * millisecondsMinute },
{ label: "1 hour", value: "1h", milliseconds: millisecondsHour },
{ label: "2 hours", value: "2h", milliseconds: 2 * millisecondsHour },
{ label: "3 hours", value: "3h", milliseconds: 3 * millisecondsHour },
{ label: "4 hours", value: "4h", milliseconds: 4 * millisecondsHour },
{ label: "5 hours", value: "5h", milliseconds: 5 * millisecondsHour },
{ label: "6 hours", value: "6h", milliseconds: 6 * millisecondsHour },
{ label: "8 hours", value: "8h", milliseconds: 8 * millisecondsHour },
{ label: "12 hours", value: "12h", milliseconds: 12 * millisecondsHour },
{ label: "1 day", value: "1d", milliseconds: millisecondsDay },
{ label: "3 days", value: "3d", milliseconds: 3 * millisecondsDay },
{ label: "5 days", value: "5d", milliseconds: 5 * millisecondsDay },
{ label: "1 week", value: "1w", milliseconds: millisecondsWeek },
{ label: "1.5 weeks", value: "1.5w", milliseconds: 1.5 * millisecondsWeek },
{ label: "2 weeks", value: "2w", milliseconds: 2 * millisecondsWeek },
{ label: "3 weeks", value: "3w", milliseconds: 3 * millisecondsWeek },
{ label: "1 month", value: "1M", milliseconds: millisecondsMonth },
{ label: "1.5 months", value: "1.5M", milliseconds: 1.5 * millisecondsMonth },
{ label: "2 months", value: "2M", milliseconds: 2 * millisecondsMonth },
{ label: "3 months", value: "3M", milliseconds: 3 * millisecondsMonth },
{ label: "4 months", value: "4M", milliseconds: 4 * millisecondsMonth },
{ label: "6 months", value: "6M", milliseconds: 6 * millisecondsMonth },
{ label: "1 year", value: "1y", milliseconds: millisecondsYear },
{
label: "Never",
value: "never",
milliseconds: null,
},
{
label: "5 minutes",
value: "5m",
milliseconds: 5 * millisecondsMinute,
},
{
label: "10 minutes",
value: "10m",
milliseconds: 10 * millisecondsMinute,
},
{
label: "15 minutes",
value: "15m",
milliseconds: 15 * millisecondsMinute,
},
{
label: "30 minutes",
value: "30m",
milliseconds: 30 * millisecondsMinute,
},
{
label: "1 hour",
value: "1h",
milliseconds: millisecondsHour,
},
{
label: "2 hours",
value: "2h",
milliseconds: 2 * millisecondsHour,
},
{
label: "3 hours",
value: "3h",
milliseconds: 3 * millisecondsHour,
},
{
label: "4 hours",
value: "4h",
milliseconds: 4 * millisecondsHour,
},
{
label: "5 hours",
value: "5h",
milliseconds: 5 * millisecondsHour,
},
{
label: "6 hours",
value: "6h",
milliseconds: 6 * millisecondsHour,
},
{
label: "8 hours",
value: "8h",
milliseconds: 8 * millisecondsHour,
},
{
label: "12 hours",
value: "12h",
milliseconds: 12 * millisecondsHour,
},
{
label: "1 day",
value: "1d",
milliseconds: millisecondsDay,
},
{
label: "3 days",
value: "3d",
milliseconds: 3 * millisecondsDay,
},
{
label: "5 days",
value: "5d",
milliseconds: 5 * millisecondsDay,
},
{
label: "1 week",
value: "1w",
milliseconds: millisecondsWeek,
},
{
label: "1.5 weeks",
value: "1.5w",
milliseconds: 1.5 * millisecondsWeek,
},
{
label: "2 weeks",
value: "2w",
milliseconds: 2 * millisecondsWeek,
},
{
label: "3 weeks",
value: "3w",
milliseconds: 3 * millisecondsWeek,
},
{
label: "1 month",
value: "1M",
milliseconds: millisecondsMonth,
},
{
label: "1.5 months",
value: "1.5M",
milliseconds: 1.5 * millisecondsMonth,
},
{
label: "2 months",
value: "2M",
milliseconds: 2 * millisecondsMonth,
},
{
label: "3 months",
value: "3M",
milliseconds: 3 * millisecondsMonth,
},
{
label: "4 months",
value: "4M",
milliseconds: 4 * millisecondsMonth,
},
{
label: "6 months",
value: "6M",
milliseconds: 6 * millisecondsMonth,
},
{
label: "1 year",
value: "1y",
milliseconds: millisecondsYear,
},
];
export const formats: SelectProps["data"] = [
{ label: "Random", value: "random" },
{ label: "Date", value: "date" },
{ label: "UUID", value: "uuid" },
{ label: "Use File Name", value: "name" },
{ label: "Gfycat-style Name", value: "gfycat" },
{
label: "Random",
value: "random",
},
{
label: "Date",
value: "date",
},
{
label: "UUID",
value: "uuid",
},
{
label: "Use File Name",
value: "name",
},
{
label: "Gfycat-style Name",
value: "gfycat",
},
];
export const avaibleTextMimetypes: SelectProps["data"] = [
{ label: "HTML", value: "html", mimetype: "text/x-zipline-html" },
{ label: "CSS", value: "css", mimetype: "text/x-zipline-css" },
{ label: "C++", value: "cpp", mimetype: "text/x-zipline-c++src" },
{ label: "JavaScript", value: "js", mimetype: "text/x-zipline-javascript" },
{ label: "Python", value: "py", mimetype: "text/x-zipline-python" },
{ label: "Ruby", value: "rb", mimetype: "text/x-zipline-ruby" },
{ label: "Java", value: "java", mimetype: "text/x-zipline-java" },
{ label: "Markdown", value: "md", mimetype: "text/x-zipline-markdown" },
{ label: "C", value: "c", mimetype: "text/x-zipline-csrc" },
{ label: "PHP", value: "php", mimetype: "text/x-zipline-httpd-php" },
{ label: "Sass", value: "sass", mimetype: "text/x-zipline-sass" },
{ label: "SCSS", value: "scss", mimetype: "text/x-zipline-scss" },
{ label: "Swift", value: "swift", mimetype: "text/x-zipline-swift" },
{ label: "TypeScript", value: "ts", mimetype: "text/x-zipline-typescript" },
{ label: "Go", value: "go", mimetype: "text/x-zipline-go" },
{ label: "Rust", value: "rs", mimetype: "text/x-zipline-rustsrc" },
{ label: "Bash", value: "sh", mimetype: "text/x-zipline-sh" },
{ label: "JSON", value: "json", mimetype: "text/x-zipline-json" },
{ label: "PowerShell", value: "ps1", mimetype: "text/x-zipline-powershell" },
{ label: "SQL", value: "sql", mimetype: "text/x-zipline-sql" },
{ label: "YAML", value: "yaml", mimetype: "text/x-zipline-yaml" },
{
label: "HTML",
value: "html",
mimetype: "text/x-zipline-html",
},
{
label: "CSS",
value: "css",
mimetype: "text/x-zipline-css",
},
{
label: "C++",
value: "cpp",
mimetype: "text/x-zipline-c++src",
},
{
label: "JavaScript",
value: "js",
mimetype: "text/x-zipline-javascript",
},
{
label: "Python",
value: "py",
mimetype: "text/x-zipline-python",
},
{
label: "Ruby",
value: "rb",
mimetype: "text/x-zipline-ruby",
},
{
label: "Java",
value: "java",
mimetype: "text/x-zipline-java",
},
{
label: "Markdown",
value: "md",
mimetype: "text/x-zipline-markdown",
},
{
label: "C",
value: "c",
mimetype: "text/x-zipline-csrc",
},
{
label: "PHP",
value: "php",
mimetype: "text/x-zipline-httpd-php",
},
{
label: "Sass",
value: "sass",
mimetype: "text/x-zipline-sass",
},
{
label: "SCSS",
value: "scss",
mimetype: "text/x-zipline-scss",
},
{
label: "Swift",
value: "swift",
mimetype: "text/x-zipline-swift",
},
{
label: "TypeScript",
value: "ts",
mimetype: "text/x-zipline-typescript",
},
{
label: "Go",
value: "go",
mimetype: "text/x-zipline-go",
},
{
label: "Rust",
value: "rs",
mimetype: "text/x-zipline-rustsrc",
},
{
label: "Bash",
value: "sh",
mimetype: "text/x-zipline-sh",
},
{
label: "JSON",
value: "json",
mimetype: "text/x-zipline-json",
},
{
label: "PowerShell",
value: "ps1",
mimetype: "text/x-zipline-powershell",
},
{
label: "SQL",
value: "sql",
mimetype: "text/x-zipline-sql",
},
{
label: "YAML",
value: "yaml",
mimetype: "text/x-zipline-yaml",
},
{
label: "Dockerfile",
value: "dockerfile",
mimetype: "text/x-zipline-dockerfile",
},
{ label: "Lua", value: "lua", mimetype: "text/x-zipline-lua" },
{
label: "Lua",
value: "lua",
mimetype: "text/x-zipline-lua",
},
{
label: "NGINX Config File",
value: "conf",
mimetype: "text/x-zipline-nginx-conf",
},
{ label: "Perl", value: "pl", mimetype: "text/x-zipline-perl" },
{ label: "R", value: "r", mimetype: "text/x-zipline-rsrc" },
{ label: "Scala", value: "scala", mimetype: "text/x-zipline-scala" },
{ label: "Groovy", value: "groovy", mimetype: "text/x-zipline-groovy" },
{ label: "Kotlin", value: "kt", mimetype: "text/x-zipline-kotlin" },
{ label: "Haskell", value: "hs", mimetype: "text/x-zipline-haskell" },
{ label: "Elixir", value: "ex", mimetype: "text/x-zipline-elixir" },
{ label: "Vim", value: "vim", mimetype: "text/x-zipline-vim" },
{ label: "MATLAB", value: "m", mimetype: "text/x-zipline-matlab" },
{ label: "Dart", value: "dart", mimetype: "text/x-zipline-dart" },
{
label: "Perl",
value: "pl",
mimetype: "text/x-zipline-perl",
},
{
label: "R",
value: "r",
mimetype: "text/x-zipline-rsrc",
},
{
label: "Scala",
value: "scala",
mimetype: "text/x-zipline-scala",
},
{
label: "Groovy",
value: "groovy",
mimetype: "text/x-zipline-groovy",
},
{
label: "Kotlin",
value: "kt",
mimetype: "text/x-zipline-kotlin",
},
{
label: "Haskell",
value: "hs",
mimetype: "text/x-zipline-haskell",
},
{
label: "Elixir",
value: "ex",
mimetype: "text/x-zipline-elixir",
},
{
label: "Vim",
value: "vim",
mimetype: "text/x-zipline-vim",
},
{
label: "MATLAB",
value: "m",
mimetype: "text/x-zipline-matlab",
},
{
label: "Dart",
value: "dart",
mimetype: "text/x-zipline-dart",
},
{
label: "Handlebars",
value: "hbs",
mimetype: "text/x-zipline-handlebars-template",
},
{ label: "HCL", value: "hcl", mimetype: "text/x-zipline-hcl" },
{ label: "HTTP", value: "http", mimetype: "text/x-zipline-http" },
{ label: "INI", value: "ini", mimetype: "text/x-zipline-ini" },
{ label: "JSX", value: "jsx", mimetype: "text/x-zipline-jsx" },
{
label: "HCL",
value: "hcl",
mimetype: "text/x-zipline-hcl",
},
{
label: "HTTP",
value: "http",
mimetype: "text/x-zipline-http",
},
{
label: "INI",
value: "ini",
mimetype: "text/x-zipline-ini",
},
{
label: "JSX",
value: "jsx",
mimetype: "text/x-zipline-jsx",
},
{
label: "CoffeeScript",
value: "coffee",
mimetype: "text/x-zipline-coffeescript",
},
{ label: "LaTeX (KaTeX)", value: "tex", mimetype: "text/x-zipline-latex" },
{ label: "Plain Text", value: "txt", mimetype: "text/x-zipline-plain" },
{
label: "LaTeX (KaTeX)",
value: "tex",
mimetype: "text/x-zipline-latex",
},
{
label: "Plain Text",
value: "txt",
mimetype: "text/x-zipline-plain",
},
];

View file

@ -28,16 +28,16 @@ export const fileQuotaTypes: SelectProps["data"] = [
];
export const templateUser: APIUser = {
avatar: null,
createdAt: new Date().toISOString(),
id: "1234567890",
oauthProviders: [],
passkeys: [],
quota: null,
role: "USER",
sessions: [],
totpSecret: null,
updatedAt: new Date().toISOString(),
username: "Placeholder",
oauthProviders: [],
totpSecret: null,
id: "1234567890",
passkeys: [],
sessions: [],
avatar: null,
role: "USER",
quota: null,
view: {},
};

View file

@ -142,11 +142,11 @@ export function toRgba(color: string): RGBA {
};
}
function gammaCorrect(c: number) {
function gammaCorrect(c: number): number {
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
}
function getLightnessFromOklch(oklchColor: string) {
function getLightnessFromOklch(oklchColor: string): number | null {
const match = oklchColor.match(/oklch\((.*?)%\s/);
return match ? Number.parseFloat(match[1]) : null;
}
@ -169,7 +169,7 @@ export function luminance(color: string): number {
return 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear;
}
export function isLightColor(color: string, luminanceThreshold = 0.179) {
export function isLightColor(color: string, luminanceThreshold = 0.179): boolean {
if (color.startsWith("var(")) {
return false;
}
@ -177,7 +177,7 @@ export function isLightColor(color: string, luminanceThreshold = 0.179) {
return luminance(color) > luminanceThreshold;
}
export function rgbaToHex(r: number, g: number, b: number, a = 1) {
export function rgbaToHex(r: number, g: number, b: number, a = 1): string {
r = Math.min(255, Math.max(0, r));
g = Math.min(255, Math.max(0, g));
b = Math.min(255, Math.max(0, b));

View file

@ -1,10 +1,10 @@
import { isLightColor, rgbaToHex, toRgba } from "@/functions/color";
import mimetypesJSON from "@/assets/mimetypes.json";
import type { Mimetypes } from "@/types/mimetypes";
import bytes from "bytes";
import ms, { type FormatOptions } from "enhanced-ms";
import * as FileSystem from "expo-file-system";
import { isLightColor, rgbaToHex, toRgba } from "./color";
import { namedColors } from "@/constants/colors";
import * as FileSystem from "expo-file-system";
import ms from "enhanced-ms";
import bytes from "bytes";
const mimetypes = mimetypesJSON as Mimetypes;
@ -117,7 +117,8 @@ export function convertToBytes(
export function convertToTime(
value: string | number,
options?: FormatOptions,
// biome-ignore lint/suspicious/noExplicitAny: enhanced-ms does not export the FormatOptions interface
options?: any,
): string | null {
if (typeof value === "number") return ms(value);

View file

@ -1,10 +1,10 @@
import axios, { type AxiosError } from "axios";
import * as db from "@/functions/database";
import type {
APILoginResponse,
APISelfUser,
APITokenResponse,
} from "@/types/zipline";
import axios, { type AxiosError } from "axios";
export async function isAuthenticated(): Promise<APISelfUser["role"] | false> {
const url = db.get("url");

View file

@ -1,6 +1,6 @@
import type { APIExports } from "@/types/zipline";
import * as db from "@/functions/database";
import axios, { type AxiosError } from "axios";
import * as db from "@/functions/database";
// GET /api/user/export
export async function getUserExports(): Promise<APIExports | string> {

View file

@ -1,3 +1,6 @@
import * as FileSystem from "expo-file-system";
import axios, { type AxiosError } from "axios";
import * as db from "@/functions/database";
import type {
APIFile,
APIFiles,
@ -5,9 +8,6 @@ import type {
APITag,
APIUploadFile,
} from "@/types/zipline";
import * as FileSystem from "expo-file-system";
import * as db from "@/functions/database";
import axios, { type AxiosError } from "axios";
export interface GetFilesOptions {
id?: string;

View file

@ -1,6 +1,6 @@
import type { APIFoldersNoIncl, APIFolders, APIFolder } from "@/types/zipline";
import * as db from "@/functions/database";
import axios, { type AxiosError } from "axios";
import * as db from "@/functions/database";
// GET /api/user/folders
export async function getFolders<T extends boolean | undefined = undefined>(

View file

@ -1,6 +1,6 @@
import type { APIInvite, APIInvites } from "@/types/zipline";
import * as db from "@/functions/database";
import axios, { type AxiosError } from "axios";
import * as db from "@/functions/database";
// GET /api/auth/invites
export async function getInvites(): Promise<APIInvites | string> {

View file

@ -1,6 +1,6 @@
import * as db from "@/functions/database";
import type { APIFile, ServerActionResponse } from "@/types/zipline";
import axios, { type AxiosError } from "axios";
import * as db from "@/functions/database";
export async function clearZeroByteFiles(): Promise<
ServerActionResponse | string

View file

@ -1,7 +1,7 @@
import type { APISettings } from "@/types/zipline";
import * as db from "@/functions/database";
import axios, { type AxiosError } from "axios";
import { settingNames } from "@/constants/adminSettings";
import type { APISettings } from "@/types/zipline";
import axios, { type AxiosError } from "axios";
import * as db from "@/functions/database";
// GET /api/server/settings
export async function getSettings(): Promise<APISettings | string> {

View file

@ -1,6 +1,6 @@
import type { APIUserStats, APIStats } from "@/types/zipline";
import * as db from "@/functions/database";
import axios, { type AxiosError } from "axios";
import * as db from "@/functions/database";
export interface StatsProps {
from?: string;

View file

@ -1,6 +1,6 @@
import type { APITag, APITags } from "@/types/zipline";
import * as db from "@/functions/database";
import axios, { type AxiosError } from "axios";
import * as db from "@/functions/database";
// GET /api/user/tags
export async function getTags(): Promise<APITags | string> {

View file

@ -1,6 +1,6 @@
import type { APIURLs, APIURL } from "@/types/zipline";
import * as db from "@/functions/database";
import axios, { type AxiosError } from "axios";
import * as db from "@/functions/database";
// GET /user/urls
export async function getURLs(): Promise<APIURLs | string> {

View file

@ -1,6 +1,6 @@
import type { APIRecentFiles, APISelfUser, APIUser } from "@/types/zipline";
import * as db from "@/functions/database";
import axios, { type AxiosError } from "axios";
import * as db from "@/functions/database";
// GET /api/user
export async function getCurrentUser(): Promise<APISelfUser | string> {

View file

@ -1,5 +1,5 @@
import * as db from "@/functions/database";
import axios, { type AxiosError } from "axios";
import * as db from "@/functions/database";
import type {
APIUsersNoIncl,
APIUsers,

View file

@ -1,6 +1,6 @@
import { isAuthenticated } from "@/functions/zipline/auth";
import type { APIUser } from "@/types/zipline";
import { useFocusEffect, useRouter } from "expo-router";
import type { APIUser } from "@/types/zipline";
import { roles } from "@/constants/auth";
export const useAuth = (minimumRole: APIUser["role"] = "USER") => {

View file

@ -1,6 +1,6 @@
import { useRouter } from "expo-router";
import { useShareIntentContext } from "expo-share-intent";
import { useEffect, useState } from "react";
import { useRouter } from "expo-router";
export const useShareIntent = (skipRedirect = false) => {
const router = useRouter();

View file

@ -1,13 +1,13 @@
import { type ExternalPathString, useRouter } from "expo-router";
// import * as DocumentPicker from "expo-document-picker";
import { repoName, username } from "@/constants/updates";
import { version as appVersion } from "@/package.json";
import axios from "axios";
import { useEffect, useState } from "react";
import semver from "semver";
import * as db from "@/functions/database";
// import * as DocumentPicker from "expo-document-picker";
// import * as FileSystem from "expo-file-system";
import { useEffect, useState } from "react";
import { ToastAndroid } from "react-native";
import { type ExternalPathString, useRouter } from "expo-router";
import * as db from "@/functions/database";
import semver from "semver";
import axios from "axios";
export function useAppUpdates() {
const router = useRouter();

View file

@ -5,16 +5,11 @@
"scripts": {
"start": "expo start",
"start:dev": "APP_VARIANT=development EDGE_PATH=/home/stef/.local/bin/google-chrome expo start --dev-client",
"start:release": "APP_VARIANT=devrelease EDGE_PATH=/home/stef/.local/bin/google-chrome expo start --dev-client",
"prebuild": "expo prebuild --no-install --clean",
"prebuild:dev": "APP_VARIANT=development expo prebuild --no-install --clean",
"prebuild:release": "APP_VARIANT=devrelease expo prebuild --no-install --clean",
"prebuild": "EXPO_NO_GIT_STATUS=1 expo prebuild --no-install --clean --platform android",
"prebuild:dev": "EXPO_NO_GIT_STATUS=1 APP_VARIANT=development expo prebuild --no-install --clean --platform android",
"run:android": "expo run:android",
"run:android:release": "expo run:android --variant release",
"test": "jest --watchAll",
"lint": "expo lint",
"android": "expo run:android",
"ios": "expo run:ios"
"lint": "expo lint"
},
"jest": {
"preset": "jest-expo"

View file

@ -28,6 +28,7 @@ export const styles = StyleSheet.create({
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 10
},
dateRangeText: {
color: "gray",
@ -36,13 +37,13 @@ export const styles = StyleSheet.create({
marginTop: 10,
},
scrollView: {
marginTop: 15,
borderStyle: "solid",
borderWidth: 2,
borderColor: "#222c47",
marginHorizontal: 10,
borderRadius: 15,
padding: 15,
marginBottom: 10
},
statsContainer: {
paddingHorizontal: 7,