Platform page UX improvements (#3009)

* chore: initial fixes from app redesign merge

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: ccpa hydration error

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: migrate tailwind to esm

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: default platform selection to current mc version

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: navigating and installing content

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: respect sentence case

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: match new page padding

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: allow user to erase all data when installing from modpack

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: hide hide installed content check if modpack search

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: wording

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: make erase data toggle more prominent

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
This commit is contained in:
Evan Song 2024-12-12 16:12:54 -07:00 committed by GitHub
parent 1f060b8513
commit e86c9df39d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 159 additions and 118 deletions

View File

@ -21,108 +21,110 @@
within the last twelve (12) months: within the last twelve (12) months:
</p> </p>
<table> <table>
<tr> <tbody>
<th>Category</th> <tr>
<th>Examples</th> <th>Category</th>
<th>Collected</th> <th>Examples</th>
</tr> <th>Collected</th>
<tr> </tr>
<td>A. Identifiers.</td> <tr>
<td> <td>A. Identifiers.</td>
A real name, alias, postal address, unique personal identifier, online identifier, <td>
Internet Protocol address, email address, account name, Social Security number, driver's A real name, alias, postal address, unique personal identifier, online identifier,
license number, passport number, or other similar identifiers. Internet Protocol address, email address, account name, Social Security number, driver's
</td> license number, passport number, or other similar identifiers.
<td>YES</td> </td>
</tr> <td>YES</td>
<tr> </tr>
<td> <tr>
B. Personal information categories listed in the California Customer Records statute (Cal. <td>
Civ. Code § 1798.80(e)). B. Personal information categories listed in the California Customer Records statute
</td> (Cal. Civ. Code § 1798.80(e)).
<td> </td>
A name, signature, Social Security number, physical characteristics or description, <td>
address, telephone number, passport number, driver's license or state identification card A name, signature, Social Security number, physical characteristics or description,
number, insurance policy number, education, employment, employment history, bank account address, telephone number, passport number, driver's license or state identification
number, credit card number, debit card number, or any other financial information, medical card number, insurance policy number, education, employment, employment history, bank
information, or health insurance information. <br /><br /> account number, credit card number, debit card number, or any other financial
Some personal information included in this category may overlap with other categories. information, medical information, or health insurance information. <br /><br />
</td> Some personal information included in this category may overlap with other categories.
<td>NO</td> </td>
</tr> <td>NO</td>
<tr> </tr>
<td>C. Protected classification characteristics.</td> <tr>
<td> <td>C. Protected classification characteristics.</td>
Age (40 years or older), race, color, ancestry, national origin, citizenship, religion or <td>
creed, marital status, medical condition, physical or mental disability, sex (including Age (40 years or older), race, color, ancestry, national origin, citizenship, religion
gender, gender identity, gender expression, pregnancy or childbirth and related medical or creed, marital status, medical condition, physical or mental disability, sex
conditions), sexual orientation, veteran or military status, genetic information (including gender, gender identity, gender expression, pregnancy or childbirth and
(including familial genetic information). related medical conditions), sexual orientation, veteran or military status, genetic
</td> information (including familial genetic information).
<td>NO</td> </td>
</tr> <td>NO</td>
<tr> </tr>
<td>D. Commercial information.</td> <tr>
<td> <td>D. Commercial information.</td>
Records of personal property, products or services purchased, obtained, or considered, or <td>
other purchasing or consuming histories or tendencies. Records of personal property, products or services purchased, obtained, or considered,
</td> or other purchasing or consuming histories or tendencies.
<td>NO</td> </td>
</tr> <td>NO</td>
<tr> </tr>
<td>E. Biometric information.</td> <tr>
<td> <td>E. Biometric information.</td>
Genetic, physiological, behavioral, and biological characteristics, or activity patterns <td>
used to extract a template or other identifier or identifying information, such as, Genetic, physiological, behavioral, and biological characteristics, or activity patterns
fingerprints, faceprints, and voiceprints, iris or retina scans, keystroke, gait, or other used to extract a template or other identifier or identifying information, such as,
physical patterns, and sleep, health, or exercise data. fingerprints, faceprints, and voiceprints, iris or retina scans, keystroke, gait, or
</td> other physical patterns, and sleep, health, or exercise data.
<td>NO</td> </td>
</tr> <td>NO</td>
<tr> </tr>
<td>F. Internet or other similar network activity.</td> <tr>
<td> <td>F. Internet or other similar network activity.</td>
Browsing history, search history, information on a consumer's interaction with a website, <td>
application, or advertisement. Browsing history, search history, information on a consumer's interaction with a
</td> website, application, or advertisement.
<td>YES</td> </td>
</tr> <td>YES</td>
<tr> </tr>
<td>G. Geolocation data.</td> <tr>
<td>Physical location or movements.</td> <td>G. Geolocation data.</td>
<td>YES</td> <td>Physical location or movements.</td>
</tr> <td>YES</td>
<tr> </tr>
<td>H. Sensory data.</td> <tr>
<td>Audio, electronic, visual, thermal, olfactory, or similar information.</td> <td>H. Sensory data.</td>
<td>NO</td> <td>Audio, electronic, visual, thermal, olfactory, or similar information.</td>
</tr> <td>NO</td>
<tr> </tr>
<td>I. Professional or employment-related information.</td> <tr>
<td>Current or past job history or performance evaluations.</td> <td>I. Professional or employment-related information.</td>
<td>NO</td> <td>Current or past job history or performance evaluations.</td>
</tr> <td>NO</td>
<tr> </tr>
<td> <tr>
J. Non-public education information (per the Family Educational Rights and Privacy Act (20 <td>
U.S.C. Section 1232g, 34 C.F.R. Part 99)). J. Non-public education information (per the Family Educational Rights and Privacy Act
</td> (20 U.S.C. Section 1232g, 34 C.F.R. Part 99)).
<td> </td>
Education records directly related to a student maintained by an educational institution <td>
or party acting on its behalf, such as grades, transcripts, class lists, student Education records directly related to a student maintained by an educational institution
schedules, student identification codes, student financial information, or student or party acting on its behalf, such as grades, transcripts, class lists, student
disciplinary records. schedules, student identification codes, student financial information, or student
</td> disciplinary records.
<td>NO</td> </td>
</tr> <td>NO</td>
<tr> </tr>
<td>K. Inferences drawn from other personal information.</td> <tr>
<td> <td>K. Inferences drawn from other personal information.</td>
Profile reflecting a person's preferences, characteristics, psychological trends, <td>
predispositions, behavior, attitudes, intelligence, abilities, and aptitudes. Profile reflecting a person's preferences, characteristics, psychological trends,
</td> predispositions, behavior, attitudes, intelligence, abilities, and aptitudes.
<td>NO</td> </td>
</tr> <td>NO</td>
</tr>
</tbody>
</table> </table>
<p>Personal information does not include:</p> <p>Personal information does not include:</p>
<ul> <ul>

View File

@ -84,7 +84,26 @@
</button> </button>
</ButtonStyled> </ButtonStyled>
</div> </div>
<div v-if="server" class="rounded-2xl bg-bg-raised p-4"> <div v-if="server && projectType.id === 'modpack'" class="rounded-2xl bg-bg-raised">
<div class="flex flex-row items-center gap-2 px-6 py-4 text-contrast">
<h3 class="m-0 text-lg">Options</h3>
</div>
<div class="flex flex-row items-center justify-between gap-2 px-6">
<label for="erase-data-on-install"> Erase all data on install </label>
<input
id="erase-data-on-install"
v-model="eraseDataOnInstall"
label="Erase all data on install"
class="switch stylized-toggle flex-none"
type="checkbox"
/>
</div>
<div class="px-6 py-4 text-sm">
If enabled, existing mods, worlds, and configurations, will be deleted before installing
the selected modpack.
</div>
</div>
<div v-if="server && projectType.id !== 'modpack'" class="rounded-2xl bg-bg-raised p-4">
<Checkbox <Checkbox
v-model="serverHideInstalled" v-model="serverHideInstalled"
label="Hide installed content" label="Hide installed content"
@ -238,7 +257,7 @@
<button <button
v-if=" v-if="
result.installed || result.installed ||
server.mods.data.find((x) => x.project_id === result.project_id) || server.content.data.find((x) => x.project_id === result.project_id) ||
server.general?.project?.id === result.project_id server.general?.project?.id === result.project_id
" "
disabled disabled
@ -328,6 +347,7 @@ const projectTypes = computed(() => [projectType.value.id]);
const server = ref(); const server = ref();
const serverHideInstalled = ref(false); const serverHideInstalled = ref(false);
const eraseDataOnInstall = ref(false);
const PERSISTENT_QUERY_PARAMS = ["sid", "shi"]; const PERSISTENT_QUERY_PARAMS = ["sid", "shi"];
@ -342,7 +362,7 @@ async function updateServerContext() {
if (!auth.value.user) { if (!auth.value.user) {
router.push("/auth/sign-in?redirect=" + encodeURIComponent(route.fullPath)); router.push("/auth/sign-in?redirect=" + encodeURIComponent(route.fullPath));
} else if (route.query.sid !== null) { } else if (route.query.sid !== null) {
server.value = await usePyroServer(route.query.sid, ["general", "mods"]); server.value = await usePyroServer(route.query.sid, ["general", "content"]);
} }
} }
@ -382,7 +402,7 @@ const serverFilters = computed(() => {
} }
if (serverHideInstalled.value) { if (serverHideInstalled.value) {
const installedMods = server.value.mods?.data const installedMods = server.value.content?.data
.filter((x) => x.project_id) .filter((x) => x.project_id)
.map((x) => x.project_id); .map((x) => x.project_id);
@ -461,7 +481,14 @@ async function serverInstall(project) {
) ?? versions[0]; ) ?? versions[0];
if (projectType.value.id === "modpack") { if (projectType.value.id === "modpack") {
await server.value.general?.reinstall(route.query.sid, false, project.project_id, version.id); await server.value.general?.reinstall(
route.query.sid,
false,
project.project_id,
version.id,
undefined,
eraseDataOnInstall.value,
);
project.installed = true; project.installed = true;
navigateTo(`/servers/manage/${route.query.sid}/options/loader`); navigateTo(`/servers/manage/${route.query.sid}/options/loader`);
} else if (projectType.value.id === "mod") { } else if (projectType.value.id === "mod") {
@ -817,4 +844,8 @@ useSeoMeta({
mask-image: linear-gradient(to bottom, black, transparent); mask-image: linear-gradient(to bottom, black, transparent);
opacity: 0.25; opacity: 0.25;
} }
.stylized-toggle:checked::after {
background: var(--color-accent-contrast) !important;
}
</style> </style>

View File

@ -86,10 +86,12 @@
</ButtonStyled> </ButtonStyled>
</div> </div>
</div> </div>
<!-- SERVER START -->
<div <div
v-else-if="serverData" v-else-if="serverData"
data-pyro-server-manager-root data-pyro-server-manager-root
class="experimental-styles-within mobile-blurred-servericon relative mx-auto box-border flex min-h-screen w-full min-w-0 max-w-[1280px] flex-col gap-6 px-3 transition-all duration-300" class="experimental-styles-within mobile-blurred-servericon relative mx-auto box-border flex min-h-screen w-full min-w-0 max-w-[1280px] flex-col gap-6 px-6 transition-all duration-300"
:style="{ :style="{
'--server-bg-image': serverData.image '--server-bg-image': serverData.image
? `url(${serverData.image})` ? `url(${serverData.image})`
@ -302,6 +304,7 @@ import {
import DOMPurify from "dompurify"; import DOMPurify from "dompurify";
import { ButtonStyled } from "@modrinth/ui"; import { ButtonStyled } from "@modrinth/ui";
import { Intercom, shutdown } from "@intercom/messenger-js-sdk"; import { Intercom, shutdown } from "@intercom/messenger-js-sdk";
import { reloadNuxtApp } from "#app";
import type { ServerState, Stats, WSEvent, WSInstallationResultEvent } from "~/types/servers"; import type { ServerState, Stats, WSEvent, WSInstallationResultEvent } from "~/types/servers";
import { usePyroConsole } from "~/store/console.ts"; import { usePyroConsole } from "~/store/console.ts";

View File

@ -237,7 +237,7 @@ import { ref, computed } from "vue";
import type { Server } from "~/composables/pyroServers"; import type { Server } from "~/composables/pyroServers";
const props = defineProps<{ const props = defineProps<{
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>; server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
isServerRunning: boolean; isServerRunning: boolean;
}>(); }>();

View File

@ -102,10 +102,10 @@
<ButtonStyled v-if="hasMods" color="brand" type="outlined"> <ButtonStyled v-if="hasMods" color="brand" type="outlined">
<nuxt-link <nuxt-link
class="w-full text-nowrap sm:w-fit" class="w-full text-nowrap sm:w-fit"
:to="`/${type}s?sid=${props.server.serverId}`" :to="`/${type.toLocaleLowerCase()}s?sid=${props.server.serverId}`"
> >
<PlusIcon /> <PlusIcon />
Add {{ type }} Add {{ type.toLocaleLowerCase() }}
</nuxt-link> </nuxt-link>
</ButtonStyled> </ButtonStyled>
</div> </div>
@ -241,9 +241,9 @@
<p class="m-0 font-bold text-contrast">No {{ type }}s found!</p> <p class="m-0 font-bold text-contrast">No {{ type }}s found!</p>
<p class="m-0">Add some {{ type }}s to your server to manage them here.</p> <p class="m-0">Add some {{ type }}s to your server to manage them here.</p>
<ButtonStyled color="brand"> <ButtonStyled color="brand">
<NuxtLink :to="`/${type}s?sid=${props.server.serverId}`"> <NuxtLink :to="`/${type.toLocaleLowerCase()}s?sid=${props.server.serverId}`">
<PlusIcon /> <PlusIcon />
Add {{ type }} Add {{ type.toLocaleLowerCase() }}
</NuxtLink> </NuxtLink>
</ButtonStyled> </ButtonStyled>
</div> </div>
@ -299,7 +299,7 @@ const props = defineProps<{
const type = computed(() => { const type = computed(() => {
const loader = props.server.general?.loader?.toLowerCase(); const loader = props.server.general?.loader?.toLowerCase();
return loader === "paper" || loader === "purpur" ? "plugin" : "mod"; return loader === "paper" || loader === "purpur" ? "Plugin" : "Mod";
}); });
interface Mod { interface Mod {
@ -460,7 +460,10 @@ async function removeMod(mod: Mod) {
mod.changing = true; mod.changing = true;
try { try {
await props.server.content?.remove(type.value, `/${type.value}s/${mod.filename}`); await props.server.content?.remove(
type.value as "Mod" | "Plugin",
`/${type.value.toLowerCase()}s/${mod.filename}`,
);
await props.server.refresh(["general", "content"]); await props.server.refresh(["general", "content"]);
} catch (error) { } catch (error) {
console.error("Error removing mod:", error); console.error("Error removing mod:", error);

View File

@ -198,7 +198,7 @@ type ServerProps = {
exit_code?: number; exit_code?: number;
}; };
isServerRunning: boolean; isServerRunning: boolean;
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>; server: Server<["general", "content", "backups", "network", "startup", "ws", "fs"]>;
}; };
const props = defineProps<ServerProps>(); const props = defineProps<ServerProps>();

View File

@ -703,7 +703,7 @@ watch(selectedMCVersion, async () => {
}); });
const onShow = () => { const onShow = () => {
selectedMCVersion.value = ""; selectedMCVersion.value = props.server.general?.mc_version || "";
selectedLoaderVersion.value = ""; selectedLoaderVersion.value = "";
}; };

View File

@ -1,5 +1,5 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { const config = {
content: [ content: [
"./src/components/**/*.{js,vue,ts}", "./src/components/**/*.{js,vue,ts}",
"./src/layouts/**/*.vue", "./src/layouts/**/*.vue",
@ -152,3 +152,5 @@ module.exports = {
preflight: false, preflight: false,
}, },
}; };
export default config;