Skip to content

Commit ce8ee95

Browse files
committed
Add import/export functionality for favorite servers list
1 parent d14481d commit ce8ee95

File tree

4 files changed

+205
-4
lines changed

4 files changed

+205
-4
lines changed

src/containers/Settings/Tab/Advanced.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { t } from "i18next";
2-
import { Pressable, StyleSheet, View } from "react-native";
2+
import { Pressable, StyleSheet, View, TouchableOpacity } from "react-native";
33
import CheckBox from "../../../components/CheckBox";
44
import Text from "../../../components/Text";
55
import { useGenericPersistentState } from "../../../states/genericStates";
66
import { useTheme } from "../../../states/theme";
77
import { sc } from "../../../utils/sizeScaler";
8+
import { exportFavoriteListFile, importFavoriteListFile } from "../../../utils/game";
89

910
const Advanced = () => {
1011
const { theme } = useTheme();
1112
const { shouldUpdateDiscordStatus, toggleDiscordStatus } =
1213
useGenericPersistentState();
13-
1414
return (
1515
<View
1616
style={{
@@ -44,12 +44,40 @@ const Advanced = () => {
4444
)}`}
4545
</Text>
4646
</Pressable>
47+
<TouchableOpacity
48+
style={[
49+
styles.importButton,
50+
{
51+
backgroundColor: `${theme.primary}BB`,
52+
borderColor: theme.primary
53+
}
54+
]}
55+
onPress={() => exportFavoriteListFile()}
56+
>
57+
<Text semibold color={"#FFFFFF"} size={2}>
58+
{t("settings_export_favorite_list_file")}
59+
</Text>
60+
</TouchableOpacity>
61+
62+
<TouchableOpacity
63+
style={[
64+
styles.importButton,
65+
{
66+
backgroundColor: `${theme.primary}BB`,
67+
borderColor: theme.primary
68+
}
69+
]}
70+
onPress={() => importFavoriteListFile()}
71+
>
72+
<Text semibold color={"#FFFFFF"} size={2}>
73+
{t("settings_import_favorite_list_file")}
74+
</Text>
75+
</TouchableOpacity>
4776
</View>
4877
<View style={styles.pathInputContainer}></View>
4978
</View>
5079
);
5180
};
52-
5381
const styles = StyleSheet.create({
5482
pathInputContainer: {
5583
flexDirection: "row",

src/locales/translations/en.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,16 @@ export default {
101101
reconnect: "Reconnect",
102102
settings_advanced_discord_status_requires_restart:
103103
"(Requires restarting the game to take action)",
104+
settings_export_favorite_list_file: "Export favorites list to file",
105+
settings_import_favorite_list_file: "Import favorites list from file",
106+
export_no_servers_description: "You don't have any favourite servers to export.",
107+
export_successful_title: "Export Complete",
108+
export_successful_description: "{count} servers exported to {path}.",
109+
export_failed_title: "Export Failed",
110+
export_failed_description: "An error occurred while exporting your favorite servers.",
111+
import_successful_title: "Import Complete",
112+
import_successful_description: "{count} servers imported from {path}.",
113+
import_failed_title: "Import Failed",
114+
import_failed_description: "An error occurred while importing your favorite servers.",
115+
import_invalid_data_description: "The selected file contains invalid data. Please select a valid favorites list file.",
104116
};

src/locales/translations/pt.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,16 @@ export default {
101101
reconnect: "Reconectar",
102102
settings_advanced_discord_status_requires_restart:
103103
"(Requer reiniciar o jogo para aplicar)",
104+
settings_export_favorite_list_file: "Exportar lista de favoritos para um arquivo",
105+
settings_import_favorite_list_file: "Importar lista de favoritos de um arquivo",
106+
export_no_servers_description: "Você não tem servidores favoritos para exportar.",
107+
export_successful_title: "Exportação Concluída",
108+
export_successful_description: "{count} servidores exportados para {path}.",
109+
export_failed_title: "Falha na Exportação",
110+
export_failed_description: "Ocorreu um erro ao exportar seus servidores favoritos.",
111+
import_successful_title: "Importação Concluída",
112+
import_successful_description: "{count} servidores importados de {path}.",
113+
import_failed_title: "Falha na Importação",
114+
import_failed_description: "Ocorreu um erro ao importar seus servidores favoritos.",
115+
import_invalid_data_description: "O arquivo selecionado contém dados inválidos. Selecione um arquivo de lista de favoritos válido.",
104116
};

src/utils/game.ts

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { fs, invoke, path, process, shell } from "@tauri-apps/api";
2-
import { exists } from "@tauri-apps/api/fs";
2+
import { exists, writeTextFile, readTextFile } from "@tauri-apps/api/fs";
33
import { t } from "i18next";
44
import { invoke_rpc } from "../api/rpc";
55
import { ResourceInfo, validFileChecksums } from "../constants/app";
@@ -12,6 +12,8 @@ import { Log } from "./logger";
1212
import { sc } from "./sizeScaler";
1313
import { Server } from "./types";
1414
import { useGenericPersistentState } from "../states/genericStates";
15+
import { save, open } from '@tauri-apps/api/dialog';
16+
import { useNotification } from "../states/notification";
1517

1618
export const copySharedFilesIntoGameFolder = async () => {
1719
const { gtasaPath } = useSettings.getState();
@@ -315,3 +317,150 @@ export const checkDirectoryValidity = async (
315317

316318
return true;
317319
};
320+
321+
export const exportFavoriteListFile = async () => {
322+
const { favorites } = usePersistentServers.getState();
323+
const { showMessageBox, hideMessageBox } = useMessageBox.getState();
324+
325+
if (!favorites.length) {
326+
showMessageBox({
327+
title: t("export_failed_title"),
328+
description: t("export_no_servers_description"),
329+
buttons: [
330+
{
331+
title: "OK",
332+
onPress: () => hideMessageBox(),
333+
},
334+
],
335+
});
336+
return;
337+
}
338+
339+
try {
340+
const exportData = {
341+
version: 1,
342+
servers: favorites.map(server => ({
343+
ip: server.ip,
344+
port: server.port,
345+
name: server.hostname,
346+
password: server.password || ""
347+
}))
348+
};
349+
350+
const jsonString = JSON.stringify(exportData, null, 2);
351+
352+
const savePath = await save({
353+
filters: [{
354+
name: 'JSON',
355+
extensions: ['json']
356+
}],
357+
defaultPath: 'omp_servers.json'
358+
});
359+
360+
if (savePath) {
361+
await writeTextFile(savePath, jsonString);
362+
363+
const { showNotification } = useNotification.getState();
364+
showNotification(
365+
t("export_successful_title"),
366+
t("export_successful_description", { count: favorites.length, path: savePath })
367+
);
368+
}
369+
} catch (error) {
370+
Log.debug("Error exporting servers:", error);
371+
showMessageBox({
372+
title: t("export_failed_title"),
373+
description: t("export_failed_description"),
374+
buttons: [
375+
{
376+
title: "OK",
377+
onPress: () => hideMessageBox(),
378+
},
379+
],
380+
});
381+
}
382+
};
383+
384+
export const importFavoriteListFile = async () => {
385+
const { showMessageBox, hideMessageBox } = useMessageBox.getState();
386+
387+
try {
388+
const selected = await open({
389+
multiple: false,
390+
filters: [{
391+
name: 'JSON',
392+
extensions: ['json']
393+
}]
394+
});
395+
if (!selected) return;
396+
const fileContent = await readTextFile(selected as string);
397+
const { addToFavorites } = usePersistentServers.getState();
398+
const { showNotification } = useNotification.getState();
399+
400+
try {
401+
const data = JSON.parse(fileContent);
402+
403+
if (!data.servers || !Array.isArray(data.servers)) {
404+
throw new Error("Invalid file format: missing servers array");
405+
}
406+
407+
let importCount = 0;
408+
409+
for (const importedServer of data.servers) {
410+
if (importedServer.ip && importedServer.port) {
411+
const serverInfo: Server = {
412+
ip: importedServer.ip,
413+
port: Number(importedServer.port),
414+
hostname: importedServer.name || "Imported Server",
415+
playerCount: 0,
416+
maxPlayers: 0,
417+
gameMode: "-",
418+
language: "-",
419+
hasPassword: !!importedServer.password,
420+
version: "-",
421+
usingOmp: false,
422+
partner: false,
423+
ping: 9999,
424+
players: [],
425+
password: importedServer.password || "",
426+
rules: {} as Server["rules"],
427+
};
428+
429+
addToFavorites(serverInfo);
430+
importCount++;
431+
}
432+
}
433+
434+
showNotification(
435+
t("import_successful_title"),
436+
t("import_successful_description", { count: importCount, path: selected })
437+
);
438+
439+
} catch (parseError) {
440+
showMessageBox({
441+
title: t("import_failed_title"),
442+
description: t("import_invalid_data_description"),
443+
buttons: [
444+
{
445+
title: "OK",
446+
onPress: () => hideMessageBox(),
447+
},
448+
],
449+
});
450+
Log.debug("Error parsing imported file:", parseError);
451+
}
452+
453+
} catch (error) {
454+
showMessageBox({
455+
title: t("import_failed_title"),
456+
description: t("import_failed_description"),
457+
buttons: [
458+
{
459+
title: "OK",
460+
onPress: () => hideMessageBox(),
461+
},
462+
],
463+
});
464+
Log.debug("Error importing servers:", error);
465+
}
466+
};

0 commit comments

Comments
 (0)