-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Implement offline SPX project compilation and web-based execution #6
Open
motongxue
wants to merge
5
commits into
goplus:main
Choose a base branch
from
motongxue:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
456de09
feat: Implement offline SPX project compilation and web-based execution
motongxue 76e3b5a
refactor: Reconstruction code to solve the problems mentioned in prev…
motongxue 14c8d87
style: adjust the file name to meet the naming specification
motongxue 3c89ee3
refactor: Reconstruction code to solve the problems mentioned in prev…
motongxue 043bdef
refactor: refactor JS interaction functions for enhanced reusability
motongxue File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,3 +13,7 @@ | |
|
||
# Dependency directories (remove the comment below to include it) | ||
# vendor/ | ||
|
||
.vscode/ | ||
.idea/ | ||
main.wasm | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Offline SPX Project | ||
|
||
## Introduction | ||
|
||
This project allows users to compile SPX projects offline by uploading folders and then run and view them on the Web. | ||
Users can upload the entire project folder, view the file structure and content, and run the project directly in the | ||
browser. | ||
|
||
index.html is as follows: | ||
|
||
![index](./image/index.png) | ||
|
||
## Features | ||
|
||
- Folder Upload: Users can upload the entire SPX project folder. | ||
- File Structure Viewing: After uploading, users can browse the tree structure of the folder. | ||
- File Content Viewing: Users can view the content of each file uploaded. | ||
- Online Execution: Users can run the SPX project in the web browser and view the results. | ||
|
||
## How to Use | ||
|
||
### Folder Upload | ||
|
||
![folder_upload](./image/folder_upload.png) | ||
|
||
- Click the "Select Folder" button. | ||
- Choose your SPX project folder and upload it. | ||
|
||
### View File Structure and Content | ||
|
||
- After a successful upload, the file structure will be displayed on the page in a tree format. | ||
|
||
![show_file_structure](./image/show_file_structure.png) | ||
- Enter the file's project path (starting with the project name) to view the content in the respective area. | ||
|
||
![file_content](./image/file_content.png) | ||
|
||
### Run Project Online | ||
|
||
![online_game](./image/online_game.png) | ||
|
||
- Ensure that your project files include all necessary files for execution. | ||
- Input the project name | ||
- Click the "play project" button to start the project in the browser. | ||
- The execution results will be displayed in the designated output area. | ||
|
||
## Installation and Running | ||
|
||
```sh | ||
./build.sh | ||
cp $GOROOT/misc/wasm/wasm_exec.js ./ | ||
``` | ||
|
||
run http server |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
#!/bin/sh | ||
GOOS=js GOARCH=wasm go build -tags canvas -o main.wasm main.go |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. _js 本身就代表了明确的在 GOOS=js 上工作,所以这里不需要 with. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
package ifs | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"log" | ||
"strconv" | ||
"syscall/js" | ||
"time" | ||
) | ||
|
||
func getFilesStartingWith(dirname string) ([]string, error) { | ||
log.Println("getFilesStartingWith dirname:", dirname) | ||
defer func() { | ||
if r := recover(); r != nil { | ||
log.Println("getFilesStartingWith panic:", r) | ||
} | ||
}() | ||
|
||
// Get JavaScript global object | ||
jsGlobal := js.Global() | ||
|
||
// Get the JavaScript function we want to call | ||
jsFunc := jsGlobal.Get("getFilesStartingWith") | ||
// Check if function is defined | ||
if jsFunc.Type() == js.TypeUndefined { | ||
log.Panicln("getFilesStartingWith function is not defined.") | ||
} | ||
// Call a JavaScript function, passing dirname as argument | ||
promise := jsFunc.Invoke(dirname) | ||
// Prepare a channel for receiving results | ||
done := make(chan []string) | ||
var files []string | ||
|
||
// Define success callback function | ||
onSuccess := js.FuncOf(func(this js.Value, args []js.Value) interface{} { | ||
// Get the results returned by JavaScript | ||
jsResult := args[0] | ||
length := jsResult.Length() | ||
files = make([]string, length) | ||
for i := 0; i < length; i++ { | ||
files[i] = jsResult.Index(i).String() | ||
} | ||
|
||
// Send results through channel | ||
done <- files | ||
return nil | ||
}) | ||
|
||
// Define failure callback function | ||
onError := js.FuncOf(func(this js.Value, args []js.Value) interface{} { | ||
log.Println("Error calling getFilesStartingWith:", args[0]) | ||
done <- nil | ||
return nil | ||
}) | ||
|
||
// Bind callback function to Promise | ||
promise.Call("then", onSuccess) | ||
promise.Call("catch", onError) | ||
// Wait for Promise to resolve | ||
result := <-done | ||
|
||
// Clean up callbacks | ||
onSuccess.Release() | ||
onError.Release() | ||
if result == nil { | ||
return nil, fmt.Errorf("error reading directory from IndexedDB") | ||
} | ||
return result, nil | ||
} | ||
|
||
func readFileFromIndexedDB(filename string) ([]byte, error) { | ||
defer func() { | ||
if r := recover(); r != nil { | ||
log.Println("readFileFromIndexedDB panic", r) | ||
} | ||
}() | ||
|
||
// Get JavaScript global object | ||
jsGlobal := js.Global() | ||
|
||
// Get the JavaScript function we want to call | ||
jsFunc := jsGlobal.Get("readFileFromIndexedDB") | ||
|
||
// Call a JavaScript function, passing filename as argument | ||
promise := jsFunc.Invoke(filename) | ||
|
||
// Prepare a channel for receiving results | ||
done := make(chan []byte) | ||
|
||
// Define success callback function | ||
onSuccess := js.FuncOf(func(this js.Value, args []js.Value) interface{} { | ||
// Handle the returned ArrayBuffer | ||
jsArrayBuffer := args[0] | ||
length := jsArrayBuffer.Get("byteLength").Int() | ||
fileContent := make([]byte, length) | ||
js.CopyBytesToGo(fileContent, jsArrayBuffer) | ||
|
||
done <- fileContent | ||
return nil | ||
}) | ||
|
||
// Define failure callback function | ||
onError := js.FuncOf(func(this js.Value, args []js.Value) interface{} { | ||
fmt.Println("Error calling readFileFromIndexedDB:", args[0]) | ||
done <- nil | ||
return nil | ||
}) | ||
|
||
// Bind callback function to Promise | ||
promise.Call("then", onSuccess) | ||
promise.Call("catch", onError) | ||
|
||
// Wait for Promise to resolve | ||
result := <-done | ||
// Clean up callbacks | ||
onSuccess.Release() | ||
onError.Release() | ||
|
||
if result == nil { | ||
return nil, errors.New("error reading file from IndexedDB") | ||
} | ||
return result, nil | ||
} | ||
|
||
type FileProperties struct { | ||
Size int64 | ||
LastModified time.Time | ||
} | ||
|
||
func getFileProperties(filename string) (FileProperties, error) { | ||
defer func() { | ||
if r := recover(); r != nil { | ||
log.Println("getFileProperties panic:", r) | ||
} | ||
}() | ||
|
||
// Get JavaScript global object | ||
jsGlobal := js.Global() | ||
|
||
// Get the JavaScript function we want to call | ||
jsFunc := jsGlobal.Get("getFileProperties") | ||
|
||
// Call a JavaScript function, passing filename as argument | ||
promise := jsFunc.Invoke(filename) | ||
|
||
// Prepare a channel for receiving results | ||
done := make(chan FileProperties) | ||
var fileProperties FileProperties | ||
|
||
// Define success callback function | ||
onSuccess := js.FuncOf(func(this js.Value, args []js.Value) interface{} { | ||
// Handle the returned object | ||
jsResult := args[0] | ||
size, _ := strconv.ParseInt(jsResult.Get("size").String(), 10, 64) | ||
lastModifiedMillis, _ := strconv.ParseInt(jsResult.Get("lastModified").String(), 10, 64) | ||
lastModified := time.Unix(0, lastModifiedMillis*int64(time.Millisecond)) | ||
|
||
fileProperties = FileProperties{ | ||
Size: size, | ||
LastModified: lastModified, | ||
} | ||
|
||
done <- fileProperties | ||
return nil | ||
}) | ||
|
||
// Define failure callback function | ||
onError := js.FuncOf(func(this js.Value, args []js.Value) interface{} { | ||
fmt.Println("Error calling getFileProperties:", args[0]) | ||
done <- FileProperties{} | ||
return nil | ||
}) | ||
|
||
// Bind callback function to Promise | ||
promise.Call("then", onSuccess) | ||
promise.Call("catch", onError) | ||
|
||
// Wait for Promise to resolve | ||
result := <-done | ||
|
||
// Clean up callbacks | ||
onSuccess.Release() | ||
onError.Release() | ||
|
||
if result.Size == 0 && result.LastModified.IsZero() { | ||
return FileProperties{}, errors.New("error getting file properties") | ||
} | ||
return result, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package ifs | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"io" | ||
"io/ioutil" | ||
"log" | ||
) | ||
|
||
// IndexedDBDir is the implementation of a fs.Dir | ||
type IndexedDBDir struct { | ||
assert string | ||
} | ||
|
||
func NewIndexedDBDir(assert string) *IndexedDBDir { | ||
return &IndexedDBDir{ | ||
assert: assert, | ||
} | ||
} | ||
|
||
// Open opens a file | ||
func (d *IndexedDBDir) Open(file string) (io.ReadCloser, error) { | ||
defer func() { | ||
if r := recover(); r != nil { | ||
log.Printf("IndexedDBDir.Open %s panic %s\n", file, r) | ||
} | ||
}() | ||
|
||
file = d.assert + "/" + file | ||
|
||
content, err := readFileFromIndexedDB(file) | ||
if err != nil { | ||
return nil, errors.New("file not found") | ||
} | ||
return ioutil.NopCloser(bytes.NewReader(content)), nil | ||
} | ||
|
||
// Close closes the directory (in this implementation, this method does nothing) | ||
func (d *IndexedDBDir) Close() error { | ||
// In actual applications, you may need to perform cleanup operations | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package ifs | ||
|
||
import ( | ||
"io/fs" | ||
"os" | ||
"time" | ||
) | ||
|
||
// IndexedDBDirEntry is the implementation of a fs.DirEntry | ||
type IndexedDBDirEntry struct { | ||
Path string | ||
IsDirectory bool | ||
Size int64 | ||
ModTime time.Time | ||
} | ||
|
||
func (e IndexedDBDirEntry) Name() string { return e.Path } | ||
func (e IndexedDBDirEntry) IsDir() bool { return e.IsDirectory } | ||
func (e IndexedDBDirEntry) Type() fs.FileMode { | ||
if e.IsDir() { | ||
return os.ModeDir | ||
} | ||
return 0 // file | ||
} | ||
func (e IndexedDBDirEntry) Info() (fs.FileInfo, error) { | ||
return IndexedDBFileInfo{ | ||
name: e.Path, | ||
size: e.Size, | ||
modTime: e.ModTime, | ||
isDir: e.IsDirectory, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package ifs | ||
|
||
import ( | ||
"os" | ||
"time" | ||
) | ||
|
||
// IndexedDBFileInfo is the implementation of an os.FileInfo | ||
type IndexedDBFileInfo struct { | ||
name string | ||
size int64 | ||
modTime time.Time | ||
isDir bool | ||
} | ||
|
||
func (fi IndexedDBFileInfo) Name() string { return fi.name } | ||
func (fi IndexedDBFileInfo) Size() int64 { return fi.size } | ||
func (fi IndexedDBFileInfo) Mode() os.FileMode { | ||
if fi.isDir { | ||
return os.ModeDir | ||
} | ||
return 0 | ||
} | ||
func (fi IndexedDBFileInfo) ModTime() time.Time { return fi.modTime } | ||
func (fi IndexedDBFileInfo) IsDir() bool { return fi.isDir } | ||
func (fi IndexedDBFileInfo) Sys() interface{} { return nil } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.wasm