diff --git a/README.md b/README.md index c07c357c..344b9625 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Mod manager for [Satisfactory](https://www.satisfactorygame.com/). Handles all the steps of installing mods for you. -Implemented in [Wails](https://wails.io/). +Implemented in [Wails](https://wails.io/) using [Svelte](https://svelte.dev/) and [Skeleton](https://www.skeleton.dev/). ## Installation and Usage diff --git a/backend/migration/migration.go b/backend/migration/migration.go new file mode 100644 index 00000000..e7c5c0fe --- /dev/null +++ b/backend/migration/migration.go @@ -0,0 +1,59 @@ +package migration + +import ( + "errors" + "fmt" + "log/slog" + "os" + "path/filepath" + + "github.com/spf13/viper" +) + +type migration struct { + smm2Dir string + migrationSuccessMarkerPath string +} + +var Migration *migration + +func Init() { + if Migration == nil { + Migration = &migration{} + Migration.smm2Dir = filepath.Join(viper.GetString("smm-local-dir"), "profiles") + Migration.migrationSuccessMarkerPath = filepath.Join(Migration.smm2Dir, migrationSuccessMarkerFile) + } +} + +const migrationSuccessMarkerFile = ".smm3_migration_acknowledged" + +// https://stackoverflow.com/questions/12518876/how-to-check-if-a-file-exists-in-go +func pathExists(path string) bool { + if _, err := os.Stat(path); err == nil { + return true + } else if errors.Is(err, os.ErrNotExist) { + return false + } else { + slog.Warn("Error when checking path exists, so assuming it does not exist: "+path, slog.Any("error", err)) + return false + } +} + +func (m *migration) NeedsSmm2Migration() bool { + if pathExists(m.smm2Dir) { + return !pathExists(Migration.migrationSuccessMarkerPath) + } + return false +} + +func (m *migration) MarkSmm2MigrationSuccess() error { + file, err := os.Create(Migration.migrationSuccessMarkerPath) + if err != nil { + return fmt.Errorf("failed to create migration success marker file: %w", err) + } + err = file.Close() + if err != nil { + return fmt.Errorf("failed to close file: %w", err) + } + return nil +} diff --git a/backend/settings/settings.go b/backend/settings/settings.go index 2c484401..6e9d1722 100644 --- a/backend/settings/settings.go +++ b/backend/settings/settings.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" + psUtilDisk "github.com/shirou/gopsutil/v3/disk" "github.com/spf13/viper" wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime" @@ -65,6 +66,8 @@ type settings struct { CacheDir string `json:"cacheDir,omitempty"` Debug bool `json:"debug,omitempty"` + + NewUserSetupComplete bool `json:"newUserSetupComplete,omitempty"` } var Settings = &settings{ @@ -95,6 +98,18 @@ var Settings = &settings{ LaunchButton: "normal", Debug: false, + + NewUserSetupComplete: false, +} + +func (s *settings) GetNewUserSetupComplete() bool { + return s.NewUserSetupComplete +} + +func (s *settings) SetNewUserSetupComplete(value bool) { + slog.Info("changing NewUserSetupComplete state", slog.Bool("value", value)) + s.NewUserSetupComplete = value + _ = SaveSettings() } func (s *settings) FavoriteMod(modReference string) (bool, error) { @@ -314,6 +329,21 @@ func ValidateCacheDir(dir string) error { return nil } +// GetCacheDirDiskSpaceLeft returns the amount of disk space left on the cache directory's disk in bytes +func (s *settings) GetCacheDirDiskSpaceLeft() (uint64, error) { + cacheDir := s.GetCacheDir() + err := ValidateCacheDir(cacheDir) + if err != nil { + return 0, fmt.Errorf("cache directory to check space on failed to validate: %w", err) + } + + usage, err := psUtilDisk.Usage(cacheDir) + if err != nil { + return 0, fmt.Errorf("failed to get disk free space: %w", err) + } + return usage.Free, nil +} + func moveCacheDir(newDir string) error { if newDir == viper.GetString("cache-dir") { return nil diff --git a/cspell.json b/cspell.json index ab666615..551be24b 100644 --- a/cspell.json +++ b/cspell.json @@ -17,6 +17,7 @@ "Maximised", "Minimised", "mircearoata", + "noclose", "Nyan", "smmanager", "smmprofile", diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 332c3e79..2731e9d4 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -13,7 +13,9 @@ import ErrorDetails from '$lib/components/modals/ErrorDetails.svelte'; import ErrorModal from '$lib/components/modals/ErrorModal.svelte'; import ExternalInstallMod from '$lib/components/modals/ExternalInstallMod.svelte'; + import MigrationModal from '$lib/components/modals/MigrationModal.svelte'; import { supportedProgressTypes } from '$lib/components/modals/ProgressModal.svelte'; + import FirstTimeSetupModal from '$lib/components/modals/first-time-setup/FirstTimeSetupModal.svelte'; import { modalRegistry } from '$lib/components/modals/modalsRegistry'; import ImportProfile from '$lib/components/modals/profiles/ImportProfile.svelte'; import { isUpdateOnStart } from '$lib/components/modals/smmUpdate/smmUpdate'; @@ -23,9 +25,11 @@ import { getModalStore, initializeModalStore } from '$lib/skeletonExtensions'; import { installs, invalidInstalls, progress } from '$lib/store/ficsitCLIStore'; import { error, expandedMod, siteURL } from '$lib/store/generalStore'; - import { konami, language, updateCheckMode } from '$lib/store/settingsStore'; + import { cacheDir, konami, language, updateCheckMode } from '$lib/store/settingsStore'; import { smmUpdate, smmUpdateReady } from '$lib/store/smmUpdateStore'; import { ExpandMod, UnexpandMod } from '$wailsjs/go/app/app'; + import { NeedsSmm2Migration } from '$wailsjs/go/migration/migration'; + import { GetCacheDirDiskSpaceLeft, GetNewUserSetupComplete } from '$wailsjs/go/settings/settings'; import { Environment, EventsOn } from '$wailsjs/runtime'; initializeStores(); @@ -138,11 +142,19 @@ meta: { persistent: true, }, - }); + }); } } } - + + $: GetCacheDirDiskSpaceLeft().then((spaceLeftBytes) => { + if (spaceLeftBytes < 10e9) { + const spaceLeftGbReadable = (spaceLeftBytes * 1e-9).toFixed(1); + $error = `The drive your cache directory is on (${$cacheDir}) is very low on disk space (Only ~${spaceLeftGbReadable} GB left). Please free up some space or move the cache directory to another drive in the Mod Manager Settings.`; + } + }).catch((err) => { + $error = `failed to check cache directory disk space left: ${err}`; + }); $: if ($smmUpdateReady && $updateCheckMode === 'ask') { modalStore.trigger({ type: 'component', @@ -163,6 +175,35 @@ $error = null; } + // Order of checks is intentional + NeedsSmm2Migration().then((needsMigration) => { + if (needsMigration) { + modalStore.trigger({ + type: 'component', + component: { + ref: MigrationModal, + }, + meta: { + persistent: true, + }, + }); + } + }).then(() => { + GetNewUserSetupComplete().then((wasSetupCompleted) => { + if (!wasSetupCompleted) { + modalStore.trigger({ + type: 'component', + component: { + ref: FirstTimeSetupModal, + }, + meta: { + persistent: true, + }, + }); + } + }); + }); + EventsOn('externalInstallMod', (modReference: string, version: string) => { if (!modReference) return; modalStore.trigger({ diff --git a/frontend/src/_global.postcss b/frontend/src/_global.postcss index c53d4696..10071175 100644 --- a/frontend/src/_global.postcss +++ b/frontend/src/_global.postcss @@ -38,6 +38,13 @@ button { } } +.announcement-markdown-content { + & a { + @apply text-yellow-500; + text-decoration: underline; + } +} + .markdown-content { @apply text-base; diff --git a/frontend/src/lib/components/announcements/Announcement.svelte b/frontend/src/lib/components/announcements/Announcement.svelte index ec4e24e9..c418def0 100644 --- a/frontend/src/lib/components/announcements/Announcement.svelte +++ b/frontend/src/lib/components/announcements/Announcement.svelte @@ -4,6 +4,7 @@ import SvgIcon from '$lib/components/SVGIcon.svelte'; import { type Announcement, AnnouncementImportance } from '$lib/generated'; import { viewedAnnouncements } from '$lib/store/settingsStore'; + import { markdown as renderMarkdown } from '$lib/utils/markdown'; export let announcement: Pick; @@ -20,6 +21,8 @@ return mdiInformationOutline; } })(); + + $: rendered = renderMarkdown(announcement.message);
@@ -27,7 +30,10 @@
- {announcement.message} +
+ + {@html rendered} +
@@ -77,4 +83,4 @@ .announcement-new.announcement-bg { animation: slide 6s linear infinite; } - + diff --git a/frontend/src/lib/components/announcements/AnnouncementsBar.svelte b/frontend/src/lib/components/announcements/AnnouncementsBar.svelte index 89b0e011..2dc0a561 100644 --- a/frontend/src/lib/components/announcements/AnnouncementsBar.svelte +++ b/frontend/src/lib/components/announcements/AnnouncementsBar.svelte @@ -9,6 +9,7 @@ import { AnnouncementImportance, type Announcement as AnnouncementType, GetAnnouncementsDocument, SmrHealthcheckDocument } from '$lib/generated'; import { type PopupSettings, popup } from '$lib/skeletonExtensions'; import { offline, viewedAnnouncements } from '$lib/store/settingsStore'; + import { markdown as renderMarkdown } from '$lib/utils/markdown'; import { SetAnnouncementViewed } from '$wailsjs/go/settings/settings'; const { t } = getTranslate(); @@ -118,16 +119,23 @@ }, placement: 'bottom', } satisfies PopupSettings; + + $: renderedTooltip = (() => { + const content = $offline ? offlineAnnouncement.message : announcements[currentIndex]?.message; + if (content?.length > 0) { + return renderMarkdown(content); + } + return content; + })(); - {#if $offline} - {offlineAnnouncement.message} - {:else} - {announcements[currentIndex]?.message} - {/if} +
+ + {@html renderedTooltip} +
diff --git a/frontend/src/lib/components/left-bar/LeftBar.svelte b/frontend/src/lib/components/left-bar/LeftBar.svelte index c46efd46..49179f60 100644 --- a/frontend/src/lib/components/left-bar/LeftBar.svelte +++ b/frontend/src/lib/components/left-bar/LeftBar.svelte @@ -364,7 +364,9 @@
- Links + + + +

+ +
+ +
+
diff --git a/frontend/src/lib/components/modals/first-time-setup/FirstTimeSetupModal.svelte b/frontend/src/lib/components/modals/first-time-setup/FirstTimeSetupModal.svelte new file mode 100644 index 00000000..3b7841fc --- /dev/null +++ b/frontend/src/lib/components/modals/first-time-setup/FirstTimeSetupModal.svelte @@ -0,0 +1,116 @@ + + +
+
+ +
+
+

+ +

+
+
+
    +
  • + + + +
    +

    +
    + + +
    +
    +
  • +
  • + + + +
    + + +
    +
  • +
+
+
+

+ +

+
+
+

+ +

+
+
+ +
+
diff --git a/frontend/src/lib/components/modals/first-time-setup/LanguageSelector.svelte b/frontend/src/lib/components/modals/first-time-setup/LanguageSelector.svelte new file mode 100644 index 00000000..6b159c9e --- /dev/null +++ b/frontend/src/lib/components/modals/first-time-setup/LanguageSelector.svelte @@ -0,0 +1,35 @@ + + + \ No newline at end of file diff --git a/main.go b/main.go index 7b49ead9..233a0081 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,7 @@ import ( "github.com/satisfactorymodding/SatisfactoryModManager/backend/ficsitcli" "github.com/satisfactorymodding/SatisfactoryModManager/backend/installfinders/common" "github.com/satisfactorymodding/SatisfactoryModManager/backend/logging" + "github.com/satisfactorymodding/SatisfactoryModManager/backend/migration" "github.com/satisfactorymodding/SatisfactoryModManager/backend/settings" "github.com/satisfactorymodding/SatisfactoryModManager/backend/utils" "github.com/satisfactorymodding/SatisfactoryModManager/backend/wailsextras" @@ -131,6 +132,8 @@ func main() { startUpdateFound = <-foundOrError } + migration.Init() + // Create application with options err = wails.Run(&options.App{ Title: "SatisfactoryModManager", @@ -204,6 +207,7 @@ func main() { autoupdate.Updater, settings.Settings, ficsitcli.ServerPicker, + migration.Migration, }, EnumBind: []interface{}{ common.AllInstallTypes,