diff --git a/.osf b/.osf new file mode 100644 index 0000000..ee657c8 --- /dev/null +++ b/.osf @@ -0,0 +1 @@ +{"id":"4d8e6d26-8cc4-4916-a349-53c5fc87a4c3","version":0,"attempts":21,"game_name":"Final Fantasy VI World's Collide","game_category":"Ultros League","window_x":1539,"window_y":586,"window_height":388,"window_width":416,"runs":[{"id":"d7f6c8dd-0c32-48a8-bb0d-6d31c30059ee","split_file_version":0,"total_time":6844552,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3863072,"current_duration":3863072},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":6844552,"current_duration":2981480}},"leaf_segments":null,"completed":false},{"id":"434f571b-802d-4ec9-8e81-8da7d99a8769","split_file_version":0,"total_time":0,"splits":{},"leaf_segments":null,"completed":false},{"id":"e8ab994e-965a-4d1c-8f63-680b88f58488","split_file_version":0,"total_time":5151654,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3625974,"current_duration":3625974},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":5151654,"current_duration":1525680}},"leaf_segments":null,"completed":false},{"id":"da1ed76e-7104-4d19-9fa6-e3ab01b5890b","split_file_version":0,"total_time":5884904,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":4204444,"current_duration":4204444},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":5884904,"current_duration":1680460}},"leaf_segments":null,"completed":false},{"id":"1e2317eb-e693-4102-8901-411f8048206e","split_file_version":0,"total_time":5356052,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3810011,"current_duration":3810011},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":5356052,"current_duration":1546041}},"leaf_segments":null,"completed":false},{"id":"25fe2c27-0cef-4bf0-be0d-a1cc39bd729e","split_file_version":0,"total_time":5040039,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3492359,"current_duration":3492359},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":5040039,"current_duration":1547680}},"leaf_segments":null,"completed":false},{"id":"26f1e4e8-c594-465c-83cc-4d233ec91565","split_file_version":0,"total_time":5276154,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":4094294,"current_duration":4094294},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":5276154,"current_duration":1181860}},"leaf_segments":null,"completed":false},{"id":"c9d67d12-75dc-4d88-a2c1-cc5d3c202c6b","split_file_version":0,"total_time":5864942,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3783242,"current_duration":3783242},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":5864942,"current_duration":2081700}},"leaf_segments":null,"completed":false},{"id":"8e4762a0-69eb-424b-8db4-9b9258a9e90a","split_file_version":0,"total_time":0,"splits":{},"leaf_segments":null,"completed":false},{"id":"d5e6f04c-491c-4d13-85bd-fd5ac0d29834","split_file_version":0,"total_time":5183477,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3527552,"current_duration":3527552},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":5183477,"current_duration":1655925}},"leaf_segments":null,"completed":false},{"id":"ec6d4173-b4d0-4343-9d0d-fdc9eac54b17","split_file_version":0,"total_time":0,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":4959039,"current_duration":4959039}},"leaf_segments":null,"completed":false},{"id":"850a9b09-7fed-44e5-981b-46aa66f288e5","split_file_version":0,"total_time":4772269,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3671129,"current_duration":3671129},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":4772269,"current_duration":1101140}},"leaf_segments":null,"completed":false},{"id":"05f98b42-75ff-4100-8b38-d9f3694a4944","split_file_version":0,"total_time":5213407,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3908687,"current_duration":3908687},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":5213407,"current_duration":1304720}},"leaf_segments":null,"completed":false},{"id":"36faea64-7549-42db-b67a-467494d7a222","split_file_version":0,"total_time":5895423,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":4018765,"current_duration":4018765},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":5895423,"current_duration":1876657}},"leaf_segments":null,"completed":false},{"id":"d67f52e8-8fb6-46df-b7dc-2f711d0c8755","split_file_version":0,"total_time":5651004,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3105004,"current_duration":3105004},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":5651004,"current_duration":2546000}},"leaf_segments":null,"completed":false},{"id":"8596e95d-5377-4733-be8e-f8c27cdedf7e","split_file_version":0,"total_time":5173226,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3277192,"current_duration":3277192},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":5173226,"current_duration":1896034}},"leaf_segments":null,"completed":false},{"id":"e14e4ed4-3a23-44c5-a9dd-b9d50adde55e","split_file_version":0,"total_time":6003774,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":4053154,"current_duration":4053154},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":6003774,"current_duration":1950620}},"leaf_segments":null,"completed":false},{"id":"a75d5af6-8515-439d-8113-4fab4a23ae20","split_file_version":0,"total_time":4476763,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3006623,"current_duration":3006623},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":4476763,"current_duration":1470140}},"leaf_segments":null,"completed":false},{"id":"d558aa91-ca39-46c9-8253-9fbfdfd7444b","split_file_version":0,"total_time":0,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3698829,"current_duration":3698829}},"leaf_segments":null,"completed":false},{"id":"6efffb6c-7a06-4975-8f5b-05a020a0b02b","split_file_version":0,"total_time":5393626,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3805064,"current_duration":3805064},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":5393626,"current_duration":1588561}},"leaf_segments":null,"completed":false}],"segments":[{"id":"22d0710b-5a73-443d-8fee-9d4725d865c2","name":"GO MODE","gold":3006623,"average":3772468,"pb":3805064,"children":[]},{"id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","name":"KefKILT","gold":1101140,"average":1745918,"pb":1588561,"children":[]}],"sob":4107763,"pb":{"id":"6efffb6c-7a06-4975-8f5b-05a020a0b02b","split_file_version":0,"total_time":5393626,"splits":{"22d0710b-5a73-443d-8fee-9d4725d865c2":{"split_segment_id":"22d0710b-5a73-443d-8fee-9d4725d865c2","current_cumulative":3805064,"current_duration":3805064},"397c03ca-30c5-442a-a40c-4f7f580f6a04":{"split_segment_id":"397c03ca-30c5-442a-a40c-4f7f580f6a04","current_cumulative":5393626,"current_duration":1588561}},"leaf_segments":null,"completed":false},"offset":0,"platform":""} \ No newline at end of file diff --git a/Taskfile.yml b/Taskfile.yml index 64da848..d6c24df 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -122,7 +122,7 @@ tasks: echo 1 # If env WAILS_TAGS is set (e.g. by GHA), use it. - # Otherwise compute tags dynamically for local dev. + # Otherwise, compute tags dynamically for local dev. WAILS_TAGS: sh: | set -euo pipefail @@ -191,3 +191,25 @@ tasks: platforms: [windows] cmds: - wails build -clean + + package-default-skin: + desc: Zip folder into ./skin/default-skin.zip + vars: + SRC_DIR: '{{.SRC_DIR}}' + ZIP_PATH: "./skin/default-skin.zip" + + cmds: + - cmd: | + set -e + mkdir -p "$(dirname "{{.ZIP_PATH}}")" + parent="$(cd "$(dirname "{{.SRC_DIR}}")" && pwd)" + base="$(basename "{{.SRC_DIR}}")" + (cd "$parent" && zip -r "$OLDPWD/{{.ZIP_PATH}}" "$base") + platforms: [ linux, darwin ] + + - cmd: > + powershell -NoProfile -Command + "New-Item -ItemType Directory -Force -Path (Split-Path -Parent '{{.ZIP_PATH}}') | Out-Null; + if (Test-Path -LiteralPath '{{.ZIP_PATH}}') { Remove-Item '{{.ZIP_PATH}}' -Force }; + Compress-Archive -LiteralPath '{{.SRC_DIR}}' -DestinationPath '{{.ZIP_PATH}}' -Force" + platforms: [ windows ] diff --git a/autosplitter/socket.go b/autosplitter/socket.go index 677a18d..46d4dd2 100644 --- a/autosplitter/socket.go +++ b/autosplitter/socket.go @@ -6,6 +6,7 @@ import ( "net" "sync" + "github.com/zellydev-games/opensplit/command" "github.com/zellydev-games/opensplit/dispatcher" "github.com/zellydev-games/opensplit/logger" ) @@ -95,7 +96,7 @@ func (s *Socket) Listen() { version := int(packet[4]) ackRequested := int(packet[5]) == 1 - command := dispatcher.Command(packet[6]) + c := command.Command(packet[6]) if version != 1 { logger.Errorf(logModule, "invalid version: %d", version) @@ -105,7 +106,7 @@ func (s *Socket) Listen() { continue } - _, err = s.dispatcher.Dispatch(command, nil) + _, err = s.dispatcher.Dispatch(c, nil) if err != nil { if ackRequested { sendAck(conn, addr, 2) diff --git a/command/commands.go b/command/commands.go new file mode 100644 index 0000000..6d09039 --- /dev/null +++ b/command/commands.go @@ -0,0 +1,23 @@ +package command + +// Command bytes are sent to the Service.Dispatch method receiver to indicate the state machine should take some action. +type Command byte + +const ( + QUIT Command = iota + NEW + LOAD + EDIT + CANCEL + SUBMIT + CLOSE + RESET + SAVE + SPLIT + UNDO + SKIP + PAUSE + TOGGLEGLOBAL + FOCUS + HELLO +) diff --git a/config/service.go b/config/service.go index 09b2286..10051a5 100644 --- a/config/service.go +++ b/config/service.go @@ -4,7 +4,7 @@ import ( "os" "sync" - "github.com/zellydev-games/opensplit/dispatcher" + "github.com/zellydev-games/opensplit/command" "github.com/zellydev-games/opensplit/keyinfo" "github.com/zellydev-games/opensplit/logger" ) @@ -14,12 +14,12 @@ const logModule = "config" // Service holds configuration options so that Service.GetEnvironment can work for both backend and frontend. type Service struct { mu sync.Mutex - SpeedRunAPIBase string `json:"speed_run_API_base"` - KeyConfig map[dispatcher.Command]keyinfo.KeyData `json:"key_config"` - GlobalHotkeysActive bool `json:"global_hotkeys_active"` - SplitFileDir string `json:"splitfile_dir"` - SkinsDir string `json:"skins_dir"` - SelectedSkin string `json:"selected_skin"` + SpeedRunAPIBase string `json:"speed_run_API_base"` + KeyConfig map[command.Command]keyinfo.KeyData `json:"key_config"` + GlobalHotkeysActive bool `json:"global_hotkeys_active"` + SplitFileDir string `json:"splitfile_dir"` + SkinsDir string `json:"skins_dir"` + SelectedSkin string `json:"selected_skin"` configUpdatedChannel chan<- *Service } @@ -29,7 +29,7 @@ func NewService(splitFileFir string, skinsDir string) (*Service, chan *Service) SplitFileDir: splitFileFir, SkinsDir: skinsDir, SpeedRunAPIBase: "", - KeyConfig: map[dispatcher.Command]keyinfo.KeyData{}, + KeyConfig: map[command.Command]keyinfo.KeyData{}, configUpdatedChannel: updateChannel, }, updateChannel } @@ -48,24 +48,24 @@ func (s *Service) GetEnvironment() *Service { } // UpdateKeyBinding changes the ConfigPayload for the given command. -func (s *Service) UpdateKeyBinding(command dispatcher.Command, data keyinfo.KeyData) { +func (s *Service) UpdateKeyBinding(c command.Command, data keyinfo.KeyData) { s.mu.Lock() defer s.mu.Unlock() - s.KeyConfig[command] = data + s.KeyConfig[c] = data s.sendUIBridgeUpdate() - logger.Infof(logModule, "updated key binding for command %v to %s", command, data.LocaleName) + logger.Infof(logModule, "updated key binding for c %v to %s", c, data.LocaleName) } // CreateDefaultConfig sets the service's options to reasonable defaults. // // Useful if the config file hasn't been created yet (first run) func (s *Service) CreateDefaultConfig() { - s.KeyConfig = map[dispatcher.Command]keyinfo.KeyData{} - s.KeyConfig[dispatcher.SPLIT] = keyinfo.KeyData{} - s.KeyConfig[dispatcher.UNDO] = keyinfo.KeyData{} - s.KeyConfig[dispatcher.SKIP] = keyinfo.KeyData{} - s.KeyConfig[dispatcher.PAUSE] = keyinfo.KeyData{} - s.KeyConfig[dispatcher.RESET] = keyinfo.KeyData{} + s.KeyConfig = map[command.Command]keyinfo.KeyData{} + s.KeyConfig[command.SPLIT] = keyinfo.KeyData{} + s.KeyConfig[command.UNDO] = keyinfo.KeyData{} + s.KeyConfig[command.SKIP] = keyinfo.KeyData{} + s.KeyConfig[command.PAUSE] = keyinfo.KeyData{} + s.KeyConfig[command.RESET] = keyinfo.KeyData{} s.sendUIBridgeUpdate() logger.Infof(logModule, "created default config") } diff --git a/config/service_test.go b/config/service_test.go index 1af02ad..5867100 100644 --- a/config/service_test.go +++ b/config/service_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/zellydev-games/opensplit/dispatcher" + "github.com/zellydev-games/opensplit/command" "github.com/zellydev-games/opensplit/keyinfo" ) @@ -62,11 +62,11 @@ func TestUpdateKeyBinding(t *testing.T) { ch := make(chan *Service, 1) s := &Service{ - KeyConfig: make(map[dispatcher.Command]keyinfo.KeyData), + KeyConfig: make(map[command.Command]keyinfo.KeyData), configUpdatedChannel: ch, } - cmd := dispatcher.SPLIT + cmd := command.SPLIT data := keyinfo.KeyData{ KeyCode: 32, LocaleName: "SPACE", @@ -107,12 +107,12 @@ func TestCreateDefaultConfig(t *testing.T) { } // Required commands should always be present - required := []dispatcher.Command{ - dispatcher.SPLIT, - dispatcher.UNDO, - dispatcher.SKIP, - dispatcher.PAUSE, - dispatcher.RESET, + required := []command.Command{ + command.SPLIT, + command.UNDO, + command.SKIP, + command.PAUSE, + command.RESET, } for _, cmd := range required { diff --git a/dispatcher/service.go b/dispatcher/service.go index cc65087..d52673d 100644 --- a/dispatcher/service.go +++ b/dispatcher/service.go @@ -4,6 +4,8 @@ import ( "sync" "github.com/wailsapp/wails/v2/pkg/runtime" + "github.com/zellydev-games/opensplit/command" + "github.com/zellydev-games/opensplit/dto" "github.com/zellydev-games/opensplit/logger" ) @@ -19,27 +21,11 @@ type FolderProvider interface { OpenSkinsDir() } -// Command bytes are sent to the Service.Dispatch method receiver to indicate the state machine should take some action. -type Command byte - -const ( - QUIT Command = iota - NEW - LOAD - EDIT - CANCEL - SUBMIT - CLOSE - RESET - SAVE - SPLIT - UNDO - SKIP - PAUSE - TOGGLEGLOBAL - FOCUS - HELLO -) +type RepoProvider interface { + LoadSplitFile() (dto.SplitFile, error) + SaveSplitFile(dto.SplitFile) error + Export() error +} // DispatchReply is sent in response to Dispatch // @@ -50,7 +36,7 @@ type DispatchReply struct { } type DispatchReceiver interface { - ReceiveDispatch(Command, *string) (DispatchReply, error) + ReceiveDispatch(command.Command, *string) (DispatchReply, error) } type Service struct { @@ -58,24 +44,27 @@ type Service struct { receiver DispatchReceiver runtime RuntimeProvider folderProvider FolderProvider + repo RepoProvider } func NewService(receiver DispatchReceiver, runtime RuntimeProvider, folderProvider FolderProvider, + repo RepoProvider, ) *Service { return &Service{ runtime: runtime, receiver: receiver, folderProvider: folderProvider, + repo: repo, } } -func (s *Service) Dispatch(command Command, payload *string) (DispatchReply, error) { - logger.Debugf(logModule, "dispatching command: %v", command) +func (s *Service) Dispatch(cmd command.Command, payload *string) (DispatchReply, error) { + logger.Debugf(logModule, "dispatching cmd: %v", cmd) s.mu.Lock() defer s.mu.Unlock() - return s.receiver.ReceiveDispatch(command, payload) + return s.receiver.ReceiveDispatch(cmd, payload) } func (s *Service) OpenSplitFileFolder() { @@ -85,3 +74,7 @@ func (s *Service) OpenSplitFileFolder() { func (s *Service) OpenSkinsFolder() { s.folderProvider.OpenSkinsDir() } + +func (s *Service) ExportSplitFile(platform string) error { + return s.repo.Export() +} diff --git a/dispatcher/service_test.go b/dispatcher/service_test.go index fc6d9e8..17fb4b9 100644 --- a/dispatcher/service_test.go +++ b/dispatcher/service_test.go @@ -1,10 +1,14 @@ package dispatcher -import "testing" +import ( + "testing" + + "github.com/zellydev-games/opensplit/command" +) type mockDispatchReceiver struct{} -func (r mockDispatchReceiver) ReceiveDispatch(Command, *string) (DispatchReply, error) { +func (r mockDispatchReceiver) ReceiveDispatch(command.Command, *string) (DispatchReply, error) { return DispatchReply{ Code: 69, Message: "Nice.", @@ -13,8 +17,8 @@ func (r mockDispatchReceiver) ReceiveDispatch(Command, *string) (DispatchReply, func TestDispatch(t *testing.T) { dr := mockDispatchReceiver{} - s := NewService(dr, nil, nil) - reply, _ := s.Dispatch(SPLIT, nil) + s := NewService(dr, nil, nil, nil) + reply, _ := s.Dispatch(command.SPLIT, nil) if reply.Code != 69 || reply.Message != "Nice." { t.Fatalf("Dispatch expected to return code 69 with message Nice. but got %v: %s", reply.Code, reply.Message) } diff --git a/dto/splitfile.go b/dto/splitfile.go index e54c6ef..7427ef5 100644 --- a/dto/splitfile.go +++ b/dto/splitfile.go @@ -31,4 +31,5 @@ type SplitFile struct { SOB int64 `json:"sob"` PB *Run `json:"pb"` Offset int64 `json:"offset"` + Platform string `json:"platform"` } diff --git a/frontend/src/components/Config.tsx b/frontend/src/components/Config.tsx index c9f6de4..24156e3 100644 --- a/frontend/src/components/Config.tsx +++ b/frontend/src/components/Config.tsx @@ -27,6 +27,7 @@ export default function Config({ configPayload }: ConfigParams) { useEffect(() => { (async () => { const as = await GetAvailableSkins(); + console.log(as); setAvailableSkins(as); })(); }, []); diff --git a/frontend/src/components/SkinPicker.tsx b/frontend/src/components/SkinPicker.tsx deleted file mode 100644 index dabcd2b..0000000 --- a/frontend/src/components/SkinPicker.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useEffect, useState } from "react"; - -import { setActiveSkin } from "../skinLoader"; - -export default function SkinPicker() { - const [active, setActive] = useState("default"); - const [available] = useState([]); - - useEffect(() => { - setTimeout(async () => { - //const skins = await GetAvailableSkins(); - //setAvailable(skins); - }, 1000); - }, []); - - return ( -
- {available && - available.map((name) => ( - - ))} - -
- ); -} diff --git a/frontend/src/components/editor/SplitEditor.tsx b/frontend/src/components/editor/SplitEditor.tsx index 68aae6f..e63603a 100644 --- a/frontend/src/components/editor/SplitEditor.tsx +++ b/frontend/src/components/editor/SplitEditor.tsx @@ -10,7 +10,7 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import React, { useEffect, useRef, useState } from "react"; -import { Dispatch } from "../../../wailsjs/go/dispatcher/Service"; +import { Dispatch, ExportSplitFile } from "../../../wailsjs/go/dispatcher/Service"; import { WindowCenter, WindowSetSize } from "../../../wailsjs/runtime"; import { Command } from "../../App"; import { useClickOutside } from "../../hooks/useClickOutside"; @@ -150,8 +150,10 @@ export default function SplitEditor({ splitFilePayload, speedRunAPIBase }: Split setGameResults([]); }); + // Is this a new file or are we editing? + const editing = splitFilePayload != null; + // Segment stats - const [splitFileLoaded] = useState(false); const [gameName, setGameName] = React.useState(splitFilePayload?.game_name ?? ""); const [gameCategory, setGameCategory] = React.useState(splitFilePayload?.game_category ?? ""); const [attempts, setAttempts] = React.useState(splitFilePayload?.attempts ?? 0); @@ -162,6 +164,9 @@ export default function SplitEditor({ splitFilePayload, speedRunAPIBase }: Split const [gameResults, setGameResults] = React.useState([]); const timeoutID = useRef(0); + // Exporter + const [platform, setPlatform] = React.useState(splitFilePayload?.platform ?? "SNES"); + // Position and size the edit window useEffect(() => { WindowSetSize(1000, 900); @@ -272,6 +277,7 @@ export default function SplitEditor({ splitFilePayload, speedRunAPIBase }: Split pb: splitFilePayload?.pb ?? null, sob: splitFilePayload?.sob ?? 0, offset: offsetMS, + platform: platform, }); const payload = JSON.stringify(newSplitFilePayload); @@ -516,7 +522,7 @@ export default function SplitEditor({ splitFilePayload, speedRunAPIBase }: Split return (
-

