WASM: Store User Files in localStorage

* In WASM build, user levels and doodads are written to localStorage
  using their userdir path as keys (".config/levels/test.level")
* LoadFile() and WriteFile() for both Levels and Doodads interact with
  the localStorage for WASM build instead of filesystem for desktop.
* userdir.ListLevels() and ListDoodads() for WASM scan the localStorage
  keys for file names.
* userdir.ResolvePath() now works for WASM (previously was dummied out),
  checks for the file in localStorage.
This commit is contained in:
Noah 2019-06-27 15:59:18 -07:00
parent b17ca34de2
commit 35a89e5dbe
8 changed files with 89 additions and 26 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ dist/
wasm/assets/
*.wasm
*.doodad
*.level
docker/ubuntu
docker/debian
docker/fedora

View File

@ -43,12 +43,8 @@ func (d *Doodle) EditFile(filename string) error {
return d.EditDrawing(filename)
}
// WASM: no filesystem access, can go no further.
if runtime.GOOS == "js" {
return fmt.Errorf("EditFile(%s): not found for WASM and can go no further", filename)
}
// Check the user's levels directory.
// Check the user's levels directory. In WASM this will check in
// localStorage.
if foundFilename := userdir.ResolvePath(filename, extension, false); foundFilename != "" {
log.Info("EditFile: resolved name '%s' to path %s", filename, foundFilename)
absPath = foundFilename

View File

@ -68,6 +68,14 @@ func LoadFile(filename string) (*Doodad, error) {
// WASM: try the file over HTTP ajax request.
if runtime.GOOS == "js" {
if result, ok := wasm.GetSession(filename); ok {
log.Info("recall doodad data from localStorage")
return FromJSON(filename, []byte(result))
}
// TODO: ajax load for doodads might not work, filesystem.FindFile returns
// the base file for WASM but for now force it to system doodads path
filename = "assets/doodads/" + filename
jsonData, err := wasm.HTTPGet(filename)
if err != nil {
return nil, err
@ -90,13 +98,19 @@ func (d *Doodad) WriteFile(filename string) error {
d.Version = 1
d.GameVersion = branding.Version
// bin, err := m.ToBinary()
bin, err := d.ToJSON()
if err != nil {
return err
}
// Save it to their profile directory.
// WASM: place in localStorage.
if runtime.GOOS == "js" {
log.Info("wasm: write %s to localStorage", filename)
wasm.SetSession(filename, string(bin))
return nil
}
// Desktop: write to disk.
filename = userdir.DoodadPath(filename)
log.Info("Write Doodad: %s", filename)
err = ioutil.WriteFile(filename, bin, 0644)

View File

@ -105,7 +105,7 @@ func FindFile(filename string) (string, error) {
// WASM: can't check the filesystem. Let the caller go ahead and try
// loading via ajax request.
if runtime.GOOS == "js" {
return candidate, nil
return filename, nil
}
// external system level?
@ -133,7 +133,7 @@ func FindFile(filename string) (string, error) {
// WASM: can't check the filesystem. Let the caller go ahead and try
// loading via ajax request.
if runtime.GOOS == "js" {
return candidate, nil
return filename, nil
}
// external system doodad?

View File

@ -61,8 +61,14 @@ func LoadFile(filename string) (*Level, error) {
return FromJSON(filename, jsonData)
}
// WASM: try the file over HTTP ajax request.
// WASM: try the file from localStorage or HTTP ajax request.
if runtime.GOOS == "js" {
if result, ok := wasm.GetSession(filename); ok {
log.Info("recall level data from localStorage")
return FromJSON(filename, []byte(result))
}
// Ajax request.
jsonData, err := wasm.HTTPGet(filename)
if err != nil {
return nil, err
@ -98,7 +104,6 @@ func (m *Level) WriteFile(filename string) error {
m.Version = 1
m.GameVersion = branding.Version
// bin, err := m.ToBinary()
bin, err := m.ToJSON()
if err != nil {
return err
@ -107,6 +112,15 @@ func (m *Level) WriteFile(filename string) error {
// Save it to their profile directory.
filename = userdir.LevelPath(filename)
log.Info("Write Level: %s", filename)
// WASM: place in localStorage.
if runtime.GOOS == "js" {
log.Info("wasm: write %s to localStorage", filename)
wasm.SetSession(filename, string(bin))
return nil
}
// Desktop: write to disk.
err = ioutil.WriteFile(filename, bin, 0644)
if err != nil {
return fmt.Errorf("level.WriteFile: %s", err)

View File

@ -7,7 +7,7 @@ import (
"runtime"
"strings"
"git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/wasm"
"github.com/kirsle/configdir"
)
@ -77,6 +77,11 @@ func CacheFilename(filename ...string) string {
func ListDoodads() ([]string, error) {
var names []string
// WASM: list from localStorage.
if runtime.GOOS == "js" {
return wasm.StorageKeys(DoodadDirectory + "/"), nil
}
files, err := ioutil.ReadDir(DoodadDirectory)
if err != nil {
return names, err
@ -96,6 +101,11 @@ func ListDoodads() ([]string, error) {
func ListLevels() ([]string, error) {
var names []string
// WASM: list from localStorage.
if runtime.GOOS == "js" {
return wasm.StorageKeys(LevelDirectory + "/"), nil
}
files, err := ioutil.ReadDir(LevelDirectory)
if err != nil {
return names, err
@ -133,15 +143,11 @@ func resolvePath(directory, filename, extension string) string {
// existed. So the filename should have a ".level" or ".doodad" extension and
// then this path will resolve the ProfileDirectory of the file.
func ResolvePath(filename, extension string, one bool) string {
// WASM has no file system.
if runtime.GOOS == "js" {
log.Error("userdir.ResolvePath: not supported in WASM build")
return ""
}
// If the filename exists outright, return it.
if _, err := os.Stat(filename); !os.IsNotExist(err) {
return filename
if !(runtime.GOOS == "js") {
if _, err := os.Stat(filename); !os.IsNotExist(err) {
return filename
}
}
var paths []string
@ -157,6 +163,15 @@ func ResolvePath(filename, extension string, one bool) string {
}
for _, test := range paths {
// WASM: check the path in localStorage.
if runtime.GOOS == "js" {
if _, ok := wasm.GetSession(test); ok {
return test
}
continue
}
// Desktop: test the filesystem.
if _, err := os.Stat(test); os.IsNotExist(err) {
continue
}

View File

@ -2,6 +2,12 @@
package wasm
// StorageKeys returns the list of localStorage keys matching a prefix.
// This is a no-op when not in wasm.
func StorageKeys(prefix string) []string {
return []string{}
}
// SetSession sets a binary value on sessionStorage.
// This is a no-op when not in wasm.
func SetSession(key string, value string) {

View File

@ -3,20 +3,37 @@
package wasm
import (
"strings"
"syscall/js"
"git.kirsle.net/apps/doodle/pkg/log"
)
// StorageKeys returns the list of localStorage keys matching a prefix.
func StorageKeys(prefix string) []string {
keys := js.Global().Get("Object").Call("keys", js.Global().Get("localStorage"))
var result []string
for i := 0; i < keys.Length(); i++ {
value := keys.Index(i).String()
if strings.HasPrefix(value, prefix) {
result = append(result,
strings.TrimPrefix(keys.Index(i).String(), prefix),
)
}
}
log.Info("LS KEYS: %+v", result)
return result
}
// SetSession sets a text value on sessionStorage.
func SetSession(key string, value string) {
// b64 := base64.StdEncoding.EncodeToString(value)
panic("SesSession: " + key)
js.Global().Get("sessionStorage").Call("setItem", key, value)
js.Global().Get("localStorage").Call("setItem", key, value)
}
// GetSession retrieves a text value from sessionStorage.
func GetSession(key string) (string, bool) {
panic("GetSession: " + key)
var value js.Value
value = js.Global().Get("sessionStorage").Call("getItem", key)
value = js.Global().Get("localStorage").Call("getItem", key)
return value.String(), value.Type() == js.TypeString
}