{splitFileLoaded ? "Editing Split File" : "New Split File"}

+

{editing ? "Editing Split File" : "New Split File"}

@@ -573,6 +579,64 @@ export default function SplitEditor({ splitFilePayload, speedRunAPIBase }: Split />
+
+ + +
+
+
+ +
+
diff --git a/frontend/src/models/splitFilePayload.ts b/frontend/src/models/splitFilePayload.ts index 8a7e630..3e92b36 100644 --- a/frontend/src/models/splitFilePayload.ts +++ b/frontend/src/models/splitFilePayload.ts @@ -16,6 +16,7 @@ export default class SplitFilePayload { sob: number = 0; pb: RunPayload | null = null; offset: number = 0; + platform: string = "SNES"; constructor(init?: Partial) { if (init) { diff --git a/frontend/wailsjs/go/dispatcher/Service.d.ts b/frontend/wailsjs/go/dispatcher/Service.d.ts index d162b79..275e815 100644 --- a/frontend/wailsjs/go/dispatcher/Service.d.ts +++ b/frontend/wailsjs/go/dispatcher/Service.d.ts @@ -1,8 +1,11 @@ // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT +import {command} from '../models'; import {dispatcher} from '../models'; -export function Dispatch(arg1:dispatcher.Command,arg2:any):Promise; +export function Dispatch(arg1:command.Command,arg2:any):Promise; + +export function ExportSplitFile(arg1:string):Promise; export function OpenSkinsFolder():Promise; diff --git a/frontend/wailsjs/go/dispatcher/Service.js b/frontend/wailsjs/go/dispatcher/Service.js index de62706..f2aad75 100644 --- a/frontend/wailsjs/go/dispatcher/Service.js +++ b/frontend/wailsjs/go/dispatcher/Service.js @@ -6,6 +6,10 @@ export function Dispatch(arg1, arg2) { return window['go']['dispatcher']['Service']['Dispatch'](arg1, arg2); } +export function ExportSplitFile(arg1) { + return window['go']['dispatcher']['Service']['ExportSplitFile'](arg1); +} + export function OpenSkinsFolder() { return window['go']['dispatcher']['Service']['OpenSkinsFolder'](); } diff --git a/opensplit.go b/opensplit.go index 42be385..c5f6f2a 100644 --- a/opensplit.go +++ b/opensplit.go @@ -76,7 +76,7 @@ func main() { // Build dispatcher that can receive commands from frontend or backend and dispatch them to the state machine folderProvider := platform.NewFolderProvider(configService) - commandDispatcher := dispatcher.NewService(machine, runtimeProvider, folderProvider) + commandDispatcher := dispatcher.NewService(machine, runtimeProvider, folderProvider, repoService) var hotkeyProvider statemachine.HotkeyProvider diff --git a/repo/adapters/session.go b/repo/adapters/session.go index 49e521c..a27c352 100644 --- a/repo/adapters/session.go +++ b/repo/adapters/session.go @@ -28,3 +28,34 @@ func DomainToDTO(svc *session.Service) *dto.Session { Dirty: svc.Dirty(), } } + +func CleanSplitFile(dtoSplitFile dto.SplitFile) (dto.SplitFile, error) { + splitFile, err := DTOSplitFileToDomain(dtoSplitFile) + if err != nil { + return dto.SplitFile{}, err + } + + sf := session.DeepCopySplitFile(&splitFile) + sf.WindowY = 100 + sf.WindowX = 100 + sf.Attempts = 0 + sf.SOB = 0 + sf.Runs = []session.Run{} + sf.PB = nil + + for i := 0; i < len(sf.Segments); i++ { + clearSegmentRecursive(&sf.Segments[i]) + } + + return DomainSplitFileToDTO(sf), nil +} + +func clearSegmentRecursive(segment *session.Segment) { + segment.PB = 0 + segment.Gold = 0 + segment.Average = 0 + + for i := 0; i < len(segment.Children); i++ { + clearSegmentRecursive(&segment.Children[i]) + } +} diff --git a/repo/adapters/splitfile.go b/repo/adapters/splitfile.go index ebdbaa8..da2d568 100644 --- a/repo/adapters/splitfile.go +++ b/repo/adapters/splitfile.go @@ -35,6 +35,7 @@ func DomainSplitFileToDTO(sf session.SplitFile) dto.SplitFile { WindowHeight: sf.WindowHeight, Attempts: sf.Attempts, Offset: sf.Offset.Milliseconds(), + Platform: sf.Platform, } } @@ -86,6 +87,7 @@ func DTOSplitFileToDomain(payload dto.SplitFile) (session.SplitFile, error) { newSplitFile.Runs = dtoRunsToDomain(fixedRuns) newSplitFile.PB = PB newSplitFile.Offset = time.Duration(payload.Offset) * time.Millisecond + newSplitFile.Platform = payload.Platform return newSplitFile, nil } diff --git a/repo/jsonfile.go b/repo/jsonfile.go index 3ccbcc3..c2216e9 100644 --- a/repo/jsonfile.go +++ b/repo/jsonfile.go @@ -65,19 +65,24 @@ func (j *JsonFile) ClearCachedFileName() { } func (j *JsonFile) SaveAs(payload []byte, defaultFileName string) error { - return j.SaveSplitFile(payload, defaultFileName) + return j.SaveSplitFile(payload, defaultFileName, false) +} + +func (j *JsonFile) Export(payload []byte, fileName string) error { + return j.SaveSplitFile(payload, fileName, true) } // SaveSplitFile takes a payload marshalled as bytes and saves it to disk -func (j *JsonFile) SaveSplitFile(payload []byte, identifier string) error { +func (j *JsonFile) SaveSplitFile(payload []byte, identifier string, export bool) error { + workingFileName := j.fileName defaultDirectory, err := j.getDefaultDirectory() if err != nil { logger.Errorf(logModule, "save failed: %s", err.Error()) return err } - if j.fileName == "" { - filename, err := j.runtimeProvider.SaveFileDialog(runtime.SaveDialogOptions{ + if export || workingFileName == "" { + workingFileName, err = j.runtimeProvider.SaveFileDialog(runtime.SaveDialogOptions{ Title: "Save OpenSplit File", DefaultDirectory: defaultDirectory, DefaultFilename: identifier, @@ -92,20 +97,29 @@ func (j *JsonFile) SaveSplitFile(payload []byte, identifier string) error { return err } - if filename == "" { + if workingFileName == "" { logger.Debug(logModule, "user cancelled save") return ErrUserCancelledSave } - j.fileName = filename + if !export { + // if this is just an export, don't save the filename state + j.fileName = workingFileName + } + } + + if !strings.HasSuffix(strings.ToLower(workingFileName), ".osf") { + workingFileName += ".osf" + if !export { + j.fileName = workingFileName + } } - if !strings.HasSuffix(strings.ToLower(j.fileName), ".osf") { - j.fileName += ".osf" + if !export { + j.lastUsedDirectory = filepath.Dir(j.fileName) } - j.lastUsedDirectory = filepath.Dir(j.fileName) - err = j.fileProvider.WriteFile(j.fileName, payload, 0644) + err = j.fileProvider.WriteFile(workingFileName, payload, 0644) if err != nil { logger.Errorf(logModule, "failed to save split file: %s", err.Error()) return err diff --git a/repo/jsonfile_test.go b/repo/jsonfile_test.go index 87314c3..522b850 100644 --- a/repo/jsonfile_test.go +++ b/repo/jsonfile_test.go @@ -68,7 +68,7 @@ func TestSave(t *testing.T) { m := &MockRuntimeProvider{} f := &MockFileProvider{} j := NewJsonFile(m, f) - err := j.SaveSplitFile([]byte(""), "testfile.osf") + err := j.SaveSplitFile([]byte(""), "testfile.osf", false) if err != nil { t.Error(err) } diff --git a/repo/service.go b/repo/service.go index 7efa9c2..360951d 100644 --- a/repo/service.go +++ b/repo/service.go @@ -2,13 +2,13 @@ package repo import ( "errors" + "fmt" "sync" "github.com/zellydev-games/opensplit/config" "github.com/zellydev-games/opensplit/dto" "github.com/zellydev-games/opensplit/logger" "github.com/zellydev-games/opensplit/repo/adapters" - "github.com/zellydev-games/opensplit/session" ) const logModule = "repo" @@ -20,8 +20,9 @@ var ErrConfigMissing = errors.New("config missing") type Repository interface { LoadSplitFile() ([]byte, error) GetLoadedSplitFile() ([]byte, error) - SaveSplitFile([]byte, string) error + SaveSplitFile([]byte, string, bool) error SaveAs([]byte, string) error + Export([]byte, string) error ClearCachedFileName() SaveConfig([]byte) error LoadConfig() ([]byte, error) @@ -38,18 +39,18 @@ func NewService(repository Repository) *Service { } // LoadSplitFile reads splitfile bytes from a repo and returns it as a session.SplitFile -func (s *Service) LoadSplitFile() (session.SplitFile, error) { +func (s *Service) LoadSplitFile() (dto.SplitFile, error) { logger.Debug(logModule, "loading split file") s.splitFileLock.RLock() splitFile, err := s.repository.LoadSplitFile() if err != nil { s.splitFileLock.RUnlock() - return session.SplitFile{}, err + return dto.SplitFile{}, err } s.splitFileLock.RUnlock() splitFileDTO, _ := adapters.JSONSplitFileToDTO(string(splitFile)) logger.Infof(logModule, "loaded split file: %s-%s", splitFileDTO.GameName, splitFileDTO.GameCategory) - return adapters.DTOSplitFileToDomain(splitFileDTO) + return splitFileDTO, nil } // SaveSplitFileWindowDimensions loads the active filename in the repository service, @@ -96,7 +97,7 @@ func (s *Service) SaveSplitFile(splitFile dto.SplitFile) error { logger.Debugf(logModule, "repository saving split file: %s", identifier) s.splitFileLock.Lock() - err = s.repository.SaveSplitFile(payload, identifier) + err = s.repository.SaveSplitFile(payload, identifier, false) s.splitFileLock.Unlock() if err != nil { logger.Errorf(logModule, "repo failed to save splitfile: %s", err) @@ -107,6 +108,31 @@ func (s *Service) SaveSplitFile(splitFile dto.SplitFile) error { return nil } +func (s *Service) Export() error { + sfBytes, err := s.repository.GetLoadedSplitFile() + if err != nil { + return err + } + + sf, err := adapters.JSONSplitFileToDTO(string(sfBytes)) + if err != nil { + return err + } + + cleanDTO, err := adapters.CleanSplitFile(sf) + if err != nil { + return err + } + + cleanBytes, err := adapters.SplitFileToFrontEnd(cleanDTO) + if err != nil { + return err + } + + defaultFileName := fmt.Sprintf("%s-%s-%s.osf", sf.Platform, sf.GameName, sf.GameCategory) + return s.repository.Export(cleanBytes, defaultFileName) +} + func (s *Service) Close() { s.splitFileLock.Lock() s.repository.ClearCachedFileName() diff --git a/session/service.go b/session/service.go index e0043c0..1a259f3 100644 --- a/session/service.go +++ b/session/service.go @@ -280,7 +280,7 @@ func (s *Service) SplitFile() (SplitFile, bool) { if s.loadedSplitFile == nil { return sf, false } - return deepCopySplitFile(s.loadedSplitFile), true + return DeepCopySplitFile(s.loadedSplitFile), true } // Dirty returns the unsaved changes status of the session @@ -432,80 +432,3 @@ func (s *Service) sendUpdate() { default: } } - -func deepCopySplitFile(inFile *SplitFile) SplitFile { - segments := deepCopySegments(inFile.Segments) - runs := deepCopyRuns(inFile.Runs) - - var pbRun *Run - if inFile.PB != nil { - pbCopy := deepCopyRun(*inFile.PB) - pbRun = &pbCopy - } - - return SplitFile{ - ID: inFile.ID, - GameName: inFile.GameName, - GameCategory: inFile.GameCategory, - Version: inFile.Version, - Attempts: inFile.Attempts, - Offset: inFile.Offset, - Segments: segments, - WindowX: inFile.WindowX, - WindowY: inFile.WindowY, - WindowHeight: inFile.WindowHeight, - WindowWidth: inFile.WindowWidth, - SOB: inFile.SOB, - Runs: runs, - PB: pbRun, - } -} - -func deepCopyRuns(inRuns []Run) []Run { - runs := make([]Run, len(inRuns)) - for i := range inRuns { - runs[i] = deepCopyRun(inRuns[i]) - } - return runs -} - -func deepCopyRun(run Run) Run { - segments := deepCopySegments(run.LeafSegments) - splits := deepCopySplits(run.Splits) - - return Run{ - ID: run.ID, - SplitFileVersion: run.SplitFileVersion, - TotalTime: run.TotalTime, - Splits: splits, - Completed: run.Completed, - LeafSegments: segments, - } -} - -func deepCopySplits(inSplits map[uuid.UUID]Split) map[uuid.UUID]Split { - splits := map[uuid.UUID]Split{} - for segmentID, split := range inSplits { - splits[segmentID] = Split{ - SplitSegmentID: split.SplitSegmentID, - CurrentCumulative: split.CurrentCumulative, - CurrentDuration: split.CurrentDuration, - } - } - return splits -} - -func deepCopySegments(list []Segment) []Segment { - out := make([]Segment, len(list)) - for i, s := range list { - out[i] = Segment{ - ID: s.ID, - Name: s.Name, - Gold: s.Gold, - Average: s.Average, - PB: s.PB, - Children: deepCopySegments(s.Children), - } - } - return out -} diff --git a/session/splitfile.go b/session/splitfile.go index f53923c..4ab73dc 100644 --- a/session/splitfile.go +++ b/session/splitfile.go @@ -23,6 +23,7 @@ type SplitFile struct { Runs []Run PB *Run Offset time.Duration + Platform string } func (s *SplitFile) DeepCopyLeafSegments() []Segment { @@ -119,6 +120,35 @@ func (s *SplitFile) perSegmentAggregates(runs []Run) (golds map[uuid.UUID]time.D return golds, sums, counts } +func DeepCopySplitFile(inFile *SplitFile) SplitFile { + segments := deepCopySegments(inFile.Segments) + runs := deepCopyRuns(inFile.Runs) + + var pbRun *Run + if inFile.PB != nil { + pbCopy := deepCopyRun(*inFile.PB) + pbRun = &pbCopy + } + + return SplitFile{ + ID: inFile.ID, + GameName: inFile.GameName, + GameCategory: inFile.GameCategory, + Version: inFile.Version, + Attempts: inFile.Attempts, + Offset: inFile.Offset, + Segments: segments, + WindowX: inFile.WindowX, + WindowY: inFile.WindowY, + WindowHeight: inFile.WindowHeight, + WindowWidth: inFile.WindowWidth, + SOB: inFile.SOB, + Runs: runs, + PB: pbRun, + Platform: inFile.Platform, + } +} + func getPB(runs []Run) (*Run, time.Duration, error) { if len(runs) == 0 { return nil, 0, errors.New("no runs found") @@ -153,3 +183,52 @@ func getLeafSegments(segments []Segment, out []*Segment) []*Segment { } return out } + +func deepCopyRuns(inRuns []Run) []Run { + runs := make([]Run, len(inRuns)) + for i := range inRuns { + runs[i] = deepCopyRun(inRuns[i]) + } + return runs +} + +func deepCopyRun(run Run) Run { + segments := deepCopySegments(run.LeafSegments) + splits := deepCopySplits(run.Splits) + + return Run{ + ID: run.ID, + SplitFileVersion: run.SplitFileVersion, + TotalTime: run.TotalTime, + Splits: splits, + Completed: run.Completed, + LeafSegments: segments, + } +} + +func deepCopySplits(inSplits map[uuid.UUID]Split) map[uuid.UUID]Split { + splits := map[uuid.UUID]Split{} + for segmentID, split := range inSplits { + splits[segmentID] = Split{ + SplitSegmentID: split.SplitSegmentID, + CurrentCumulative: split.CurrentCumulative, + CurrentDuration: split.CurrentDuration, + } + } + return splits +} + +func deepCopySegments(list []Segment) []Segment { + out := make([]Segment, len(list)) + for i, s := range list { + out[i] = Segment{ + ID: s.ID, + Name: s.Name, + Gold: s.Gold, + Average: s.Average, + PB: s.PB, + Children: deepCopySegments(s.Children), + } + } + return out +} diff --git a/skin/default-skin.zip b/skin/default-skin.zip index 90c582c..5b10854 100644 Binary files a/skin/default-skin.zip and b/skin/default-skin.zip differ diff --git a/skin/service.go b/skin/service.go index cc1c271..9efae3a 100644 --- a/skin/service.go +++ b/skin/service.go @@ -80,14 +80,14 @@ func (s *Service) Startup() error { return errors.New(msg) } - target := filepath.Join(s.skinDir, "default") + target := s.skinDir if !strings.Contains(target, "OpenSplit") { msg := fmt.Sprintf("refusing to delete outside OpenSplit directory: %s", target) logger.Error(logModule, msg) return errors.New(msg) } - if err := os.RemoveAll(target); err != nil { + if err := os.RemoveAll(filepath.Join(target, "default")); err != nil { return err } diff --git a/statemachine/config.go b/statemachine/config.go index cd86a2e..92d2d38 100644 --- a/statemachine/config.go +++ b/statemachine/config.go @@ -6,6 +6,7 @@ import ( "sync" "github.com/zellydev-games/opensplit/bridge" + "github.com/zellydev-games/opensplit/command" "github.com/zellydev-games/opensplit/dispatcher" "github.com/zellydev-games/opensplit/keyinfo" "github.com/zellydev-games/opensplit/logger" @@ -15,7 +16,7 @@ const RecordingArmed = 10 type Config struct { mu sync.Mutex - listeningFor dispatcher.Command + listeningFor command.Command recordingArmed bool previousState StateID } @@ -38,26 +39,26 @@ func (c *Config) OnExit() error { return nil } -func (c *Config) Receive(command dispatcher.Command, payload *string) (dispatcher.DispatchReply, error) { +func (c *Config) Receive(cmd command.Command, _ *string) (dispatcher.DispatchReply, error) { c.mu.Lock() defer c.mu.Unlock() - switch command { - case dispatcher.SPLIT: + switch cmd { + case command.SPLIT: fallthrough - case dispatcher.UNDO: + case command.UNDO: fallthrough - case dispatcher.SKIP: + case command.SKIP: fallthrough - case dispatcher.PAUSE: + case command.PAUSE: fallthrough - case dispatcher.RESET: + case command.RESET: c.recordingArmed = true - c.listeningFor = command - logger.Infof(logModule, "recording armed for command: %d", c.listeningFor) + c.listeningFor = cmd + logger.Infof(logModule, "recording armed for cmd: %d", c.listeningFor) err := machine.hotkeyProvider.StartHook(func(data keyinfo.KeyData) { c.handleHotkey(data) c.recordingArmed = false - logger.Infof(logModule, "updated command %v with hotkey %s (%d)", + logger.Infof(logModule, "updated cmd %v with hotkey %s (%d)", c.listeningFor, data.LocaleName, data.KeyCode) err := machine.hotkeyProvider.Unhook() if err != nil { @@ -70,10 +71,10 @@ func (c *Config) Receive(command dispatcher.Command, payload *string) (dispatche return dispatcher.DispatchReply{Code: 6}, err } return dispatcher.DispatchReply{Code: RecordingArmed}, nil - case dispatcher.CANCEL: + case command.CANCEL: machine.changeState(c.previousState) return dispatcher.DispatchReply{}, nil - case dispatcher.SUBMIT: + case command.SUBMIT: err := machine.repoService.SaveConfig(machine.configService) if err != nil { message := fmt.Sprintf("error saving config to repo %s", err) @@ -83,7 +84,7 @@ func (c *Config) Receive(command dispatcher.Command, payload *string) (dispatche machine.changeState(c.previousState) return dispatcher.DispatchReply{}, nil default: - message := fmt.Sprintf("unknown command sent to config service: %v", command) + message := fmt.Sprintf("unknown cmd sent to config service: %v", cmd) return dispatcher.DispatchReply{Code: 5, Message: message}, errors.New(message) } } diff --git a/statemachine/editing.go b/statemachine/editing.go index fec5688..940577a 100644 --- a/statemachine/editing.go +++ b/statemachine/editing.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/zellydev-games/opensplit/bridge" + "github.com/zellydev-games/opensplit/command" "github.com/zellydev-games/opensplit/dispatcher" "github.com/zellydev-games/opensplit/repo/adapters" ) @@ -34,11 +35,11 @@ func (e *Editing) OnEnter() error { } func (e *Editing) OnExit() error { return nil } -func (e *Editing) Receive(command dispatcher.Command, payload *string) (dispatcher.DispatchReply, error) { - switch command { - case dispatcher.CANCEL: +func (e *Editing) Receive(c command.Command, payload *string) (dispatcher.DispatchReply, error) { + switch c { + case command.CANCEL: machine.changeState(RUNNING) - case dispatcher.SUBMIT: + case command.SUBMIT: if payload == nil { return dispatcher.DispatchReply{ Code: 1, diff --git a/statemachine/newFile.go b/statemachine/newFile.go index 15dd4ea..71d02f0 100644 --- a/statemachine/newFile.go +++ b/statemachine/newFile.go @@ -2,6 +2,7 @@ package statemachine import ( "github.com/zellydev-games/opensplit/bridge" + "github.com/zellydev-games/opensplit/command" "github.com/zellydev-games/opensplit/dispatcher" "github.com/zellydev-games/opensplit/logger" "github.com/zellydev-games/opensplit/repo/adapters" @@ -32,11 +33,11 @@ func (n *NewFile) OnEnter() error { return nil } func (n *NewFile) OnExit() error { return nil } -func (n *NewFile) Receive(command dispatcher.Command, payload *string) (dispatcher.DispatchReply, error) { - switch command { - case dispatcher.CANCEL: +func (n *NewFile) Receive(c command.Command, payload *string) (dispatcher.DispatchReply, error) { + switch c { + case command.CANCEL: machine.changeState(WELCOME) - case dispatcher.SUBMIT: + case command.SUBMIT: if payload == nil { return dispatcher.DispatchReply{ Code: 1, diff --git a/statemachine/running.go b/statemachine/running.go index 7663cab..87bf396 100644 --- a/statemachine/running.go +++ b/statemachine/running.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/zellydev-games/opensplit/bridge" + "github.com/zellydev-games/opensplit/command" "github.com/zellydev-games/opensplit/dispatcher" "github.com/zellydev-games/opensplit/keyinfo" "github.com/zellydev-games/opensplit/logger" @@ -26,7 +27,7 @@ func (r *Running) OnEnter() error { return } - for command, keyData := range machine.configService.KeyConfig { + for c, keyData := range machine.configService.KeyConfig { if keyData.KeyCode != data.KeyCode { continue } @@ -54,10 +55,10 @@ func (r *Running) OnEnter() error { if !match { continue } - _, _ = machine.ReceiveDispatch(command, nil) + _, _ = machine.ReceiveDispatch(c, nil) return } else { - _, _ = machine.ReceiveDispatch(command, nil) + _, _ = machine.ReceiveDispatch(c, nil) return } } @@ -89,10 +90,10 @@ func (r *Running) OnExit() error { return nil } -func (r *Running) Receive(command dispatcher.Command, _ *string) (dispatcher.DispatchReply, error) { - switch command { - case dispatcher.CLOSE: - logger.Debug(logModule, "Running received CLOSE command") +func (r *Running) Receive(c command.Command, _ *string) (dispatcher.DispatchReply, error) { + switch c { + case command.CLOSE: + logger.Debug(logModule, "Running received CLOSE c") err := machine.promptDirtySave() if err != nil { return dispatcher.DispatchReply{}, err @@ -100,37 +101,37 @@ func (r *Running) Receive(command dispatcher.Command, _ *string) (dispatcher.Dis machine.sessionService.CloseRun() machine.repoService.Close() machine.changeState(WELCOME, nil) - case dispatcher.EDIT: - logger.Debug(logModule, "Running received EDIT command") + case command.EDIT: + logger.Debug(logModule, "Running received EDIT c") if _, ok := machine.sessionService.Run(); ok { return dispatcher.DispatchReply{Code: 1, Message: "can't edit splitfile mid run"}, nil } machine.changeState(EDITING, nil) - case dispatcher.SAVE: - logger.Debug(logModule, "Running received SAVE command") + case command.SAVE: + logger.Debug(logModule, "Running received SAVE c") err := machine.saveSplitFile() if err != nil { msg := fmt.Sprintf("failed to save split file to session: %s", err) logger.Error(logModule, msg) return dispatcher.DispatchReply{Code: 2, Message: msg}, err } - case dispatcher.SPLIT: - logger.Debug(logModule, "Running received SPLIT command") + case command.SPLIT: + logger.Debug(logModule, "Running received SPLIT c") machine.sessionService.Split() - case dispatcher.UNDO: + case command.UNDO: machine.sessionService.Undo() - case dispatcher.SKIP: + case command.SKIP: machine.sessionService.Skip() - case dispatcher.PAUSE: + case command.PAUSE: machine.sessionService.Pause() - case dispatcher.RESET: + case command.RESET: _ = machine.promptPartialRun() // note: promptPartialRun only adds the partial run to the session's loadedSplitFile's Runs slice. // Nothing has been saved to disk at this point, so keep the file dirty if needs be. machine.sessionService.Reset() default: - logger.Warnf(logModule, "unhandled default case in Running: %d", command) + logger.Warnf(logModule, "unhandled default case in Running: %d", c) } return dispatcher.DispatchReply{}, nil diff --git a/statemachine/service.go b/statemachine/service.go index 665b208..1458116 100644 --- a/statemachine/service.go +++ b/statemachine/service.go @@ -7,6 +7,7 @@ import ( "sync" "github.com/wailsapp/wails/v2/pkg/runtime" + "github.com/zellydev-games/opensplit/command" "github.com/zellydev-games/opensplit/config" "github.com/zellydev-games/opensplit/dispatcher" "github.com/zellydev-games/opensplit/keyinfo" @@ -55,7 +56,7 @@ type HotkeyProvider interface { type state interface { OnEnter() error OnExit() error - Receive(command dispatcher.Command, payload *string) (dispatcher.DispatchReply, error) + Receive(c command.Command, payload *string) (dispatcher.DispatchReply, error) String() string ID() StateID } @@ -99,14 +100,14 @@ func (s *Service) AttachHotkeyProvider(provider HotkeyProvider) { } // ReceiveDispatch allows external facing code to send Command bytes to the state machine -func (s *Service) ReceiveDispatch(command dispatcher.Command, payload *string) (dispatcher.DispatchReply, error) { +func (s *Service) ReceiveDispatch(c command.Command, payload *string) (dispatcher.DispatchReply, error) { if s.currentState == nil { - logger.Error(logModule, "command sent to state machine without a loaded state") - return dispatcher.DispatchReply{}, errors.New("command sent to state machine without a loaded state") + logger.Error(logModule, "c sent to state machine without a loaded state") + return dispatcher.DispatchReply{}, errors.New("c sent to state machine without a loaded state") } - if command == dispatcher.QUIT { - logger.Debug(logModule, "QUIT command dispatched from front end") + if c == command.QUIT { + logger.Debug(logModule, "QUIT c dispatched from front end") _ = s.promptDirtySave() if s.unsubscribeFromWindowDimensionChanges != nil { s.unsubscribeFromWindowDimensionChanges() @@ -115,15 +116,15 @@ func (s *Service) ReceiveDispatch(command dispatcher.Command, payload *string) ( return dispatcher.DispatchReply{}, nil } - if command == dispatcher.HELLO { + if c == command.HELLO { return dispatcher.DispatchReply{ Code: 0, Message: "HELLO", }, nil } - if command == dispatcher.TOGGLEGLOBAL { - logger.Debug(logModule, "TOGGLEGLOBAL command dispatched from frontend") + if c == command.TOGGLEGLOBAL { + logger.Debug(logModule, "TOGGLEGLOBAL c dispatched from frontend") s.configService.GlobalHotkeysActive = !s.configService.GlobalHotkeysActive err := machine.repoService.SaveConfig(machine.configService) if err != nil { @@ -136,7 +137,7 @@ func (s *Service) ReceiveDispatch(command dispatcher.Command, payload *string) ( }, nil } - if command == dispatcher.FOCUS { + if c == command.FOCUS { if payload == nil { return dispatcher.DispatchReply{Code: 1, Message: "focus requires payload of \"true\" or \"false\""}, nil } @@ -145,8 +146,8 @@ func (s *Service) ReceiveDispatch(command dispatcher.Command, payload *string) ( return dispatcher.DispatchReply{}, nil } - logger.Debugf(logModule, "command %d dispatched to state %s", command, s.currentState.String()) - return s.currentState.Receive(command, payload) + logger.Debugf(logModule, "c %d dispatched to state %s", c, s.currentState.String()) + return s.currentState.Receive(c, payload) } // changeState provides a structured way to change the current state, calling appropriate lifecycle methods along the way diff --git a/statemachine/welcome.go b/statemachine/welcome.go index d920321..443eb19 100644 --- a/statemachine/welcome.go +++ b/statemachine/welcome.go @@ -4,8 +4,10 @@ import ( "fmt" "github.com/zellydev-games/opensplit/bridge" + "github.com/zellydev-games/opensplit/command" "github.com/zellydev-games/opensplit/dispatcher" "github.com/zellydev-games/opensplit/logger" + "github.com/zellydev-games/opensplit/repo/adapters" ) // Welcome greets the user by indicating the frontend should display the Welcome screen @@ -33,26 +35,30 @@ func (w *Welcome) OnEnter() error { return nil } func (w *Welcome) OnExit() error { return nil } -func (w *Welcome) Receive(command dispatcher.Command, _ *string) (dispatcher.DispatchReply, error) { - switch command { - case dispatcher.LOAD: - logger.Debug(logModule, "Welcome received command LOAD") +func (w *Welcome) Receive(c command.Command, _ *string) (dispatcher.DispatchReply, error) { + switch c { + case command.LOAD: + logger.Debug(logModule, "Welcome received c LOAD") sf, err := machine.repoService.LoadSplitFile() if err != nil { return dispatcher.DispatchReply{Code: 1, Message: "failed to load dto: " + err.Error()}, err } - machine.sessionService.SetLoadedSplitFile(sf) + domainSF, err := adapters.DTOSplitFileToDomain(sf) + if err != nil { + return dispatcher.DispatchReply{Code: 2, Message: "failed to convert dto: " + err.Error()}, err + } + machine.sessionService.SetLoadedSplitFile(domainSF) machine.changeState(RUNNING) return dispatcher.DispatchReply{}, nil - case dispatcher.NEW: - logger.Debug(logModule, "Welcome received command NEW") + case command.NEW: + logger.Debug(logModule, "Welcome received c NEW") machine.changeState(NEWFILE) return dispatcher.DispatchReply{}, nil - case dispatcher.EDIT: - logger.Debug(logModule, "Welcome received command EDIT") + case command.EDIT: + logger.Debug(logModule, "Welcome received c EDIT") machine.changeState(CONFIG) return dispatcher.DispatchReply{}, nil default: - return dispatcher.DispatchReply{}, fmt.Errorf("invalid command %d for state Welcome", command) + return dispatcher.DispatchReply{}, fmt.Errorf("invalid c %d for state Welcome", c) } }