forked from rojo-rbx/rojo
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e482aba030 | ||
|
|
535e4d42bb | ||
|
|
54398d4c4b | ||
|
|
0987b44e23 | ||
|
|
58098e96d4 | ||
|
|
f649c180cf | ||
|
|
966478b131 | ||
|
|
ca0759a011 | ||
|
|
f1cdf2fe79 | ||
|
|
04fa5e2719 | ||
|
|
eccb95690c | ||
|
|
acf7456371 | ||
|
|
8ea41480b7 | ||
|
|
0d1bc0d7fe | ||
|
|
f9b7774286 | ||
|
|
2e672badf2 | ||
|
|
cd5d6fd15c | ||
|
|
cf76982cfa | ||
|
|
2624ea7d2a | ||
|
|
2e7c4b6dff | ||
|
|
e5dbee1073 | ||
|
|
c06463b61d | ||
|
|
10341e3776 | ||
|
|
824cdc5dcd | ||
|
|
7aa7a35aa5 | ||
|
|
79b57b3359 | ||
|
|
c7aeffe586 | ||
|
|
79c02f2457 | ||
|
|
b9ed68fa9e | ||
|
|
6c6d6c9c8d | ||
|
|
e169d7be68 | ||
|
|
192fd7d4dd | ||
|
|
1f1193e857 | ||
|
|
0a412ade88 | ||
|
|
3cef2fe9aa | ||
|
|
18e53f06fe | ||
|
|
eaac539087 | ||
|
|
57005c4fd5 | ||
|
|
ea58999a2a |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
patreon: lpghatguy
|
||||||
42
.github/workflows/ci.yml
vendored
42
.github/workflows/ci.yml
vendored
@@ -11,29 +11,49 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
name: Build and Test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust_version: [stable, "1.55.0"]
|
rust_version: [stable, 1.57.0]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Setup Rust toolchain
|
- name: Install Rust
|
||||||
run: rustup default ${{ matrix.rust_version }}
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.rust_version }}
|
||||||
|
override: true
|
||||||
|
profile: minimal
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --locked --verbose
|
run: cargo build --locked --verbose
|
||||||
|
|
||||||
- name: Run tests
|
- name: Test
|
||||||
run: cargo test --locked --verbose
|
run: cargo test --locked --verbose
|
||||||
|
|
||||||
- name: Rustfmt and Clippy
|
lint:
|
||||||
run: |
|
name: Rustfmt and Clippy
|
||||||
cargo fmt -- --check
|
runs-on: ubuntu-latest
|
||||||
cargo clippy
|
|
||||||
if: matrix.rust_version == 'stable'
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Rustfmt
|
||||||
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
|
- name: Clippy
|
||||||
|
run: cargo clippy
|
||||||
199
.github/workflows/release.yml
vendored
199
.github/workflows/release.yml
vendored
@@ -2,65 +2,152 @@ name: Release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: ["*"]
|
tags: ["v*"]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
windows:
|
create-release:
|
||||||
runs-on: windows-latest
|
name: Create Release
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Build release binary
|
|
||||||
run: cargo build --verbose --locked --release
|
|
||||||
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v1
|
|
||||||
with:
|
|
||||||
name: rojo-win64
|
|
||||||
path: target/release/rojo.exe
|
|
||||||
|
|
||||||
macos:
|
|
||||||
runs-on: macos-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Install Rust
|
|
||||||
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
|
||||||
|
|
||||||
- name: Build release binary
|
|
||||||
run: |
|
|
||||||
source $HOME/.cargo/env
|
|
||||||
cargo build --verbose --locked --release
|
|
||||||
env:
|
|
||||||
OPENSSL_STATIC: 1
|
|
||||||
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v1
|
|
||||||
with:
|
|
||||||
name: rojo-macos
|
|
||||||
path: target/release/rojo
|
|
||||||
|
|
||||||
linux:
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- name: Create Release
|
||||||
with:
|
id: create_release
|
||||||
submodules: true
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
release_name: ${{ github.ref }}
|
||||||
|
draft: true
|
||||||
|
prerelease: false
|
||||||
|
|
||||||
- name: Build
|
build-plugin:
|
||||||
run: cargo build --locked --verbose --release
|
needs: ["create-release"]
|
||||||
env:
|
name: Build Roblox Studio Plugin
|
||||||
OPENSSL_STATIC: 1
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Setup Foreman
|
||||||
uses: actions/upload-artifact@v1
|
uses: Roblox/setup-foreman@v1
|
||||||
with:
|
with:
|
||||||
name: rojo-linux
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
path: target/release/rojo
|
|
||||||
|
- name: Build Plugin
|
||||||
|
run: rojo build plugin --output Rojo.rbxm
|
||||||
|
|
||||||
|
- name: Upload Plugin to Release
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||||
|
asset_path: Rojo.rbxm
|
||||||
|
asset_name: Rojo.rbxm
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
|
||||||
|
- name: Upload Plugin to Artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: Rojo.rbxm
|
||||||
|
path: Rojo.rbxm
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: ["create-release"]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
# https://doc.rust-lang.org/rustc/platform-support.html
|
||||||
|
#
|
||||||
|
# FIXME: After the Rojo VS Code extension updates, add architecture
|
||||||
|
# names to each of these releases. We'll rename win64 to windows and add
|
||||||
|
# -x86_64 to each release.
|
||||||
|
include:
|
||||||
|
- host: linux
|
||||||
|
os: ubuntu-18.04
|
||||||
|
target: x86_64-unknown-linux-gnu
|
||||||
|
label: linux
|
||||||
|
|
||||||
|
- host: windows
|
||||||
|
os: windows-latest
|
||||||
|
target: x86_64-pc-windows-msvc
|
||||||
|
label: win64
|
||||||
|
|
||||||
|
- host: macos
|
||||||
|
os: macos-latest
|
||||||
|
target: x86_64-apple-darwin
|
||||||
|
label: macos
|
||||||
|
|
||||||
|
- host: macos
|
||||||
|
os: macos-latest
|
||||||
|
target: aarch64-apple-darwin
|
||||||
|
label: macos-aarch64
|
||||||
|
|
||||||
|
name: Build (${{ matrix.target }})
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
BIN: rojo
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Get Version from Tag
|
||||||
|
shell: bash
|
||||||
|
# https://github.community/t/how-to-get-just-the-tag-name/16241/7#M1027
|
||||||
|
run: |
|
||||||
|
echo "PROJECT_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||||
|
echo "Version is: ${{ env.PROJECT_VERSION }}"
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
override: true
|
||||||
|
profile: minimal
|
||||||
|
|
||||||
|
- name: Build Release
|
||||||
|
run: cargo build --release --locked --verbose
|
||||||
|
env:
|
||||||
|
# Build into a known directory so we can find our build artifact more
|
||||||
|
# easily.
|
||||||
|
CARGO_TARGET_DIR: output
|
||||||
|
|
||||||
|
# On platforms that use OpenSSL, ensure it is statically linked to
|
||||||
|
# make binaries more portable.
|
||||||
|
OPENSSL_STATIC: 1
|
||||||
|
|
||||||
|
- name: Create Release Archive
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
mkdir staging
|
||||||
|
|
||||||
|
if [ "${{ matrix.host }}" = "windows" ]; then
|
||||||
|
cp "output/release/$BIN.exe" staging/
|
||||||
|
cd staging
|
||||||
|
7z a ../release.zip *
|
||||||
|
else
|
||||||
|
cp "output/release/$BIN" staging/
|
||||||
|
cd staging
|
||||||
|
zip ../release.zip *
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Upload Archive to Release
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||||
|
asset_path: release.zip
|
||||||
|
asset_name: ${{ env.BIN }}-${{ env.PROJECT_VERSION }}-${{ matrix.label }}.zip
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
|
||||||
|
- name: Upload Archive to Artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ env.BIN }}-${{ env.PROJECT_VERSION }}-${{ matrix.label }}.zip
|
||||||
|
path: release.zip
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -19,6 +19,3 @@
|
|||||||
|
|
||||||
# Snapshot files from the 'insta' Rust crate
|
# Snapshot files from the 'insta' Rust crate
|
||||||
**/*.snap.new
|
**/*.snap.new
|
||||||
|
|
||||||
# Selene generates a roblox.toml file that should not be checked in.
|
|
||||||
/roblox.toml
|
|
||||||
58
.luacheckrc
58
.luacheckrc
@@ -1,58 +0,0 @@
|
|||||||
stds.roblox = {
|
|
||||||
read_globals = {
|
|
||||||
game = {
|
|
||||||
other_fields = true,
|
|
||||||
},
|
|
||||||
|
|
||||||
-- Roblox globals
|
|
||||||
"script",
|
|
||||||
|
|
||||||
-- Extra functions
|
|
||||||
"tick", "warn", "spawn",
|
|
||||||
"wait", "settings", "typeof",
|
|
||||||
|
|
||||||
-- Types
|
|
||||||
"Vector2", "Vector3",
|
|
||||||
"Vector2int16", "Vector3int16",
|
|
||||||
"Color3",
|
|
||||||
"UDim", "UDim2",
|
|
||||||
"Rect",
|
|
||||||
"CFrame",
|
|
||||||
"Enum",
|
|
||||||
"Instance",
|
|
||||||
"DockWidgetPluginGuiInfo",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stds.plugin = {
|
|
||||||
read_globals = {
|
|
||||||
"plugin",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stds.testez = {
|
|
||||||
read_globals = {
|
|
||||||
"describe",
|
|
||||||
"it", "itFOCUS", "itSKIP", "itFIXME",
|
|
||||||
"FOCUS", "SKIP", "HACK_NO_XPCALL",
|
|
||||||
"expect",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ignore = {
|
|
||||||
"212", -- unused arguments
|
|
||||||
"421", -- shadowing local variable
|
|
||||||
"422", -- shadowing argument
|
|
||||||
"431", -- shadowing upvalue
|
|
||||||
"432", -- shadowing upvalue argument
|
|
||||||
}
|
|
||||||
|
|
||||||
std = "lua51+roblox"
|
|
||||||
|
|
||||||
files["**/*.server.lua"] = {
|
|
||||||
std = "+plugin",
|
|
||||||
}
|
|
||||||
|
|
||||||
files["**/*.spec.lua"] = {
|
|
||||||
std = "+testez",
|
|
||||||
}
|
|
||||||
39
CHANGELOG.md
39
CHANGELOG.md
@@ -2,6 +2,45 @@
|
|||||||
|
|
||||||
## Unreleased Changes
|
## Unreleased Changes
|
||||||
|
|
||||||
|
## [7.2.1] - July 8, 2022
|
||||||
|
* Fixed notification sound by changing it to a generic sound. ([#566])
|
||||||
|
* Added setting to turn off sound effects. ([#568])
|
||||||
|
|
||||||
|
[#566]: https://github.com/rojo-rbx/rojo/pull/566
|
||||||
|
[#568]: https://github.com/rojo-rbx/rojo/pull/568
|
||||||
|
[7.2.0]: https://github.com/rojo-rbx/rojo/releases/tag/v7.2.0
|
||||||
|
|
||||||
|
## [7.2.0] - June 29, 2022
|
||||||
|
* Added support for `.luau` files. ([#552])
|
||||||
|
* Added support for live syncing Attributes and Tags. ([#553])
|
||||||
|
* Added notification popups in the Roblox Studio plugin. ([#540])
|
||||||
|
* Fixed `init.meta.json` when used with `init.lua` and related files. ([#549])
|
||||||
|
* Fixed incorrect output when serving from a non-default address or port ([#556])
|
||||||
|
* Fixed Linux binaries not running on systems with older glibc. ([#561])
|
||||||
|
* Added `camelCase` casing for JSON models, deprecating `PascalCase` names. ([#563])
|
||||||
|
* Switched from structopt to clap for command line argument parsing.
|
||||||
|
* Significantly improved performance of building and serving. ([#548])
|
||||||
|
* Increased minimum supported Rust version to 1.57.0. ([#564])
|
||||||
|
|
||||||
|
[#540]: https://github.com/rojo-rbx/rojo/pull/540
|
||||||
|
[#548]: https://github.com/rojo-rbx/rojo/pull/548
|
||||||
|
[#549]: https://github.com/rojo-rbx/rojo/pull/549
|
||||||
|
[#552]: https://github.com/rojo-rbx/rojo/pull/552
|
||||||
|
[#553]: https://github.com/rojo-rbx/rojo/pull/553
|
||||||
|
[#556]: https://github.com/rojo-rbx/rojo/pull/556
|
||||||
|
[#561]: https://github.com/rojo-rbx/rojo/pull/561
|
||||||
|
[#563]: https://github.com/rojo-rbx/rojo/pull/563
|
||||||
|
[#564]: https://github.com/rojo-rbx/rojo/pull/564
|
||||||
|
[7.2.0]: https://github.com/rojo-rbx/rojo/releases/tag/v7.2.0
|
||||||
|
|
||||||
|
## [7.1.1] - May 26, 2022
|
||||||
|
* Fixed sourcemap command not stripping paths correctly ([#544])
|
||||||
|
* Fixed Studio plugin settings not saving correctly.
|
||||||
|
|
||||||
|
[#544]: https://github.com/rojo-rbx/rojo/pull/544
|
||||||
|
[#545]: https://github.com/rojo-rbx/rojo/pull/545
|
||||||
|
[7.1.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.1.1
|
||||||
|
|
||||||
## [7.1.0] - May 22, 2022
|
## [7.1.0] - May 22, 2022
|
||||||
* Added support for specifying an address to be used by default in project files. ([#507])
|
* Added support for specifying an address to be used by default in project files. ([#507])
|
||||||
* Added support for optional paths in project files. ([#472])
|
* Added support for optional paths in project files. ([#472])
|
||||||
|
|||||||
@@ -49,11 +49,9 @@ The Rojo release process is pretty manual right now. If you need to do it, here'
|
|||||||
* `cargo publish`
|
* `cargo publish`
|
||||||
8. Publish the Plugin
|
8. Publish the Plugin
|
||||||
* `cargo run -- upload plugin --asset_id 6415005344`
|
* `cargo run -- upload plugin --asset_id 6415005344`
|
||||||
* `cargo run -- build plugin --output Rojo.rbxm`
|
|
||||||
9. Push commits and tags
|
9. Push commits and tags
|
||||||
* `git push && git push --tags`
|
* `git push && git push --tags`
|
||||||
10. Copy GitHub release content from previous release
|
10. Copy GitHub release content from previous release
|
||||||
* Update the leading text with a summary about the release
|
* Update the leading text with a summary about the release
|
||||||
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md)
|
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md)
|
||||||
* Write a small summary of each major feature
|
* Write a small summary of each major feature
|
||||||
* Attach release artifacts from GitHub Actions for each platform
|
|
||||||
|
|||||||
1503
Cargo.lock
generated
1503
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
38
Cargo.toml
38
Cargo.toml
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rojo"
|
name = "rojo"
|
||||||
version = "7.1.0"
|
version = "7.2.1"
|
||||||
|
rust-version = "1.57.0"
|
||||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||||
description = "Enables professional-grade development tools for Roblox developers"
|
description = "Enables professional-grade development tools for Roblox developers"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
@@ -8,7 +9,7 @@ homepage = "https://rojo.space"
|
|||||||
documentation = "https://rojo.space/docs"
|
documentation = "https://rojo.space/docs"
|
||||||
repository = "https://github.com/rojo-rbx/rojo"
|
repository = "https://github.com/rojo-rbx/rojo"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
@@ -27,11 +28,10 @@ default = []
|
|||||||
# Enable this feature to live-reload assets from the web UI.
|
# Enable this feature to live-reload assets from the web UI.
|
||||||
dev_live_assets = []
|
dev_live_assets = []
|
||||||
|
|
||||||
|
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = ["crates/*"]
|
||||||
"rojo-insta-ext",
|
|
||||||
"memofs",
|
|
||||||
]
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "librojo"
|
name = "librojo"
|
||||||
@@ -42,7 +42,7 @@ name = "build"
|
|||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
memofs = { version = "0.2.0", path = "memofs" }
|
memofs = { version = "0.2.0", path = "crates/memofs" }
|
||||||
|
|
||||||
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
||||||
# rbx_binary = { path = "../rbx-dom/rbx_binary" }
|
# rbx_binary = { path = "../rbx-dom/rbx_binary" }
|
||||||
@@ -51,8 +51,8 @@ memofs = { version = "0.2.0", path = "memofs" }
|
|||||||
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
||||||
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
||||||
|
|
||||||
rbx_binary = "0.6.4"
|
rbx_binary = "0.6.5"
|
||||||
rbx_dom_weak = "2.3.0"
|
rbx_dom_weak = "2.4.0"
|
||||||
rbx_reflection = "4.2.0"
|
rbx_reflection = "4.2.0"
|
||||||
rbx_reflection_database = "0.2.2"
|
rbx_reflection_database = "0.2.2"
|
||||||
rbx_xml = "0.12.3"
|
rbx_xml = "0.12.3"
|
||||||
@@ -69,29 +69,28 @@ globset = "0.4.8"
|
|||||||
humantime = "2.1.0"
|
humantime = "2.1.0"
|
||||||
hyper = { version = "0.14.13", features = ["server", "tcp", "http1"] }
|
hyper = { version = "0.14.13", features = ["server", "tcp", "http1"] }
|
||||||
jod-thread = "0.1.2"
|
jod-thread = "0.1.2"
|
||||||
lazy_static = "1.4.0"
|
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
notify = "4.0.17"
|
notify = "4.0.17"
|
||||||
opener = "0.5.0"
|
opener = "0.5.0"
|
||||||
regex = "1.5.4"
|
reqwest = { version = "0.11.10", features = ["blocking", "json"] }
|
||||||
reqwest = "0.9.24"
|
|
||||||
ritz = "0.1.0"
|
ritz = "0.1.0"
|
||||||
rlua = "0.17.1"
|
|
||||||
roblox_install = "1.0.0"
|
roblox_install = "1.0.0"
|
||||||
serde = { version = "1.0.130", features = ["derive", "rc"] }
|
serde = { version = "1.0.130", features = ["derive", "rc"] }
|
||||||
serde_json = "1.0.68"
|
serde_json = "1.0.68"
|
||||||
structopt = "0.3.23"
|
|
||||||
termcolor = "1.1.2"
|
termcolor = "1.1.2"
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] }
|
tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] }
|
||||||
uuid = { version = "0.8.2", features = ["v4", "serde"] }
|
uuid = { version = "1.0.0", features = ["v4", "serde"] }
|
||||||
|
clap = { version = "3.1.18", features = ["derive"] }
|
||||||
|
profiling = "1.0.6"
|
||||||
|
tracy-client = { version = "0.13.2", optional = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.9.0"
|
winreg = "0.10.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
memofs = { version = "0.2.0", path = "memofs" }
|
memofs = { version = "0.2.0", path = "crates/memofs" }
|
||||||
|
|
||||||
embed-resource = "1.6.4"
|
embed-resource = "1.6.4"
|
||||||
anyhow = "1.0.44"
|
anyhow = "1.0.44"
|
||||||
@@ -100,13 +99,12 @@ fs-err = "2.6.0"
|
|||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rojo-insta-ext = { path = "rojo-insta-ext" }
|
rojo-insta-ext = { path = "crates/rojo-insta-ext" }
|
||||||
|
|
||||||
criterion = "0.3.5"
|
criterion = "0.3.5"
|
||||||
insta = { version = "1.8.0", features = ["redactions"] }
|
insta = { version = "1.8.0", features = ["redactions"] }
|
||||||
lazy_static = "1.4.0"
|
|
||||||
paste = "1.0.5"
|
paste = "1.0.5"
|
||||||
pretty_assertions = "0.7.2"
|
pretty_assertions = "1.2.1"
|
||||||
serde_yaml = "0.8.21"
|
serde_yaml = "0.8.21"
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions fo
|
|||||||
|
|
||||||
Pull requests are welcome!
|
Pull requests are welcome!
|
||||||
|
|
||||||
Rojo supports Rust 1.46.0 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
Rojo supports Rust 1.57.0 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
||||||
BIN
assets/NotificationPop.mp3
Normal file
BIN
assets/NotificationPop.mp3
Normal file
Binary file not shown.
@@ -1,5 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
watchexec -c -w plugin "sh -c './bin/install-dev-plugin.sh'"
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
DIR="$( mktemp -d )"
|
|
||||||
PLUGIN_FILE="$DIR/Rojo.rbxm"
|
|
||||||
TESTEZ_FILE="$DIR/TestEZ.rbxm"
|
|
||||||
|
|
||||||
rojo build plugin -o "$PLUGIN_FILE"
|
|
||||||
rojo build plugin/testez.project.json -o "$TESTEZ_FILE"
|
|
||||||
remodel bin/mark-plugin-as-dev.lua "$PLUGIN_FILE" "$TESTEZ_FILE" 2>/dev/null
|
|
||||||
|
|
||||||
cp "$PLUGIN_FILE" "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
rojo build plugin -o "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
local pluginPath, testezPath = ...
|
|
||||||
|
|
||||||
local plugin = remodel.readModelFile(pluginPath)[1]
|
|
||||||
local testez = remodel.readModelFile(testezPath)[1]
|
|
||||||
|
|
||||||
local marker = Instance.new("Folder")
|
|
||||||
marker.Name = "ROJO_DEV_BUILD"
|
|
||||||
marker.Parent = plugin
|
|
||||||
|
|
||||||
testez.Parent = plugin
|
|
||||||
|
|
||||||
remodel.writeModelFile(plugin, pluginPath)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
local pluginPath, placePath = ...
|
|
||||||
|
|
||||||
local plugin = remodel.readModelFile(pluginPath)[1]
|
|
||||||
local place = remodel.readPlaceFile(placePath)
|
|
||||||
|
|
||||||
plugin.Parent = place:GetService("ReplicatedStorage")
|
|
||||||
|
|
||||||
remodel.writePlaceFile(place, placePath)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
./bin/run-cli-tests.sh
|
|
||||||
./bin/run-plugin-tests.sh
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
cargo test --all --locked
|
|
||||||
cargo fmt -- --check
|
|
||||||
|
|
||||||
touch src/lib.rs # Nudge Rust source to make Clippy actually check things
|
|
||||||
cargo clippy
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
DIR="$( mktemp -d )"
|
|
||||||
PLUGIN_FILE="$DIR/Rojo.rbxmx"
|
|
||||||
PLACE_FILE="$DIR/RojoTestPlace.rbxlx"
|
|
||||||
|
|
||||||
rojo build plugin -o "$PLUGIN_FILE"
|
|
||||||
rojo build plugin/place.project.json -o "$PLACE_FILE"
|
|
||||||
|
|
||||||
remodel bin/put-plugin-in-test-place.lua "$PLUGIN_FILE" "$PLACE_FILE"
|
|
||||||
|
|
||||||
run-in-roblox -s plugin/testBootstrap.server.lua "$PLACE_FILE"
|
|
||||||
|
|
||||||
luacheck plugin/src plugin/log plugin/http
|
|
||||||
2
build.rs
2
build.rs
@@ -21,7 +21,7 @@ fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
|||||||
|
|
||||||
// We can skip any TestEZ test files since they aren't necessary for
|
// We can skip any TestEZ test files since they aren't necessary for
|
||||||
// the plugin to run.
|
// the plugin to run.
|
||||||
if file_name.ends_with(".spec.lua") {
|
if file_name.ends_with(".spec.lua") || file_name.ends_with(".spec.luau") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
[tools]
|
[tools]
|
||||||
rojo = { source = "rojo-rbx/rojo", version = "6.1.0" }
|
rojo = { source = "rojo-rbx/rojo", version = "7.1.1" }
|
||||||
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }
|
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }
|
||||||
|
selene = { source = "Kampfkarren/selene", version = "0.18.2" }
|
||||||
|
|||||||
@@ -23,8 +23,45 @@ end
|
|||||||
local ALL_AXES = {"X", "Y", "Z"}
|
local ALL_AXES = {"X", "Y", "Z"}
|
||||||
local ALL_FACES = {"Right", "Top", "Back", "Left", "Bottom", "Front"}
|
local ALL_FACES = {"Right", "Top", "Back", "Left", "Bottom", "Front"}
|
||||||
|
|
||||||
|
local EncodedValue = {}
|
||||||
|
|
||||||
local types
|
local types
|
||||||
types = {
|
types = {
|
||||||
|
Attributes = {
|
||||||
|
fromPod = function(pod)
|
||||||
|
local output = {}
|
||||||
|
|
||||||
|
for key, value in pairs(pod) do
|
||||||
|
local ok, result = EncodedValue.decode(value)
|
||||||
|
|
||||||
|
if ok then
|
||||||
|
output[key] = result
|
||||||
|
else
|
||||||
|
local warning = ("Could not decode attribute value of type %q: %s"):format(typeof(value), tostring(result))
|
||||||
|
warn(warning)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return output
|
||||||
|
end,
|
||||||
|
toPod = function(roblox)
|
||||||
|
local output = {}
|
||||||
|
|
||||||
|
for key, value in pairs(roblox) do
|
||||||
|
local ok, result = EncodedValue.encodeNaive(value)
|
||||||
|
|
||||||
|
if ok then
|
||||||
|
output[key] = result
|
||||||
|
else
|
||||||
|
local warning = ("Could not encode attribute value of type %q: %s"):format(typeof(value), tostring(result))
|
||||||
|
warn(warning)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return output
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
Axes = {
|
Axes = {
|
||||||
fromPod = function(pod)
|
fromPod = function(pod)
|
||||||
local axes = {}
|
local axes = {}
|
||||||
@@ -433,8 +470,6 @@ types = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
local EncodedValue = {}
|
|
||||||
|
|
||||||
function EncodedValue.decode(encodedValue)
|
function EncodedValue.decode(encodedValue)
|
||||||
local ty, value = next(encodedValue)
|
local ty, value = next(encodedValue)
|
||||||
|
|
||||||
@@ -459,4 +494,19 @@ function EncodedValue.encode(rbxValue, propertyType)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local propertyTypeRenames = {
|
||||||
|
number = "Float64",
|
||||||
|
boolean = "Bool",
|
||||||
|
string = "String",
|
||||||
|
}
|
||||||
|
|
||||||
|
function EncodedValue.encodeNaive(rbxValue)
|
||||||
|
local propertyType = typeof(rbxValue)
|
||||||
|
if propertyTypeRenames[propertyType] ~= nil then
|
||||||
|
propertyType = propertyTypeRenames[propertyType]
|
||||||
|
end
|
||||||
|
|
||||||
|
return EncodedValue.encode(rbxValue, propertyType)
|
||||||
|
end
|
||||||
|
|
||||||
return EncodedValue
|
return EncodedValue
|
||||||
|
|||||||
@@ -1,4 +1,73 @@
|
|||||||
{
|
{
|
||||||
|
"Attributes": {
|
||||||
|
"value": {
|
||||||
|
"Attributes": {
|
||||||
|
"TestBool": {
|
||||||
|
"Bool": true
|
||||||
|
},
|
||||||
|
"TestBrickColor": {
|
||||||
|
"BrickColor": 24
|
||||||
|
},
|
||||||
|
"TestColor3": {
|
||||||
|
"Color3": [
|
||||||
|
1.0,
|
||||||
|
0.5,
|
||||||
|
0.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestNumber": {
|
||||||
|
"Float64": 1337.0
|
||||||
|
},
|
||||||
|
"TestRect": {
|
||||||
|
"Rect": [
|
||||||
|
[
|
||||||
|
1.0,
|
||||||
|
2.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.0,
|
||||||
|
4.0
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestString": {
|
||||||
|
"String": "Test"
|
||||||
|
},
|
||||||
|
"TestUDim": {
|
||||||
|
"UDim": [
|
||||||
|
1.0,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestUDim2": {
|
||||||
|
"UDim2": [
|
||||||
|
[
|
||||||
|
1.0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.0,
|
||||||
|
4
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestVector2": {
|
||||||
|
"Vector2": [
|
||||||
|
1.0,
|
||||||
|
2.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestVector3": {
|
||||||
|
"Vector3": [
|
||||||
|
1.0,
|
||||||
|
2.0,
|
||||||
|
3.0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ty": "Attributes"
|
||||||
|
},
|
||||||
"Axes": {
|
"Axes": {
|
||||||
"value": {
|
"value": {
|
||||||
"Axes": [
|
"Axes": [
|
||||||
|
|||||||
@@ -5,6 +5,26 @@ local CollectionService = game:GetService("CollectionService")
|
|||||||
-- The reflection database refers to these as having scriptability = "Custom"
|
-- The reflection database refers to these as having scriptability = "Custom"
|
||||||
return {
|
return {
|
||||||
Instance = {
|
Instance = {
|
||||||
|
Attributes = {
|
||||||
|
read = function(instance)
|
||||||
|
return true, instance:GetAttributes()
|
||||||
|
end,
|
||||||
|
write = function(instance, _, value)
|
||||||
|
local existing = instance:GetAttributes()
|
||||||
|
|
||||||
|
for key, attr in pairs(value) do
|
||||||
|
instance:SetAttribute(key, attr)
|
||||||
|
end
|
||||||
|
|
||||||
|
for key in pairs(existing) do
|
||||||
|
if value[key] == nil then
|
||||||
|
instance:SetAttribute(key, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
},
|
||||||
Tags = {
|
Tags = {
|
||||||
read = function(instance)
|
read = function(instance)
|
||||||
return true, CollectionService:GetTags(instance)
|
return true, CollectionService:GetTags(instance)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
198
plugin/src/App/Notifications.lua
Normal file
198
plugin/src/App/Notifications.lua
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
local TextService = game:GetService("TextService")
|
||||||
|
local StudioService = game:GetService("StudioService")
|
||||||
|
|
||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Plugin = Rojo.Plugin
|
||||||
|
|
||||||
|
local Roact = require(Rojo.Roact)
|
||||||
|
local Flipper = require(Rojo.Flipper)
|
||||||
|
|
||||||
|
local bindingUtil = require(script.Parent.bindingUtil)
|
||||||
|
|
||||||
|
local Theme = require(Plugin.App.Theme)
|
||||||
|
local Assets = require(Plugin.Assets)
|
||||||
|
|
||||||
|
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||||
|
|
||||||
|
local baseClock = DateTime.now().UnixTimestampMillis
|
||||||
|
|
||||||
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local Notification = Roact.Component:extend("Notification")
|
||||||
|
|
||||||
|
function Notification:init()
|
||||||
|
self.motor = Flipper.SingleMotor.new(0)
|
||||||
|
self.binding = bindingUtil.fromMotor(self.motor)
|
||||||
|
|
||||||
|
self.lifetime = self.props.timeout
|
||||||
|
|
||||||
|
self.motor:onStep(function(value)
|
||||||
|
if value <= 0 then
|
||||||
|
if self.props.onClose then
|
||||||
|
self.props.onClose()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Notification:dismiss()
|
||||||
|
self.motor:setGoal(
|
||||||
|
Flipper.Spring.new(0, {
|
||||||
|
frequency = 5,
|
||||||
|
dampingRatio = 1,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Notification:didMount()
|
||||||
|
self.motor:setGoal(
|
||||||
|
Flipper.Spring.new(1, {
|
||||||
|
frequency = 3,
|
||||||
|
dampingRatio = 1,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
self.props.soundPlayer:play(Assets.Sounds.Notification)
|
||||||
|
|
||||||
|
self.timeout = task.spawn(function()
|
||||||
|
local clock = os.clock()
|
||||||
|
local seen = false
|
||||||
|
while task.wait(1/10) do
|
||||||
|
local now = os.clock()
|
||||||
|
local dt = now - clock
|
||||||
|
clock = now
|
||||||
|
|
||||||
|
if not seen then
|
||||||
|
seen = StudioService.ActiveScript == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if not seen then
|
||||||
|
-- Don't run down timer before being viewed
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
self.lifetime -= dt
|
||||||
|
if self.lifetime <= 0 then
|
||||||
|
self:dismiss()
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Notification:willUnmount()
|
||||||
|
task.cancel(self.timeout)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Notification:render()
|
||||||
|
local time = DateTime.fromUnixTimestampMillis(self.props.timestamp)
|
||||||
|
|
||||||
|
local textBounds = TextService:GetTextSize(
|
||||||
|
self.props.text,
|
||||||
|
15,
|
||||||
|
Enum.Font.GothamSemibold,
|
||||||
|
Vector2.new(350, 700)
|
||||||
|
)
|
||||||
|
|
||||||
|
local transparency = self.binding:map(function(value)
|
||||||
|
return 1 - value
|
||||||
|
end)
|
||||||
|
|
||||||
|
local size = self.binding:map(function(value)
|
||||||
|
return UDim2.fromOffset(
|
||||||
|
(35+40+textBounds.X)*value,
|
||||||
|
math.max(14+20+textBounds.Y, 32+20)
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
return Theme.with(function(theme)
|
||||||
|
return e("TextButton", {
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = size,
|
||||||
|
LayoutOrder = self.props.layoutOrder,
|
||||||
|
Text = "",
|
||||||
|
ClipsDescendants = true,
|
||||||
|
|
||||||
|
[Roact.Event.Activated] = function()
|
||||||
|
self:dismiss()
|
||||||
|
end,
|
||||||
|
}, {
|
||||||
|
e(BorderedContainer, {
|
||||||
|
transparency = transparency,
|
||||||
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
|
}, {
|
||||||
|
TextContainer = e("Frame", {
|
||||||
|
Size = UDim2.new(0, 35+textBounds.X, 1, -20),
|
||||||
|
Position = UDim2.new(0, 0, 0, 10),
|
||||||
|
BackgroundTransparency = 1
|
||||||
|
}, {
|
||||||
|
Logo = e("ImageLabel", {
|
||||||
|
ImageTransparency = transparency,
|
||||||
|
Image = Assets.Images.PluginButton,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.new(0, 32, 0, 32),
|
||||||
|
Position = UDim2.new(0, 0, 0.5, 0),
|
||||||
|
AnchorPoint = Vector2.new(0, 0.5),
|
||||||
|
}),
|
||||||
|
Info = e("TextLabel", {
|
||||||
|
Text = self.props.text,
|
||||||
|
Font = Enum.Font.GothamSemibold,
|
||||||
|
TextSize = 15,
|
||||||
|
TextColor3 = theme.Notification.InfoColor,
|
||||||
|
TextTransparency = transparency,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
TextWrapped = true,
|
||||||
|
|
||||||
|
Size = UDim2.new(0, textBounds.X, 0, textBounds.Y),
|
||||||
|
Position = UDim2.fromOffset(35, 0),
|
||||||
|
|
||||||
|
LayoutOrder = 1,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}),
|
||||||
|
Time = e("TextLabel", {
|
||||||
|
Text = time:FormatLocalTime("LTS", "en-us"),
|
||||||
|
Font = Enum.Font.Code,
|
||||||
|
TextSize = 12,
|
||||||
|
TextColor3 = theme.Notification.InfoColor,
|
||||||
|
TextTransparency = transparency,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
|
||||||
|
Size = UDim2.new(1, -35, 0, 14),
|
||||||
|
Position = UDim2.new(0, 35, 1, -14),
|
||||||
|
|
||||||
|
LayoutOrder = 1,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
|
Padding = e("UIPadding", {
|
||||||
|
PaddingLeft = UDim.new(0, 17),
|
||||||
|
PaddingRight = UDim.new(0, 15),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local Notifications = Roact.Component:extend("Notifications")
|
||||||
|
|
||||||
|
function Notifications:render()
|
||||||
|
local notifs = {}
|
||||||
|
|
||||||
|
for index, notif in ipairs(self.props.notifications) do
|
||||||
|
notifs[notif] = e(Notification, {
|
||||||
|
soundPlayer = self.props.soundPlayer,
|
||||||
|
text = notif.text,
|
||||||
|
timestamp = notif.timestamp,
|
||||||
|
timeout = notif.timeout,
|
||||||
|
layoutOrder = (notif.timestamp - baseClock),
|
||||||
|
onClose = function()
|
||||||
|
self.props.onClose(index)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return Roact.createFragment(notifs)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Notifications
|
||||||
@@ -9,6 +9,8 @@ local Roact = require(Rojo.Roact)
|
|||||||
local defaultSettings = {
|
local defaultSettings = {
|
||||||
openScriptsExternally = false,
|
openScriptsExternally = false,
|
||||||
twoWaySync = false,
|
twoWaySync = false,
|
||||||
|
showNotifications = true,
|
||||||
|
playSounds = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
local Settings = {}
|
local Settings = {}
|
||||||
|
|||||||
@@ -202,12 +202,28 @@ function SettingsPage:render()
|
|||||||
layoutOrder = 1,
|
layoutOrder = 1,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
ShowNotifications = e(Setting, {
|
||||||
|
id = "showNotifications",
|
||||||
|
name = "Show Notifications",
|
||||||
|
description = "Popup notifications in viewport",
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
layoutOrder = 2,
|
||||||
|
}),
|
||||||
|
|
||||||
|
PlaySounds = e(Setting, {
|
||||||
|
id = "playSounds",
|
||||||
|
name = "Play Sounds",
|
||||||
|
description = "Toggle sound effects",
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
layoutOrder = 3,
|
||||||
|
}),
|
||||||
|
|
||||||
TwoWaySync = e(Setting, {
|
TwoWaySync = e(Setting, {
|
||||||
id = "twoWaySync",
|
id = "twoWaySync",
|
||||||
name = "Two-Way Sync",
|
name = "Two-Way Sync",
|
||||||
description = "EXPERIMENTAL! Editing files in Studio will sync them into the filesystem",
|
description = "EXPERIMENTAL! Editing files in Studio will sync them into the filesystem",
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 2,
|
layoutOrder = 4,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Layout = e("UIListLayout", {
|
Layout = e("UIListLayout", {
|
||||||
|
|||||||
@@ -103,6 +103,10 @@ local lightTheme = strict("LightTheme", {
|
|||||||
LogoColor = BRAND_COLOR,
|
LogoColor = BRAND_COLOR,
|
||||||
VersionColor = hexColor(0x727272),
|
VersionColor = hexColor(0x727272),
|
||||||
},
|
},
|
||||||
|
Notification = {
|
||||||
|
InfoColor = hexColor(0x00000),
|
||||||
|
CloseColor = BRAND_COLOR,
|
||||||
|
},
|
||||||
ErrorColor = hexColor(0x000000),
|
ErrorColor = hexColor(0x000000),
|
||||||
ScrollBarColor = hexColor(0x000000),
|
ScrollBarColor = hexColor(0x000000),
|
||||||
})
|
})
|
||||||
@@ -177,6 +181,10 @@ local darkTheme = strict("DarkTheme", {
|
|||||||
LogoColor = BRAND_COLOR,
|
LogoColor = BRAND_COLOR,
|
||||||
VersionColor = hexColor(0xD3D3D3)
|
VersionColor = hexColor(0xD3D3D3)
|
||||||
},
|
},
|
||||||
|
Notification = {
|
||||||
|
InfoColor = hexColor(0xFFFFFF),
|
||||||
|
CloseColor = hexColor(0xFFFFFF),
|
||||||
|
},
|
||||||
ErrorColor = hexColor(0xFFFFFF),
|
ErrorColor = hexColor(0xFFFFFF),
|
||||||
ScrollBarColor = hexColor(0xFFFFFF),
|
ScrollBarColor = hexColor(0xFFFFFF),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ local Dictionary = require(Plugin.Dictionary)
|
|||||||
local ServeSession = require(Plugin.ServeSession)
|
local ServeSession = require(Plugin.ServeSession)
|
||||||
local ApiContext = require(Plugin.ApiContext)
|
local ApiContext = require(Plugin.ApiContext)
|
||||||
local preloadAssets = require(Plugin.preloadAssets)
|
local preloadAssets = require(Plugin.preloadAssets)
|
||||||
|
local soundPlayer = require(Plugin.soundPlayer)
|
||||||
local Theme = require(script.Theme)
|
local Theme = require(script.Theme)
|
||||||
local PluginSettings = require(script.PluginSettings)
|
local PluginSettings = require(script.PluginSettings)
|
||||||
|
|
||||||
local Page = require(script.Page)
|
local Page = require(script.Page)
|
||||||
|
local Notifications = require(script.Notifications)
|
||||||
local StudioPluginAction = require(script.Components.Studio.StudioPluginAction)
|
local StudioPluginAction = require(script.Components.Studio.StudioPluginAction)
|
||||||
local StudioToolbar = require(script.Components.Studio.StudioToolbar)
|
local StudioToolbar = require(script.Components.Studio.StudioToolbar)
|
||||||
local StudioToggleButton = require(script.Components.Studio.StudioToggleButton)
|
local StudioToggleButton = require(script.Components.Studio.StudioToggleButton)
|
||||||
@@ -44,10 +46,37 @@ function App:init()
|
|||||||
self:setState({
|
self:setState({
|
||||||
appStatus = AppStatus.NotConnected,
|
appStatus = AppStatus.NotConnected,
|
||||||
guiEnabled = false,
|
guiEnabled = false,
|
||||||
|
notifications = {},
|
||||||
toolbarIcon = Assets.Images.PluginButton,
|
toolbarIcon = Assets.Images.PluginButton,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function App:addNotification(text: string, timeout: number?)
|
||||||
|
if not self.props.settings:get("showNotifications") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local notifications = table.clone(self.state.notifications)
|
||||||
|
table.insert(notifications, {
|
||||||
|
text = text,
|
||||||
|
timestamp = DateTime.now().UnixTimestampMillis,
|
||||||
|
timeout = timeout or 3,
|
||||||
|
})
|
||||||
|
|
||||||
|
self:setState({
|
||||||
|
notifications = notifications,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function App:closeNotification(index: number)
|
||||||
|
local notifications = table.clone(self.state.notifications)
|
||||||
|
table.remove(notifications, index)
|
||||||
|
|
||||||
|
self:setState({
|
||||||
|
notifications = notifications,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
function App:getHostAndPort()
|
function App:getHostAndPort()
|
||||||
local host = self.host:getValue()
|
local host = self.host:getValue()
|
||||||
local port = self.port:getValue()
|
local port = self.port:getValue()
|
||||||
@@ -81,6 +110,7 @@ function App:startSession()
|
|||||||
appStatus = AppStatus.Connecting,
|
appStatus = AppStatus.Connecting,
|
||||||
toolbarIcon = Assets.Images.PluginButton,
|
toolbarIcon = Assets.Images.PluginButton,
|
||||||
})
|
})
|
||||||
|
self:addNotification("Connecting to session...")
|
||||||
elseif status == ServeSession.Status.Connected then
|
elseif status == ServeSession.Status.Connected then
|
||||||
local address = ("%s:%s"):format(host, port)
|
local address = ("%s:%s"):format(host, port)
|
||||||
self:setState({
|
self:setState({
|
||||||
@@ -89,8 +119,7 @@ function App:startSession()
|
|||||||
address = address,
|
address = address,
|
||||||
toolbarIcon = Assets.Images.PluginButtonConnected,
|
toolbarIcon = Assets.Images.PluginButtonConnected,
|
||||||
})
|
})
|
||||||
|
self:addNotification(string.format("Connected to session '%s' at %s.", details, address), 5)
|
||||||
Log.info("Connected to session '{}' at {}", details, address)
|
|
||||||
elseif status == ServeSession.Status.Disconnected then
|
elseif status == ServeSession.Status.Disconnected then
|
||||||
self.serveSession = nil
|
self.serveSession = nil
|
||||||
|
|
||||||
@@ -104,13 +133,13 @@ function App:startSession()
|
|||||||
errorMessage = tostring(details),
|
errorMessage = tostring(details),
|
||||||
toolbarIcon = Assets.Images.PluginButtonWarning,
|
toolbarIcon = Assets.Images.PluginButtonWarning,
|
||||||
})
|
})
|
||||||
|
self:addNotification(tostring(details), 10)
|
||||||
else
|
else
|
||||||
self:setState({
|
self:setState({
|
||||||
appStatus = AppStatus.NotConnected,
|
appStatus = AppStatus.NotConnected,
|
||||||
toolbarIcon = Assets.Images.PluginButton,
|
toolbarIcon = Assets.Images.PluginButton,
|
||||||
})
|
})
|
||||||
|
self:addNotification("Disconnected from session.")
|
||||||
Log.info("Disconnected session")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
@@ -154,150 +183,162 @@ function App:render()
|
|||||||
value = self.props.plugin,
|
value = self.props.plugin,
|
||||||
}, {
|
}, {
|
||||||
e(Theme.StudioProvider, nil, {
|
e(Theme.StudioProvider, nil, {
|
||||||
e(PluginSettings.StudioProvider, {
|
gui = e(StudioPluginGui, {
|
||||||
plugin = self.props.plugin,
|
id = pluginName,
|
||||||
|
title = pluginName,
|
||||||
|
active = self.state.guiEnabled,
|
||||||
|
|
||||||
|
initDockState = Enum.InitialDockState.Right,
|
||||||
|
initEnabled = false,
|
||||||
|
overridePreviousState = false,
|
||||||
|
floatingSize = Vector2.new(300, 200),
|
||||||
|
minimumSize = Vector2.new(300, 200),
|
||||||
|
|
||||||
|
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||||
|
|
||||||
|
onInitialState = function(initialState)
|
||||||
|
self:setState({
|
||||||
|
guiEnabled = initialState,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
|
||||||
|
onClose = function()
|
||||||
|
self:setState({
|
||||||
|
guiEnabled = false,
|
||||||
|
})
|
||||||
|
end,
|
||||||
}, {
|
}, {
|
||||||
gui = e(StudioPluginGui, {
|
NotConnectedPage = createPageElement(AppStatus.NotConnected, {
|
||||||
id = pluginName,
|
host = self.host,
|
||||||
title = pluginName,
|
onHostChange = self.setHost,
|
||||||
active = self.state.guiEnabled,
|
port = self.port,
|
||||||
|
onPortChange = self.setPort,
|
||||||
|
|
||||||
initDockState = Enum.InitialDockState.Right,
|
onConnect = function()
|
||||||
initEnabled = false,
|
self:startSession()
|
||||||
overridePreviousState = false,
|
end,
|
||||||
floatingSize = Vector2.new(300, 200),
|
|
||||||
minimumSize = Vector2.new(300, 200),
|
|
||||||
|
|
||||||
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
onNavigateSettings = function()
|
||||||
|
|
||||||
onInitialState = function(initialState)
|
|
||||||
self:setState({
|
self:setState({
|
||||||
guiEnabled = initialState,
|
appStatus = AppStatus.Settings,
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Connecting = createPageElement(AppStatus.Connecting),
|
||||||
|
|
||||||
|
Connected = createPageElement(AppStatus.Connected, {
|
||||||
|
projectName = self.state.projectName,
|
||||||
|
address = self.state.address,
|
||||||
|
|
||||||
|
onDisconnect = function()
|
||||||
|
self:endSession()
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Settings = createPageElement(AppStatus.Settings, {
|
||||||
|
onBack = function()
|
||||||
|
self:setState({
|
||||||
|
appStatus = AppStatus.NotConnected,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Error = createPageElement(AppStatus.Error, {
|
||||||
|
errorMessage = self.state.errorMessage,
|
||||||
|
|
||||||
onClose = function()
|
onClose = function()
|
||||||
self:setState({
|
self:setState({
|
||||||
guiEnabled = false,
|
appStatus = AppStatus.NotConnected,
|
||||||
|
toolbarIcon = Assets.Images.PluginButton,
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
}, {
|
|
||||||
NotConnectedPage = createPageElement(AppStatus.NotConnected, {
|
|
||||||
host = self.host,
|
|
||||||
onHostChange = self.setHost,
|
|
||||||
port = self.port,
|
|
||||||
onPortChange = self.setPort,
|
|
||||||
|
|
||||||
onConnect = function()
|
|
||||||
self:startSession()
|
|
||||||
end,
|
|
||||||
|
|
||||||
onNavigateSettings = function()
|
|
||||||
self:setState({
|
|
||||||
appStatus = AppStatus.Settings,
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Connecting = createPageElement(AppStatus.Connecting),
|
|
||||||
|
|
||||||
Connected = createPageElement(AppStatus.Connected, {
|
|
||||||
projectName = self.state.projectName,
|
|
||||||
address = self.state.address,
|
|
||||||
|
|
||||||
onDisconnect = function()
|
|
||||||
self:endSession()
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Settings = createPageElement(AppStatus.Settings, {
|
|
||||||
onBack = function()
|
|
||||||
self:setState({
|
|
||||||
appStatus = AppStatus.NotConnected,
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Error = createPageElement(AppStatus.Error, {
|
|
||||||
errorMessage = self.state.errorMessage,
|
|
||||||
|
|
||||||
onClose = function()
|
|
||||||
self:setState({
|
|
||||||
appStatus = AppStatus.NotConnected,
|
|
||||||
toolbarIcon = Assets.Images.PluginButton,
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Background = Theme.with(function(theme)
|
|
||||||
return e("Frame", {
|
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
|
||||||
BackgroundColor3 = theme.BackgroundColor,
|
|
||||||
ZIndex = 0,
|
|
||||||
BorderSizePixel = 0,
|
|
||||||
})
|
|
||||||
end),
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
toggleAction = e(StudioPluginAction, {
|
Background = Theme.with(function(theme)
|
||||||
name = "RojoConnection",
|
return e("Frame", {
|
||||||
title = "Rojo: Connect/Disconnect",
|
Size = UDim2.new(1, 0, 1, 0),
|
||||||
description = "Toggles the server for a Rojo sync session",
|
BackgroundColor3 = theme.BackgroundColor,
|
||||||
icon = Assets.Images.PluginButton,
|
ZIndex = 0,
|
||||||
bindable = true,
|
BorderSizePixel = 0,
|
||||||
onTriggered = function()
|
|
||||||
if self.serveSession == nil or self.serveSession:getStatus() == ServeSession.Status.NotStarted then
|
|
||||||
self:startSession()
|
|
||||||
elseif self.serveSession ~= nil and self.serveSession:getStatus() == ServeSession.Status.Connected then
|
|
||||||
self:endSession()
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
|
|
||||||
connectAction = e(StudioPluginAction, {
|
|
||||||
name = "RojoConnect",
|
|
||||||
title = "Rojo: Connect",
|
|
||||||
description = "Connects the server for a Rojo sync session",
|
|
||||||
icon = Assets.Images.PluginButton,
|
|
||||||
bindable = true,
|
|
||||||
onTriggered = function()
|
|
||||||
if self.serveSession == nil or self.serveSession:getStatus() == ServeSession.Status.NotStarted then
|
|
||||||
self:startSession()
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
|
|
||||||
disconnectAction = e(StudioPluginAction, {
|
|
||||||
name = "RojoDisconnect",
|
|
||||||
title = "Rojo: Disconnect",
|
|
||||||
description = "Disconnects the server for a Rojo sync session",
|
|
||||||
icon = Assets.Images.PluginButton,
|
|
||||||
bindable = true,
|
|
||||||
onTriggered = function()
|
|
||||||
if self.serveSession ~= nil and self.serveSession:getStatus() == ServeSession.Status.Connected then
|
|
||||||
self:endSession()
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
|
|
||||||
toolbar = e(StudioToolbar, {
|
|
||||||
name = pluginName,
|
|
||||||
}, {
|
|
||||||
button = e(StudioToggleButton, {
|
|
||||||
name = "Rojo",
|
|
||||||
tooltip = "Show or hide the Rojo panel",
|
|
||||||
icon = self.state.toolbarIcon,
|
|
||||||
active = self.state.guiEnabled,
|
|
||||||
enabled = true,
|
|
||||||
onClick = function()
|
|
||||||
self:setState(function(state)
|
|
||||||
return {
|
|
||||||
guiEnabled = not state.guiEnabled,
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
end,
|
|
||||||
})
|
})
|
||||||
|
end),
|
||||||
|
}),
|
||||||
|
|
||||||
|
RojoNotifications = e("ScreenGui", {}, {
|
||||||
|
layout = e("UIListLayout", {
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Bottom,
|
||||||
|
Padding = UDim.new(0, 5),
|
||||||
}),
|
}),
|
||||||
|
notifs = e(Notifications, {
|
||||||
|
soundPlayer = self.props.soundPlayer,
|
||||||
|
notifications = self.state.notifications,
|
||||||
|
onClose = function(index)
|
||||||
|
self:closeNotification(index)
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
|
toggleAction = e(StudioPluginAction, {
|
||||||
|
name = "RojoConnection",
|
||||||
|
title = "Rojo: Connect/Disconnect",
|
||||||
|
description = "Toggles the server for a Rojo sync session",
|
||||||
|
icon = Assets.Images.PluginButton,
|
||||||
|
bindable = true,
|
||||||
|
onTriggered = function()
|
||||||
|
if self.serveSession == nil or self.serveSession:getStatus() == ServeSession.Status.NotStarted then
|
||||||
|
self:startSession()
|
||||||
|
elseif self.serveSession ~= nil and self.serveSession:getStatus() == ServeSession.Status.Connected then
|
||||||
|
self:endSession()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
|
||||||
|
connectAction = e(StudioPluginAction, {
|
||||||
|
name = "RojoConnect",
|
||||||
|
title = "Rojo: Connect",
|
||||||
|
description = "Connects the server for a Rojo sync session",
|
||||||
|
icon = Assets.Images.PluginButton,
|
||||||
|
bindable = true,
|
||||||
|
onTriggered = function()
|
||||||
|
if self.serveSession == nil or self.serveSession:getStatus() == ServeSession.Status.NotStarted then
|
||||||
|
self:startSession()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
|
||||||
|
disconnectAction = e(StudioPluginAction, {
|
||||||
|
name = "RojoDisconnect",
|
||||||
|
title = "Rojo: Disconnect",
|
||||||
|
description = "Disconnects the server for a Rojo sync session",
|
||||||
|
icon = Assets.Images.PluginButton,
|
||||||
|
bindable = true,
|
||||||
|
onTriggered = function()
|
||||||
|
if self.serveSession ~= nil and self.serveSession:getStatus() == ServeSession.Status.Connected then
|
||||||
|
self:endSession()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
|
||||||
|
toolbar = e(StudioToolbar, {
|
||||||
|
name = pluginName,
|
||||||
|
}, {
|
||||||
|
button = e(StudioToggleButton, {
|
||||||
|
name = "Rojo",
|
||||||
|
tooltip = "Show or hide the Rojo panel",
|
||||||
|
icon = self.state.toolbarIcon,
|
||||||
|
active = self.state.guiEnabled,
|
||||||
|
enabled = true,
|
||||||
|
onClick = function()
|
||||||
|
self:setState(function(state)
|
||||||
|
return {
|
||||||
|
guiEnabled = not state.guiEnabled,
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -308,10 +349,11 @@ return function(props)
|
|||||||
plugin = props.plugin,
|
plugin = props.plugin,
|
||||||
}, {
|
}, {
|
||||||
App = PluginSettings.with(function(settings)
|
App = PluginSettings.with(function(settings)
|
||||||
local settingsProps = Dictionary.merge(props, {
|
local mergedProps = Dictionary.merge(props, {
|
||||||
settings = settings,
|
settings = settings,
|
||||||
|
soundPlayer = soundPlayer.new(settings),
|
||||||
})
|
})
|
||||||
return e(App, settingsProps)
|
return e(App, mergedProps)
|
||||||
end),
|
end),
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
@@ -45,6 +45,9 @@ local Assets = {
|
|||||||
[500] = "rbxassetid://2609138523"
|
[500] = "rbxassetid://2609138523"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Sounds = {
|
||||||
|
Notification = "rbxassetid://203785492",
|
||||||
|
},
|
||||||
StartSession = "",
|
StartSession = "",
|
||||||
SessionActive = "",
|
SessionActive = "",
|
||||||
Configure = "",
|
Configure = "",
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
|
|||||||
return strict("Config", {
|
return strict("Config", {
|
||||||
isDevBuild = isDevBuild,
|
isDevBuild = isDevBuild,
|
||||||
codename = "Epiphany",
|
codename = "Epiphany",
|
||||||
version = {7, 1, 0},
|
version = {7, 2, 1},
|
||||||
expectedServerVersionString = "7.0 or newer",
|
expectedServerVersionString = "7.2 or newer",
|
||||||
protocolVersion = 4,
|
protocolVersion = 4,
|
||||||
defaultHost = "localhost",
|
defaultHost = "localhost",
|
||||||
defaultPort = 34872,
|
defaultPort = 34872,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ local App = require(script.App)
|
|||||||
local app = Roact.createElement(App, {
|
local app = Roact.createElement(App, {
|
||||||
plugin = plugin
|
plugin = plugin
|
||||||
})
|
})
|
||||||
local tree = Roact.mount(app, nil, "Rojo UI")
|
local tree = Roact.mount(app, game:GetService("CoreGui"), "Rojo UI")
|
||||||
|
|
||||||
plugin.Unloading:Connect(function()
|
plugin.Unloading:Connect(function()
|
||||||
Roact.unmount(tree)
|
Roact.unmount(tree)
|
||||||
|
|||||||
35
plugin/src/soundPlayer.lua
Normal file
35
plugin/src/soundPlayer.lua
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
-- Sounds only play in Edit mode when parented to a plugin widget, for some reason
|
||||||
|
local plugin = plugin or script:FindFirstAncestorWhichIsA("Plugin")
|
||||||
|
local widget = plugin:CreateDockWidgetPluginGui("Rojo_soundPlayer", DockWidgetPluginGuiInfo.new(
|
||||||
|
Enum.InitialDockState.Float,
|
||||||
|
false, true,
|
||||||
|
10, 10,
|
||||||
|
10, 10
|
||||||
|
))
|
||||||
|
widget.Name = "Rojo_soundPlayer"
|
||||||
|
widget.Title = "Rojo Sound Player"
|
||||||
|
|
||||||
|
local SoundPlayer = {}
|
||||||
|
SoundPlayer.__index = SoundPlayer
|
||||||
|
|
||||||
|
function SoundPlayer.new(settings)
|
||||||
|
return setmetatable({
|
||||||
|
settings = settings,
|
||||||
|
}, SoundPlayer)
|
||||||
|
end
|
||||||
|
|
||||||
|
function SoundPlayer:play(soundId)
|
||||||
|
if self.settings and self.settings:get("playSounds") == false then return end
|
||||||
|
|
||||||
|
local sound = Instance.new("Sound")
|
||||||
|
sound.SoundId = soundId
|
||||||
|
sound.Parent = widget
|
||||||
|
|
||||||
|
sound.Ended:Connect(function()
|
||||||
|
sound:Destroy()
|
||||||
|
end)
|
||||||
|
|
||||||
|
sound:Play()
|
||||||
|
end
|
||||||
|
|
||||||
|
return SoundPlayer
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/build.rs
|
||||||
|
assertion_line: 99
|
||||||
|
expression: contents
|
||||||
|
---
|
||||||
|
<roblox version="4">
|
||||||
|
<Item class="Folder" referent="0">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">attributes</string>
|
||||||
|
</Properties>
|
||||||
|
<Item class="Folder" referent="1">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">Explicit</string>
|
||||||
|
<BinaryString name="AttributesSerialize">AgAAAAUAAABIZWxsbwIFAAAAV29ybGQGAAAAVmVjdG9yEQAAgD8AAABAAABAQA==</BinaryString>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
<Item class="Folder" referent="2">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">ImplicitAttributes</string>
|
||||||
|
<BinaryString name="AttributesSerialize">AgAAAAMAAABIZXkCBwAAAEdyYW5kbWEGAAAAVmVjdG9yEQAAgEAAAKBAAADAQA==</BinaryString>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/build.rs
|
||||||
|
assertion_line: 98
|
||||||
|
expression: contents
|
||||||
|
---
|
||||||
|
<roblox version="4">
|
||||||
|
<Item class="LocalScript" referent="0">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">issue_546</string>
|
||||||
|
<bool name="Disabled">true</bool>
|
||||||
|
<string name="Source">print("Hello, world!")</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
---
|
---
|
||||||
source: tests/tests/build.rs
|
source: tests/tests/build.rs
|
||||||
|
assertion_line: 99
|
||||||
expression: contents
|
expression: contents
|
||||||
|
|
||||||
---
|
---
|
||||||
<roblox version="4">
|
<roblox version="4">
|
||||||
<Item class="Folder" referent="0">
|
<Item class="Folder" referent="0">
|
||||||
<Properties>
|
<Properties>
|
||||||
<string name="Name">weldconstraint</string>
|
<string name="Name">weldconstraint</string>
|
||||||
<BinaryString name="AttributesSerialize">
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
||||||
</BinaryString>
|
|
||||||
<int64 name="SourceAssetId">-1</int64>
|
<int64 name="SourceAssetId">-1</int64>
|
||||||
<BinaryString name="Tags"></BinaryString>
|
<BinaryString name="Tags"></BinaryString>
|
||||||
</Properties>
|
</Properties>
|
||||||
@@ -16,8 +15,7 @@ expression: contents
|
|||||||
<Properties>
|
<Properties>
|
||||||
<string name="Name">A</string>
|
<string name="Name">A</string>
|
||||||
<bool name="Anchored">false</bool>
|
<bool name="Anchored">false</bool>
|
||||||
<BinaryString name="AttributesSerialize">
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
||||||
</BinaryString>
|
|
||||||
<float name="BackParamA">-0.5</float>
|
<float name="BackParamA">-0.5</float>
|
||||||
<float name="BackParamB">0.5</float>
|
<float name="BackParamB">0.5</float>
|
||||||
<token name="BackSurface">0</token>
|
<token name="BackSurface">0</token>
|
||||||
@@ -108,8 +106,7 @@ expression: contents
|
|||||||
<Item class="WeldConstraint" referent="2">
|
<Item class="WeldConstraint" referent="2">
|
||||||
<Properties>
|
<Properties>
|
||||||
<string name="Name">WeldConstraint</string>
|
<string name="Name">WeldConstraint</string>
|
||||||
<BinaryString name="AttributesSerialize">
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
||||||
</BinaryString>
|
|
||||||
<CoordinateFrame name="CFrame0">
|
<CoordinateFrame name="CFrame0">
|
||||||
<X>7</X>
|
<X>7</X>
|
||||||
<Y>0.000001013279</Y>
|
<Y>0.000001013279</Y>
|
||||||
@@ -136,8 +133,7 @@ expression: contents
|
|||||||
<Properties>
|
<Properties>
|
||||||
<string name="Name">B</string>
|
<string name="Name">B</string>
|
||||||
<bool name="Anchored">false</bool>
|
<bool name="Anchored">false</bool>
|
||||||
<BinaryString name="AttributesSerialize">
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
||||||
</BinaryString>
|
|
||||||
<float name="BackParamA">-0.5</float>
|
<float name="BackParamA">-0.5</float>
|
||||||
<float name="BackParamB">0.5</float>
|
<float name="BackParamB">0.5</float>
|
||||||
<token name="BackSurface">0</token>
|
<token name="BackSurface">0</token>
|
||||||
|
|||||||
36
rojo-test/build-tests/attributes/default.project.json
Normal file
36
rojo-test/build-tests/attributes/default.project.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "attributes",
|
||||||
|
"tree": {
|
||||||
|
"$className": "Folder",
|
||||||
|
|
||||||
|
"Explicit": {
|
||||||
|
"$className": "Folder",
|
||||||
|
"$properties": {
|
||||||
|
"Attributes": {
|
||||||
|
"Attributes": {
|
||||||
|
"Hello": {
|
||||||
|
"String": "World"
|
||||||
|
},
|
||||||
|
"Vector": {
|
||||||
|
"Vector3": [1, 2, 3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"ImplicitAttributes": {
|
||||||
|
"$className": "Folder",
|
||||||
|
"$properties": {
|
||||||
|
"Attributes": {
|
||||||
|
"Hey": {
|
||||||
|
"String": "Grandma"
|
||||||
|
},
|
||||||
|
"Vector": {
|
||||||
|
"Vector3": [4, 5, 6]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
rojo-test/build-tests/issue_546/README.md
Normal file
2
rojo-test/build-tests/issue_546/README.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Issue #546 (https://github.com/rojo-rbx/rojo/issues/546)
|
||||||
|
Regression from Rojo 6.2.0 to Rojo 7.0.0. Meta files named as init.meta.json should apply after init.client.lua and other init files.
|
||||||
6
rojo-test/build-tests/issue_546/default.project.json
Normal file
6
rojo-test/build-tests/issue_546/default.project.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "issue_546",
|
||||||
|
"tree": {
|
||||||
|
"$path": "hello"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
rojo-test/build-tests/issue_546/hello/init.client.lua
Normal file
1
rojo-test/build-tests/issue_546/hello/init.client.lua
Normal file
@@ -0,0 +1 @@
|
|||||||
|
print("Hello, world!")
|
||||||
5
rojo-test/build-tests/issue_546/hello/init.meta.json
Normal file
5
rojo-test/build-tests/issue_546/hello/init.meta.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"Disabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -291,7 +291,7 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: Ref) -> Option<
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let patch_set = compute_patch_set(snapshot.as_ref(), &tree, id);
|
let patch_set = compute_patch_set(snapshot, &tree, id);
|
||||||
apply_patch_set(tree, patch_set)
|
apply_patch_set(tree, patch_set)
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
@@ -334,7 +334,7 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: Ref) -> Option<
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let patch_set = compute_patch_set(snapshot.as_ref(), &tree, id);
|
let patch_set = compute_patch_set(snapshot, &tree, id);
|
||||||
apply_patch_set(tree, patch_set)
|
apply_patch_set(tree, patch_set)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use std::{
|
use std::{
|
||||||
io::{BufWriter, Write},
|
io::{BufWriter, Write},
|
||||||
|
mem::forget,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use clap::Parser;
|
||||||
use fs_err::File;
|
use fs_err::File;
|
||||||
use memofs::Vfs;
|
use memofs::Vfs;
|
||||||
use structopt::StructOpt;
|
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
use crate::serve_session::ServeSession;
|
use crate::serve_session::ServeSession;
|
||||||
@@ -17,20 +18,20 @@ const UNKNOWN_OUTPUT_KIND_ERR: &str = "Could not detect what kind of file to bui
|
|||||||
Expected output file to end in .rbxl, .rbxlx, .rbxm, or .rbxmx.";
|
Expected output file to end in .rbxl, .rbxlx, .rbxm, or .rbxmx.";
|
||||||
|
|
||||||
/// Generates a model or place file from the Rojo project.
|
/// Generates a model or place file from the Rojo project.
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct BuildCommand {
|
pub struct BuildCommand {
|
||||||
/// Path to the project to serve. Defaults to the current directory.
|
/// Path to the project to serve. Defaults to the current directory.
|
||||||
#[structopt(default_value = "")]
|
#[clap(default_value = "")]
|
||||||
pub project: PathBuf,
|
pub project: PathBuf,
|
||||||
|
|
||||||
/// Where to output the result.
|
/// Where to output the result.
|
||||||
///
|
///
|
||||||
/// Should end in .rbxm, .rbxl, .rbxmx, or .rbxlx.
|
/// Should end in .rbxm, .rbxl, .rbxmx, or .rbxlx.
|
||||||
#[structopt(long, short)]
|
#[clap(long, short)]
|
||||||
pub output: PathBuf,
|
pub output: PathBuf,
|
||||||
|
|
||||||
/// Whether to automatically rebuild when any input files change.
|
/// Whether to automatically rebuild when any input files change.
|
||||||
#[structopt(long)]
|
#[clap(long)]
|
||||||
pub watch: bool,
|
pub watch: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +62,10 @@ impl BuildCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid dropping ServeSession: it's potentially VERY expensive to drop
|
||||||
|
// and we're about to exit anyways.
|
||||||
|
forget(session);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,6 +102,7 @@ fn xml_encode_config() -> rbx_xml::EncodeOptions {
|
|||||||
rbx_xml::EncodeOptions::new().property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown)
|
rbx_xml::EncodeOptions::new().property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[profiling::function]
|
||||||
fn write_model(
|
fn write_model(
|
||||||
session: &ServeSession,
|
session: &ServeSession,
|
||||||
output: &Path,
|
output: &Path,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use structopt::StructOpt;
|
use clap::Parser;
|
||||||
|
|
||||||
/// Open Rojo's documentation in your browser.
|
/// Open Rojo's documentation in your browser.
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct DocCommand {}
|
pub struct DocCommand {}
|
||||||
|
|
||||||
impl DocCommand {
|
impl DocCommand {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use structopt::StructOpt;
|
use clap::Parser;
|
||||||
|
|
||||||
use crate::project::Project;
|
use crate::project::Project;
|
||||||
|
|
||||||
/// Reformat a Rojo project using the standard JSON formatting rules.
|
/// Reformat a Rojo project using the standard JSON formatting rules.
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct FmtProjectCommand {
|
pub struct FmtProjectCommand {
|
||||||
/// Path to the project to format. Defaults to the current directory.
|
/// Path to the project to format. Defaults to the current directory.
|
||||||
#[structopt(default_value = "")]
|
#[clap(default_value = "")]
|
||||||
pub project: PathBuf,
|
pub project: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ use std::process::{Command, Stdio};
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::{bail, format_err};
|
use anyhow::{bail, format_err};
|
||||||
|
use clap::Parser;
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
use fs_err::OpenOptions;
|
use fs_err::OpenOptions;
|
||||||
use structopt::StructOpt;
|
|
||||||
|
|
||||||
use super::resolve_path;
|
use super::resolve_path;
|
||||||
|
|
||||||
@@ -22,14 +22,14 @@ static PLACE_README: &str = include_str!("../../assets/default-place-project/REA
|
|||||||
static PLACE_GIT_IGNORE: &str = include_str!("../../assets/default-place-project/gitignore.txt");
|
static PLACE_GIT_IGNORE: &str = include_str!("../../assets/default-place-project/gitignore.txt");
|
||||||
|
|
||||||
/// Initializes a new Rojo project.
|
/// Initializes a new Rojo project.
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct InitCommand {
|
pub struct InitCommand {
|
||||||
/// Path to the place to create the project. Defaults to the current directory.
|
/// Path to the place to create the project. Defaults to the current directory.
|
||||||
#[structopt(default_value = "")]
|
#[clap(default_value = "")]
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
|
||||||
/// The kind of project to create, 'place' or 'model'. Defaults to place.
|
/// The kind of project to create, 'place' or 'model'. Defaults to place.
|
||||||
#[structopt(long, default_value = "place")]
|
#[clap(long, default_value = "place")]
|
||||||
pub kind: InitKind,
|
pub kind: InitKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//! Defines Rojo's CLI through structopt types.
|
//! Defines Rojo's CLI through clap types.
|
||||||
|
|
||||||
mod build;
|
mod build;
|
||||||
mod doc;
|
mod doc;
|
||||||
@@ -11,7 +11,7 @@ mod upload;
|
|||||||
|
|
||||||
use std::{borrow::Cow, env, path::Path, str::FromStr};
|
use std::{borrow::Cow, env, path::Path, str::FromStr};
|
||||||
|
|
||||||
use structopt::StructOpt;
|
use clap::Parser;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub use self::build::BuildCommand;
|
pub use self::build::BuildCommand;
|
||||||
@@ -23,15 +23,15 @@ pub use self::serve::ServeCommand;
|
|||||||
pub use self::sourcemap::SourcemapCommand;
|
pub use self::sourcemap::SourcemapCommand;
|
||||||
pub use self::upload::UploadCommand;
|
pub use self::upload::UploadCommand;
|
||||||
|
|
||||||
/// Command line options that Rojo accepts, defined using the structopt crate.
|
/// Command line options that Rojo accepts, defined using the clap crate.
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, Parser)]
|
||||||
#[structopt(name = "Rojo", about, author)]
|
#[clap(name = "Rojo", version, about, author)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
#[structopt(flatten)]
|
#[clap(flatten)]
|
||||||
pub global: GlobalOptions,
|
pub global: GlobalOptions,
|
||||||
|
|
||||||
/// Subcommand to run in this invocation.
|
/// Subcommand to run in this invocation.
|
||||||
#[structopt(subcommand)]
|
#[clap(subcommand)]
|
||||||
pub subcommand: Subcommand,
|
pub subcommand: Subcommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,14 +50,14 @@ impl Options {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct GlobalOptions {
|
pub struct GlobalOptions {
|
||||||
/// Sets verbosity level. Can be specified multiple times.
|
/// Sets verbosity level. Can be specified multiple times.
|
||||||
#[structopt(long("verbose"), short, global(true), parse(from_occurrences))]
|
#[clap(long("verbose"), short, global(true), parse(from_occurrences))]
|
||||||
pub verbosity: u8,
|
pub verbosity: u8,
|
||||||
|
|
||||||
/// Set color behavior. Valid values are auto, always, and never.
|
/// Set color behavior. Valid values are auto, always, and never.
|
||||||
#[structopt(long("color"), global(true), default_value("auto"))]
|
#[clap(long("color"), global(true), default_value("auto"))]
|
||||||
pub color: ColorChoice,
|
pub color: ColorChoice,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ pub struct ColorChoiceParseError {
|
|||||||
attempted: String,
|
attempted: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, Parser)]
|
||||||
pub enum Subcommand {
|
pub enum Subcommand {
|
||||||
Init(InitCommand),
|
Init(InitCommand),
|
||||||
Serve(ServeCommand),
|
Serve(ServeCommand),
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ use std::{
|
|||||||
io::BufWriter,
|
io::BufWriter,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
use memofs::{InMemoryFs, Vfs, VfsSnapshot};
|
use memofs::{InMemoryFs, Vfs, VfsSnapshot};
|
||||||
use roblox_install::RobloxStudio;
|
use roblox_install::RobloxStudio;
|
||||||
use structopt::StructOpt;
|
|
||||||
|
|
||||||
use crate::serve_session::ServeSession;
|
use crate::serve_session::ServeSession;
|
||||||
|
|
||||||
@@ -13,14 +13,14 @@ static PLUGIN_BINCODE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/plugin.
|
|||||||
static PLUGIN_FILE_NAME: &str = "RojoManagedPlugin.rbxm";
|
static PLUGIN_FILE_NAME: &str = "RojoManagedPlugin.rbxm";
|
||||||
|
|
||||||
/// Install Rojo's plugin.
|
/// Install Rojo's plugin.
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct PluginCommand {
|
pub struct PluginCommand {
|
||||||
#[structopt(subcommand)]
|
#[clap(subcommand)]
|
||||||
subcommand: PluginSubcommand,
|
subcommand: PluginSubcommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manages Rojo's Roblox Studio plugin.
|
/// Manages Rojo's Roblox Studio plugin.
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, Parser)]
|
||||||
pub enum PluginSubcommand {
|
pub enum PluginSubcommand {
|
||||||
/// Install the plugin in Roblox Studio's plugins folder. If the plugin is
|
/// Install the plugin in Roblox Studio's plugins folder. If the plugin is
|
||||||
/// already installed, installing it again will overwrite the current plugin
|
/// already installed, installing it again will overwrite the current plugin
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
use memofs::Vfs;
|
use memofs::Vfs;
|
||||||
use structopt::StructOpt;
|
|
||||||
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
|
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
|
||||||
|
|
||||||
use crate::{serve_session::ServeSession, web::LiveServer};
|
use crate::{serve_session::ServeSession, web::LiveServer};
|
||||||
@@ -17,19 +17,19 @@ const DEFAULT_BIND_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
|
|||||||
const DEFAULT_PORT: u16 = 34872;
|
const DEFAULT_PORT: u16 = 34872;
|
||||||
|
|
||||||
/// Expose a Rojo project to the Rojo Studio plugin.
|
/// Expose a Rojo project to the Rojo Studio plugin.
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct ServeCommand {
|
pub struct ServeCommand {
|
||||||
/// Path to the project to serve. Defaults to the current directory.
|
/// Path to the project to serve. Defaults to the current directory.
|
||||||
#[structopt(default_value = "")]
|
#[clap(default_value = "")]
|
||||||
pub project: PathBuf,
|
pub project: PathBuf,
|
||||||
|
|
||||||
/// The IP address to listen on. Defaults to `127.0.0.1`.
|
/// The IP address to listen on. Defaults to `127.0.0.1`.
|
||||||
#[structopt(long)]
|
#[clap(long)]
|
||||||
pub address: Option<IpAddr>,
|
pub address: Option<IpAddr>,
|
||||||
|
|
||||||
/// The port to listen on. Defaults to the project's preference, or `34872` if
|
/// The port to listen on. Defaults to the project's preference, or `34872` if
|
||||||
/// it has none.
|
/// it has none.
|
||||||
#[structopt(long)]
|
#[clap(long)]
|
||||||
pub port: Option<u16>,
|
pub port: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,15 +67,17 @@ fn show_start_message(bind_address: IpAddr, port: u16, color: ColorChoice) -> io
|
|||||||
let writer = BufferWriter::stdout(color);
|
let writer = BufferWriter::stdout(color);
|
||||||
let mut buffer = writer.buffer();
|
let mut buffer = writer.buffer();
|
||||||
|
|
||||||
|
let address_string = if bind_address.is_loopback() {
|
||||||
|
"localhost".to_owned()
|
||||||
|
} else {
|
||||||
|
bind_address.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
writeln!(&mut buffer, "Rojo server listening:")?;
|
writeln!(&mut buffer, "Rojo server listening:")?;
|
||||||
|
|
||||||
write!(&mut buffer, " Address: ")?;
|
write!(&mut buffer, " Address: ")?;
|
||||||
buffer.set_color(&green)?;
|
buffer.set_color(&green)?;
|
||||||
if bind_address.is_loopback() {
|
writeln!(&mut buffer, "{}", address_string)?;
|
||||||
writeln!(&mut buffer, "localhost")?;
|
|
||||||
} else {
|
|
||||||
writeln!(&mut buffer, "{}", bind_address)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.set_color(&ColorSpec::new())?;
|
buffer.set_color(&ColorSpec::new())?;
|
||||||
write!(&mut buffer, " Port: ")?;
|
write!(&mut buffer, " Port: ")?;
|
||||||
@@ -88,7 +90,7 @@ fn show_start_message(bind_address: IpAddr, port: u16, color: ColorChoice) -> io
|
|||||||
write!(&mut buffer, "Visit ")?;
|
write!(&mut buffer, "Visit ")?;
|
||||||
|
|
||||||
buffer.set_color(&green)?;
|
buffer.set_color(&green)?;
|
||||||
write!(&mut buffer, "http://localhost:{}/", port)?;
|
write!(&mut buffer, "http://{}:{}/", address_string, port)?;
|
||||||
|
|
||||||
buffer.set_color(&ColorSpec::new())?;
|
buffer.set_color(&ColorSpec::new())?;
|
||||||
writeln!(&mut buffer, " in your browser for more information.")?;
|
writeln!(&mut buffer, " in your browser for more information.")?;
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
use fs_err::File;
|
use fs_err::File;
|
||||||
use memofs::Vfs;
|
use memofs::Vfs;
|
||||||
use rbx_dom_weak::types::Ref;
|
use rbx_dom_weak::types::Ref;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use structopt::StructOpt;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
serve_session::ServeSession,
|
serve_session::ServeSession,
|
||||||
@@ -33,22 +33,22 @@ struct SourcemapNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a sourcemap file from the Rojo project.
|
/// Generates a sourcemap file from the Rojo project.
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct SourcemapCommand {
|
pub struct SourcemapCommand {
|
||||||
/// Path to the project to use for the sourcemap. Defaults to the current
|
/// Path to the project to use for the sourcemap. Defaults to the current
|
||||||
/// directory.
|
/// directory.
|
||||||
#[structopt(default_value = "")]
|
#[clap(default_value = "")]
|
||||||
pub project: PathBuf,
|
pub project: PathBuf,
|
||||||
|
|
||||||
/// Where to output the sourcemap. Omit this to use stdout instead of
|
/// Where to output the sourcemap. Omit this to use stdout instead of
|
||||||
/// writing to a file.
|
/// writing to a file.
|
||||||
///
|
///
|
||||||
/// Should end in .json.
|
/// Should end in .json.
|
||||||
#[structopt(long, short)]
|
#[clap(long, short)]
|
||||||
pub output: Option<PathBuf>,
|
pub output: Option<PathBuf>,
|
||||||
|
|
||||||
/// If non-script files should be included or not. Defaults to false.
|
/// If non-script files should be included or not. Defaults to false.
|
||||||
#[structopt(long)]
|
#[clap(long)]
|
||||||
pub include_non_scripts: bool,
|
pub include_non_scripts: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,9 +56,6 @@ impl SourcemapCommand {
|
|||||||
pub fn run(self) -> anyhow::Result<()> {
|
pub fn run(self) -> anyhow::Result<()> {
|
||||||
let project_path = resolve_path(&self.project);
|
let project_path = resolve_path(&self.project);
|
||||||
|
|
||||||
let mut project_dir = project_path.to_path_buf();
|
|
||||||
project_dir.pop();
|
|
||||||
|
|
||||||
log::trace!("Constructing in-memory filesystem");
|
log::trace!("Constructing in-memory filesystem");
|
||||||
let vfs = Vfs::new_default();
|
let vfs = Vfs::new_default();
|
||||||
|
|
||||||
@@ -71,7 +68,7 @@ impl SourcemapCommand {
|
|||||||
filter_non_scripts
|
filter_non_scripts
|
||||||
};
|
};
|
||||||
|
|
||||||
let root_node = recurse_create_node(&tree, tree.get_root_id(), &project_dir, filter);
|
let root_node = recurse_create_node(&tree, tree.get_root_id(), session.root_dir(), filter);
|
||||||
|
|
||||||
if let Some(output_path) = self.output {
|
if let Some(output_path) = self.output {
|
||||||
let mut file = BufWriter::new(File::create(&output_path)?);
|
let mut file = BufWriter::new(File::create(&output_path)?);
|
||||||
|
|||||||
@@ -2,38 +2,38 @@ use std::path::PathBuf;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::{bail, format_err, Context};
|
use anyhow::{bail, format_err, Context};
|
||||||
|
use clap::Parser;
|
||||||
use memofs::Vfs;
|
use memofs::Vfs;
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT},
|
header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
};
|
};
|
||||||
use structopt::StructOpt;
|
|
||||||
|
|
||||||
use crate::{auth_cookie::get_auth_cookie, serve_session::ServeSession};
|
use crate::{auth_cookie::get_auth_cookie, serve_session::ServeSession};
|
||||||
|
|
||||||
use super::resolve_path;
|
use super::resolve_path;
|
||||||
|
|
||||||
/// Builds the project and uploads it to Roblox.
|
/// Builds the project and uploads it to Roblox.
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct UploadCommand {
|
pub struct UploadCommand {
|
||||||
/// Path to the project to upload. Defaults to the current directory.
|
/// Path to the project to upload. Defaults to the current directory.
|
||||||
#[structopt(default_value = "")]
|
#[clap(default_value = "")]
|
||||||
pub project: PathBuf,
|
pub project: PathBuf,
|
||||||
|
|
||||||
/// Authenication cookie to use. If not specified, Rojo will attempt to find one from the system automatically.
|
/// Authenication cookie to use. If not specified, Rojo will attempt to find one from the system automatically.
|
||||||
#[structopt(long)]
|
#[clap(long)]
|
||||||
pub cookie: Option<String>,
|
pub cookie: Option<String>,
|
||||||
|
|
||||||
/// API key obtained from create.roblox.com/credentials. Rojo will use the Open Cloud API when this is provided. Only supports uploading to a place.
|
/// API key obtained from create.roblox.com/credentials. Rojo will use the Open Cloud API when this is provided. Only supports uploading to a place.
|
||||||
#[structopt(long = "api_key")]
|
#[clap(long = "api_key")]
|
||||||
pub api_key: Option<String>,
|
pub api_key: Option<String>,
|
||||||
|
|
||||||
/// The Universe ID of the given place. Required when using the Open Cloud API.
|
/// The Universe ID of the given place. Required when using the Open Cloud API.
|
||||||
#[structopt(long = "universe_id")]
|
#[clap(long = "universe_id")]
|
||||||
pub universe_id: Option<u64>,
|
pub universe_id: Option<u64>,
|
||||||
|
|
||||||
/// Asset ID to upload to.
|
/// Asset ID to upload to.
|
||||||
#[structopt(long = "asset_id")]
|
#[clap(long = "asset_id")]
|
||||||
pub asset_id: u64,
|
pub asset_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ fn do_upload(buffer: Vec<u8>, asset_id: u64, cookie: &str) -> anyhow::Result<()>
|
|||||||
asset_id
|
asset_id
|
||||||
);
|
);
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
|
|
||||||
let build_request = move || {
|
let build_request = move || {
|
||||||
client
|
client
|
||||||
@@ -172,10 +172,10 @@ fn do_upload_open_cloud(
|
|||||||
universe_id, asset_id
|
universe_id, asset_id
|
||||||
);
|
);
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
|
|
||||||
log::debug!("Uploading to Roblox...");
|
log::debug!("Uploading to Roblox...");
|
||||||
let mut response = client
|
let response = client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
.header("x-api-key", api_key)
|
.header("x-api-key", api_key)
|
||||||
.header(CONTENT_TYPE, "application/xml")
|
.header(CONTENT_TYPE, "application/xml")
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
use std::{env, panic, process};
|
use std::{env, panic, process};
|
||||||
|
|
||||||
use backtrace::Backtrace;
|
use backtrace::Backtrace;
|
||||||
use structopt::StructOpt;
|
use clap::Parser;
|
||||||
|
|
||||||
use librojo::cli::Options;
|
use librojo::cli::Options;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
#[cfg(feature = "profile-with-tracy")]
|
||||||
|
tracy_client::Client::start();
|
||||||
|
|
||||||
panic::set_hook(Box::new(|panic_info| {
|
panic::set_hook(Box::new(|panic_info| {
|
||||||
// PanicInfo's payload is usually a &'static str or String.
|
// PanicInfo's payload is usually a &'static str or String.
|
||||||
// See: https://doc.rust-lang.org/beta/std/panic/struct.PanicInfo.html#method.payload
|
// See: https://doc.rust-lang.org/beta/std/panic/struct.PanicInfo.html#method.payload
|
||||||
@@ -49,7 +52,7 @@ fn main() {
|
|||||||
process::exit(1);
|
process::exit(1);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let options = Options::from_args();
|
let options = Options::parse();
|
||||||
|
|
||||||
let log_filter = match options.global.verbosity {
|
let log_filter = match options.global.verbosity {
|
||||||
0 => "info",
|
0 => "info",
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ impl<T: Clone> MessageQueue<T> {
|
|||||||
/// This method is only useful in tests. Non-test code should use subscribe
|
/// This method is only useful in tests. Non-test code should use subscribe
|
||||||
/// instead.
|
/// instead.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
#[allow(unused)]
|
||||||
pub fn subscribe_any(&self) -> oneshot::Receiver<(u32, Vec<T>)> {
|
pub fn subscribe_any(&self) -> oneshot::Receiver<(u32, Vec<T>)> {
|
||||||
let cursor = {
|
let cursor = {
|
||||||
let messages = self.messages.read().unwrap();
|
let messages = self.messages.read().unwrap();
|
||||||
|
|||||||
@@ -36,17 +36,3 @@ where
|
|||||||
|
|
||||||
seq.end()
|
seq.end()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize_option_absolute<S, T>(
|
|
||||||
maybe_path: &Option<T>,
|
|
||||||
serializer: S,
|
|
||||||
) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
T: AsRef<Path>,
|
|
||||||
{
|
|
||||||
match maybe_path {
|
|
||||||
Some(path) => serialize_absolute(path, serializer),
|
|
||||||
None => serializer.serialize_none(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ use std::borrow::Borrow;
|
|||||||
|
|
||||||
use anyhow::format_err;
|
use anyhow::format_err;
|
||||||
use rbx_dom_weak::types::{
|
use rbx_dom_weak::types::{
|
||||||
CFrame, Color3, Content, Enum, Matrix3, Tags, Variant, VariantType, Vector2, Vector3,
|
Attributes, CFrame, Color3, Content, Enum, Matrix3, Tags, Variant, VariantType, Vector2,
|
||||||
|
Vector3,
|
||||||
};
|
};
|
||||||
use rbx_reflection::{DataType, PropertyDescriptor};
|
use rbx_reflection::{DataType, PropertyDescriptor};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -40,6 +41,7 @@ pub enum AmbiguousValue {
|
|||||||
Array3([f64; 3]),
|
Array3([f64; 3]),
|
||||||
Array4([f64; 4]),
|
Array4([f64; 4]),
|
||||||
Array12([f64; 12]),
|
Array12([f64; 12]),
|
||||||
|
Attributes(Attributes),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AmbiguousValue {
|
impl AmbiguousValue {
|
||||||
@@ -128,6 +130,8 @@ impl AmbiguousValue {
|
|||||||
Ok(CFrame::new(pos, orientation).into())
|
Ok(CFrame::new(pos, orientation).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(VariantType::Attributes, AmbiguousValue::Attributes(value)) => Ok(value.into()),
|
||||||
|
|
||||||
(_, unresolved) => Err(format_err!(
|
(_, unresolved) => Err(format_err!(
|
||||||
"Wrong type of value for property {}.{}. Expected {:?}, got {}",
|
"Wrong type of value for property {}.{}. Expected {:?}, got {}",
|
||||||
class_name,
|
class_name,
|
||||||
@@ -154,6 +158,7 @@ impl AmbiguousValue {
|
|||||||
AmbiguousValue::Array3(_) => "an array of three numbers",
|
AmbiguousValue::Array3(_) => "an array of three numbers",
|
||||||
AmbiguousValue::Array4(_) => "an array of four numbers",
|
AmbiguousValue::Array4(_) => "an array of four numbers",
|
||||||
AmbiguousValue::Array12(_) => "an array of twelve numbers",
|
AmbiguousValue::Array12(_) => "an array of twelve numbers",
|
||||||
|
AmbiguousValue::Attributes(_) => "an object containing attributes",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ impl ServeSession {
|
|||||||
let snapshot = snapshot_from_vfs(&instance_context, &vfs, &start_path)?;
|
let snapshot = snapshot_from_vfs(&instance_context, &vfs, &start_path)?;
|
||||||
|
|
||||||
log::trace!("Computing initial patch set");
|
log::trace!("Computing initial patch set");
|
||||||
let patch_set = compute_patch_set(snapshot.as_ref(), &tree, root_id);
|
let patch_set = compute_patch_set(snapshot, &tree, root_id);
|
||||||
|
|
||||||
log::trace!("Applying initial patch set");
|
log::trace!("Applying initial patch set");
|
||||||
apply_patch_set(&mut tree, patch_set);
|
apply_patch_set(&mut tree, patch_set);
|
||||||
@@ -216,6 +216,10 @@ impl ServeSession {
|
|||||||
pub fn serve_address(&self) -> Option<IpAddr> {
|
pub fn serve_address(&self) -> Option<IpAddr> {
|
||||||
self.root_project.serve_address
|
self.root_project.serve_address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn root_dir(&self) -> &Path {
|
||||||
|
self.root_project.folder_location()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::{borrow::Cow, collections::HashMap};
|
|||||||
|
|
||||||
use rbx_dom_weak::{
|
use rbx_dom_weak::{
|
||||||
types::{Ref, Variant},
|
types::{Ref, Variant},
|
||||||
WeakDom,
|
Instance, WeakDom,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -102,22 +102,29 @@ impl InstanceSnapshot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_tree(tree: &WeakDom, id: Ref) -> Self {
|
#[profiling::function]
|
||||||
let instance = tree.get_by_ref(id).expect("instance did not exist in tree");
|
pub fn from_tree(tree: WeakDom, id: Ref) -> Self {
|
||||||
|
let (_, mut raw_tree) = tree.into_raw();
|
||||||
|
Self::from_raw_tree(&mut raw_tree, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_raw_tree(raw_tree: &mut HashMap<Ref, Instance>, id: Ref) -> Self {
|
||||||
|
let instance = raw_tree
|
||||||
|
.remove(&id)
|
||||||
|
.expect("instance did not exist in tree");
|
||||||
|
|
||||||
let children = instance
|
let children = instance
|
||||||
.children()
|
.children()
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.map(|&id| Self::from_raw_tree(raw_tree, id))
|
||||||
.map(|id| Self::from_tree(tree, id))
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
snapshot_id: Some(id),
|
snapshot_id: Some(id),
|
||||||
metadata: InstanceMetadata::default(),
|
metadata: InstanceMetadata::default(),
|
||||||
name: Cow::Owned(instance.name.clone()),
|
name: Cow::Owned(instance.name),
|
||||||
class_name: Cow::Owned(instance.class.clone()),
|
class_name: Cow::Owned(instance.class),
|
||||||
properties: instance.properties.clone(),
|
properties: instance.properties,
|
||||||
children,
|
children,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
//! Defines the algorithm for applying generated patches.
|
//! Defines the algorithm for applying generated patches.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
mem::take,
|
||||||
|
};
|
||||||
|
|
||||||
use rbx_dom_weak::types::{Ref, Variant};
|
use rbx_dom_weak::types::{Ref, Variant};
|
||||||
|
|
||||||
@@ -12,21 +15,31 @@ use super::{
|
|||||||
/// Consumes the input `PatchSet`, applying all of its prescribed changes to the
|
/// Consumes the input `PatchSet`, applying all of its prescribed changes to the
|
||||||
/// tree and returns an `AppliedPatchSet`, which can be used to keep another
|
/// tree and returns an `AppliedPatchSet`, which can be used to keep another
|
||||||
/// tree in sync with Rojo's.
|
/// tree in sync with Rojo's.
|
||||||
|
#[profiling::function]
|
||||||
pub fn apply_patch_set(tree: &mut RojoTree, patch_set: PatchSet) -> AppliedPatchSet {
|
pub fn apply_patch_set(tree: &mut RojoTree, patch_set: PatchSet) -> AppliedPatchSet {
|
||||||
let mut context = PatchApplyContext::default();
|
let mut context = PatchApplyContext::default();
|
||||||
|
|
||||||
for removed_id in patch_set.removed_instances {
|
{
|
||||||
apply_remove_instance(&mut context, tree, removed_id);
|
profiling::scope!("removals");
|
||||||
|
for removed_id in patch_set.removed_instances {
|
||||||
|
apply_remove_instance(&mut context, tree, removed_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for add_patch in patch_set.added_instances {
|
{
|
||||||
apply_add_child(&mut context, tree, add_patch.parent_id, add_patch.instance);
|
profiling::scope!("additions");
|
||||||
|
for add_patch in patch_set.added_instances {
|
||||||
|
apply_add_child(&mut context, tree, add_patch.parent_id, add_patch.instance);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates need to be applied after additions, which reduces the complexity
|
{
|
||||||
// of updates significantly.
|
profiling::scope!("updates");
|
||||||
for update_patch in patch_set.updated_instances {
|
// Updates need to be applied after additions, which reduces the complexity
|
||||||
apply_update_child(&mut context, tree, update_patch);
|
// of updates significantly.
|
||||||
|
for update_patch in patch_set.updated_instances {
|
||||||
|
apply_update_child(&mut context, tree, update_patch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finalize_patch_application(context, tree)
|
finalize_patch_application(context, tree)
|
||||||
@@ -55,20 +68,9 @@ struct PatchApplyContext {
|
|||||||
/// eachother.
|
/// eachother.
|
||||||
snapshot_id_to_instance_id: HashMap<Ref, Ref>,
|
snapshot_id_to_instance_id: HashMap<Ref, Ref>,
|
||||||
|
|
||||||
/// The properties of instances added by the current `PatchSet`.
|
/// Tracks all of the instances added by this patch that have refs that need
|
||||||
///
|
/// to be rewritten.
|
||||||
/// Instances added to the tree can refer to eachother via Ref properties,
|
has_refs_to_rewrite: HashSet<Ref>,
|
||||||
/// but we need to make sure they're correctly transformed from snapshot
|
|
||||||
/// space into tree space (via `snapshot_id_to_instance_id`).
|
|
||||||
///
|
|
||||||
/// It's not possible to do that transformation for refs that refer to added
|
|
||||||
/// instances until all the instances have actually been inserted into the
|
|
||||||
/// tree. For simplicity, we defer application of _all_ properties on added
|
|
||||||
/// instances instead of just Refs.
|
|
||||||
///
|
|
||||||
/// This doesn't affect updated instances, since they're always applied
|
|
||||||
/// after we've added all the instances from the patch.
|
|
||||||
added_instance_properties: HashMap<Ref, HashMap<String, Variant>>,
|
|
||||||
|
|
||||||
/// The current applied patch result, describing changes made to the tree.
|
/// The current applied patch result, describing changes made to the tree.
|
||||||
applied_patch_set: AppliedPatchSet,
|
applied_patch_set: AppliedPatchSet,
|
||||||
@@ -84,23 +86,22 @@ struct PatchApplyContext {
|
|||||||
/// The remaining Ref properties need to be handled during patch application,
|
/// The remaining Ref properties need to be handled during patch application,
|
||||||
/// where we build up a map of snapshot IDs to instance IDs as they're created,
|
/// where we build up a map of snapshot IDs to instance IDs as they're created,
|
||||||
/// then apply properties all at once at the end.
|
/// then apply properties all at once at the end.
|
||||||
|
#[profiling::function]
|
||||||
fn finalize_patch_application(context: PatchApplyContext, tree: &mut RojoTree) -> AppliedPatchSet {
|
fn finalize_patch_application(context: PatchApplyContext, tree: &mut RojoTree) -> AppliedPatchSet {
|
||||||
for (id, properties) in context.added_instance_properties {
|
for id in context.has_refs_to_rewrite {
|
||||||
// This should always succeed since instances marked as added in our
|
// This should always succeed since instances marked as added in our
|
||||||
// patch should be added without fail.
|
// patch should be added without fail.
|
||||||
let mut instance = tree
|
let mut instance = tree
|
||||||
.get_instance_mut(id)
|
.get_instance_mut(id)
|
||||||
.expect("Invalid instance ID in deferred property map");
|
.expect("Invalid instance ID in deferred property map");
|
||||||
|
|
||||||
for (key, mut property_value) in properties {
|
for value in instance.properties_mut().values_mut() {
|
||||||
if let Variant::Ref(referent) = property_value {
|
if let Variant::Ref(referent) = value {
|
||||||
if let Some(&instance_referent) = context.snapshot_id_to_instance_id.get(&referent)
|
if let Some(&instance_referent) = context.snapshot_id_to_instance_id.get(&referent)
|
||||||
{
|
{
|
||||||
property_value = Variant::Ref(instance_referent);
|
*value = Variant::Ref(instance_referent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
instance.properties_mut().insert(key, property_value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,24 +117,24 @@ fn apply_add_child(
|
|||||||
context: &mut PatchApplyContext,
|
context: &mut PatchApplyContext,
|
||||||
tree: &mut RojoTree,
|
tree: &mut RojoTree,
|
||||||
parent_id: Ref,
|
parent_id: Ref,
|
||||||
snapshot: InstanceSnapshot,
|
mut snapshot: InstanceSnapshot,
|
||||||
) {
|
) {
|
||||||
let snapshot_id = snapshot.snapshot_id;
|
let snapshot_id = snapshot.snapshot_id;
|
||||||
let properties = snapshot.properties;
|
let children = take(&mut snapshot.children);
|
||||||
let children = snapshot.children;
|
|
||||||
|
|
||||||
// Property application is deferred until after all children
|
// If an object we're adding has a non-null referent, we'll note this
|
||||||
// are constructed. This helps apply referents correctly.
|
// instance down as needing to be revisited later.
|
||||||
let remaining_snapshot = InstanceSnapshot::new()
|
let has_refs = snapshot.properties.values().any(|value| match value {
|
||||||
.name(snapshot.name)
|
Variant::Ref(value) => value.is_some(),
|
||||||
.class_name(snapshot.class_name)
|
_ => false,
|
||||||
.metadata(snapshot.metadata)
|
});
|
||||||
.snapshot_id(snapshot.snapshot_id);
|
|
||||||
|
|
||||||
let id = tree.insert_instance(parent_id, remaining_snapshot);
|
let id = tree.insert_instance(parent_id, snapshot);
|
||||||
context.applied_patch_set.added.push(id);
|
context.applied_patch_set.added.push(id);
|
||||||
|
|
||||||
context.added_instance_properties.insert(id, properties);
|
if has_refs {
|
||||||
|
context.has_refs_to_rewrite.insert(id);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(snapshot_id) = snapshot_id {
|
if let Some(snapshot_id) = snapshot_id {
|
||||||
context.snapshot_id_to_instance_id.insert(snapshot_id, id);
|
context.snapshot_id_to_instance_id.insert(snapshot_id, id);
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
//! Defines the algorithm for computing a roughly-minimal patch set given an
|
//! Defines the algorithm for computing a roughly-minimal patch set given an
|
||||||
//! existing instance tree and an instance snapshot.
|
//! existing instance tree and an instance snapshot.
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
mem::take,
|
||||||
|
};
|
||||||
|
|
||||||
use rbx_dom_weak::types::{Ref, Variant};
|
use rbx_dom_weak::types::{Ref, Variant};
|
||||||
|
|
||||||
@@ -10,11 +13,8 @@ use super::{
|
|||||||
InstanceSnapshot, InstanceWithMeta, RojoTree,
|
InstanceSnapshot, InstanceWithMeta, RojoTree,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn compute_patch_set(
|
#[profiling::function]
|
||||||
snapshot: Option<&InstanceSnapshot>,
|
pub fn compute_patch_set(snapshot: Option<InstanceSnapshot>, tree: &RojoTree, id: Ref) -> PatchSet {
|
||||||
tree: &RojoTree,
|
|
||||||
id: Ref,
|
|
||||||
) -> PatchSet {
|
|
||||||
let mut patch_set = PatchSet::new();
|
let mut patch_set = PatchSet::new();
|
||||||
|
|
||||||
if let Some(snapshot) = snapshot {
|
if let Some(snapshot) = snapshot {
|
||||||
@@ -74,7 +74,7 @@ fn rewrite_refs_in_snapshot(context: &ComputePatchContext, snapshot: &mut Instan
|
|||||||
|
|
||||||
fn compute_patch_set_internal(
|
fn compute_patch_set_internal(
|
||||||
context: &mut ComputePatchContext,
|
context: &mut ComputePatchContext,
|
||||||
snapshot: &InstanceSnapshot,
|
mut snapshot: InstanceSnapshot,
|
||||||
tree: &RojoTree,
|
tree: &RojoTree,
|
||||||
id: Ref,
|
id: Ref,
|
||||||
patch_set: &mut PatchSet,
|
patch_set: &mut PatchSet,
|
||||||
@@ -87,12 +87,12 @@ fn compute_patch_set_internal(
|
|||||||
.get_instance(id)
|
.get_instance(id)
|
||||||
.expect("Instance did not exist in tree");
|
.expect("Instance did not exist in tree");
|
||||||
|
|
||||||
compute_property_patches(snapshot, &instance, patch_set);
|
compute_property_patches(&mut snapshot, &instance, patch_set);
|
||||||
compute_children_patches(context, snapshot, tree, id, patch_set);
|
compute_children_patches(context, &mut snapshot, tree, id, patch_set);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_property_patches(
|
fn compute_property_patches(
|
||||||
snapshot: &InstanceSnapshot,
|
snapshot: &mut InstanceSnapshot,
|
||||||
instance: &InstanceWithMeta,
|
instance: &InstanceWithMeta,
|
||||||
patch_set: &mut PatchSet,
|
patch_set: &mut PatchSet,
|
||||||
) {
|
) {
|
||||||
@@ -102,32 +102,32 @@ fn compute_property_patches(
|
|||||||
let changed_name = if snapshot.name == instance.name() {
|
let changed_name = if snapshot.name == instance.name() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(snapshot.name.clone().into_owned())
|
Some(take(&mut snapshot.name).into_owned())
|
||||||
};
|
};
|
||||||
|
|
||||||
let changed_class_name = if snapshot.class_name == instance.class_name() {
|
let changed_class_name = if snapshot.class_name == instance.class_name() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(snapshot.class_name.clone().into_owned())
|
Some(take(&mut snapshot.class_name).into_owned())
|
||||||
};
|
};
|
||||||
|
|
||||||
let changed_metadata = if &snapshot.metadata == instance.metadata() {
|
let changed_metadata = if &snapshot.metadata == instance.metadata() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(snapshot.metadata.clone())
|
Some(take(&mut snapshot.metadata))
|
||||||
};
|
};
|
||||||
|
|
||||||
for (name, snapshot_value) in &snapshot.properties {
|
for (name, snapshot_value) in take(&mut snapshot.properties) {
|
||||||
visited_properties.insert(name.as_str());
|
visited_properties.insert(name.clone());
|
||||||
|
|
||||||
match instance.properties().get(name) {
|
match instance.properties().get(&name) {
|
||||||
Some(instance_value) => {
|
Some(instance_value) => {
|
||||||
if snapshot_value != instance_value {
|
if &snapshot_value != instance_value {
|
||||||
changed_properties.insert(name.clone(), Some(snapshot_value.clone()));
|
changed_properties.insert(name, Some(snapshot_value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
changed_properties.insert(name.clone(), Some(snapshot_value.clone()));
|
changed_properties.insert(name, Some(snapshot_value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,7 @@ fn compute_property_patches(
|
|||||||
|
|
||||||
fn compute_children_patches(
|
fn compute_children_patches(
|
||||||
context: &mut ComputePatchContext,
|
context: &mut ComputePatchContext,
|
||||||
snapshot: &InstanceSnapshot,
|
snapshot: &mut InstanceSnapshot,
|
||||||
tree: &RojoTree,
|
tree: &RojoTree,
|
||||||
id: Ref,
|
id: Ref,
|
||||||
patch_set: &mut PatchSet,
|
patch_set: &mut PatchSet,
|
||||||
@@ -172,7 +172,7 @@ fn compute_children_patches(
|
|||||||
|
|
||||||
let mut paired_instances = vec![false; instance_children.len()];
|
let mut paired_instances = vec![false; instance_children.len()];
|
||||||
|
|
||||||
for snapshot_child in snapshot.children.iter() {
|
for snapshot_child in take(&mut snapshot.children) {
|
||||||
let matching_instance =
|
let matching_instance =
|
||||||
instance_children
|
instance_children
|
||||||
.iter()
|
.iter()
|
||||||
@@ -209,7 +209,7 @@ fn compute_children_patches(
|
|||||||
None => {
|
None => {
|
||||||
patch_set.added_instances.push(PatchAdd {
|
patch_set.added_instances.push(PatchAdd {
|
||||||
parent_id: id,
|
parent_id: id,
|
||||||
instance: snapshot_child.clone(),
|
instance: snapshot_child,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +257,7 @@ mod test {
|
|||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let patch_set = compute_patch_set(Some(&snapshot), &tree, root_id);
|
let patch_set = compute_patch_set(Some(snapshot), &tree, root_id);
|
||||||
|
|
||||||
let expected_patch_set = PatchSet {
|
let expected_patch_set = PatchSet {
|
||||||
updated_instances: vec![PatchUpdate {
|
updated_instances: vec![PatchUpdate {
|
||||||
@@ -307,7 +307,7 @@ mod test {
|
|||||||
class_name: Cow::Borrowed("foo"),
|
class_name: Cow::Borrowed("foo"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let patch_set = compute_patch_set(Some(&snapshot), &tree, root_id);
|
let patch_set = compute_patch_set(Some(snapshot), &tree, root_id);
|
||||||
|
|
||||||
let expected_patch_set = PatchSet {
|
let expected_patch_set = PatchSet {
|
||||||
added_instances: vec![PatchAdd {
|
added_instances: vec![PatchAdd {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ fn set_name_and_class_name() {
|
|||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let patch_set = compute_patch_set(Some(&snapshot), &tree, tree.get_root_id());
|
let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
|
||||||
let patch_value = redactions.redacted_yaml(patch_set);
|
let patch_value = redactions.redacted_yaml(patch_set);
|
||||||
|
|
||||||
assert_yaml_snapshot!(patch_value);
|
assert_yaml_snapshot!(patch_value);
|
||||||
@@ -47,7 +47,7 @@ fn set_property() {
|
|||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let patch_set = compute_patch_set(Some(&snapshot), &tree, tree.get_root_id());
|
let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
|
||||||
let patch_value = redactions.redacted_yaml(patch_set);
|
let patch_value = redactions.redacted_yaml(patch_set);
|
||||||
|
|
||||||
assert_yaml_snapshot!(patch_value);
|
assert_yaml_snapshot!(patch_value);
|
||||||
@@ -78,7 +78,7 @@ fn remove_property() {
|
|||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let patch_set = compute_patch_set(Some(&snapshot), &tree, tree.get_root_id());
|
let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
|
||||||
let patch_value = redactions.redacted_yaml(patch_set);
|
let patch_value = redactions.redacted_yaml(patch_set);
|
||||||
|
|
||||||
assert_yaml_snapshot!(patch_value);
|
assert_yaml_snapshot!(patch_value);
|
||||||
@@ -107,7 +107,7 @@ fn add_child() {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
let patch_set = compute_patch_set(Some(&snapshot), &tree, tree.get_root_id());
|
let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
|
||||||
let patch_value = redactions.redacted_yaml(patch_set);
|
let patch_value = redactions.redacted_yaml(patch_set);
|
||||||
|
|
||||||
assert_yaml_snapshot!(patch_value);
|
assert_yaml_snapshot!(patch_value);
|
||||||
@@ -139,7 +139,7 @@ fn remove_child() {
|
|||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let patch_set = compute_patch_set(Some(&snapshot), &tree, tree.get_root_id());
|
let patch_set = compute_patch_set(Some(snapshot), &tree, tree.get_root_id());
|
||||||
let patch_value = redactions.redacted_yaml(patch_set);
|
let patch_value = redactions.redacted_yaml(patch_set);
|
||||||
|
|
||||||
assert_yaml_snapshot!(patch_value);
|
assert_yaml_snapshot!(patch_value);
|
||||||
|
|||||||
@@ -87,8 +87,9 @@ impl RojoTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_instance(&mut self, parent_ref: Ref, snapshot: InstanceSnapshot) -> Ref {
|
pub fn insert_instance(&mut self, parent_ref: Ref, snapshot: InstanceSnapshot) -> Ref {
|
||||||
let builder = InstanceBuilder::new(snapshot.class_name.to_owned())
|
let builder = InstanceBuilder::empty()
|
||||||
.with_name(snapshot.name.to_owned())
|
.with_class(snapshot.class_name.into_owned())
|
||||||
|
.with_name(snapshot.name.into_owned())
|
||||||
.with_properties(snapshot.properties);
|
.with_properties(snapshot.properties);
|
||||||
|
|
||||||
let referent = self.inner.insert(parent_ref, builder);
|
let referent = self.inner.insert(parent_ref, builder);
|
||||||
|
|||||||
@@ -10,6 +10,40 @@ pub fn snapshot_dir(
|
|||||||
context: &InstanceContext,
|
context: &InstanceContext,
|
||||||
vfs: &Vfs,
|
vfs: &Vfs,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
|
) -> anyhow::Result<Option<InstanceSnapshot>> {
|
||||||
|
let mut snapshot = match snapshot_dir_no_meta(context, vfs, path)? {
|
||||||
|
Some(snapshot) => snapshot,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(mut meta) = dir_meta(vfs, path)? {
|
||||||
|
meta.apply_all(&mut snapshot)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(snapshot))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the meta file that should be applied for this directory, if it
|
||||||
|
/// exists.
|
||||||
|
pub fn dir_meta(vfs: &Vfs, path: &Path) -> anyhow::Result<Option<DirectoryMetadata>> {
|
||||||
|
let meta_path = path.join("init.meta.json");
|
||||||
|
|
||||||
|
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
|
||||||
|
let metadata = DirectoryMetadata::from_slice(&meta_contents, meta_path)?;
|
||||||
|
Ok(Some(metadata))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Snapshot a directory without applying meta files; useful for if the
|
||||||
|
/// directory's ClassName will change before metadata should be applied. For
|
||||||
|
/// example, this can happen if the directory contains an `init.client.lua`
|
||||||
|
/// file.
|
||||||
|
pub fn snapshot_dir_no_meta(
|
||||||
|
context: &InstanceContext,
|
||||||
|
vfs: &Vfs,
|
||||||
|
path: &Path,
|
||||||
) -> anyhow::Result<Option<InstanceSnapshot>> {
|
) -> anyhow::Result<Option<InstanceSnapshot>> {
|
||||||
let passes_filter_rules = |child: &DirEntry| {
|
let passes_filter_rules = |child: &DirEntry| {
|
||||||
context
|
context
|
||||||
@@ -48,11 +82,14 @@ pub fn snapshot_dir(
|
|||||||
// middleware. Should we figure out a way for that function to add
|
// middleware. Should we figure out a way for that function to add
|
||||||
// relevant paths to this middleware?
|
// relevant paths to this middleware?
|
||||||
path.join("init.lua"),
|
path.join("init.lua"),
|
||||||
|
path.join("init.luau"),
|
||||||
path.join("init.server.lua"),
|
path.join("init.server.lua"),
|
||||||
|
path.join("init.server.luau"),
|
||||||
path.join("init.client.lua"),
|
path.join("init.client.lua"),
|
||||||
|
path.join("init.client.luau"),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut snapshot = InstanceSnapshot::new()
|
let snapshot = InstanceSnapshot::new()
|
||||||
.name(instance_name)
|
.name(instance_name)
|
||||||
.class_name("Folder")
|
.class_name("Folder")
|
||||||
.children(snapshot_children)
|
.children(snapshot_children)
|
||||||
@@ -63,11 +100,6 @@ pub fn snapshot_dir(
|
|||||||
.context(context),
|
.context(context),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(meta_contents) = vfs.read(&meta_path).with_not_found()? {
|
|
||||||
let mut metadata = DirectoryMetadata::from_slice(&meta_contents, meta_path)?;
|
|
||||||
metadata.apply_all(&mut snapshot)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(snapshot))
|
Ok(Some(snapshot))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,25 @@ pub fn snapshot_json_model(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let instance: JsonModel = serde_json::from_str(contents_str)
|
let mut instance: JsonModel = serde_json::from_str(contents_str)
|
||||||
.with_context(|| format!("File is not a valid JSON model: {}", path.display()))?;
|
.with_context(|| format!("File is not a valid JSON model: {}", path.display()))?;
|
||||||
|
|
||||||
|
if let Some(top_level_name) = &instance.name {
|
||||||
|
let new_name = format!("{}.model.json", top_level_name);
|
||||||
|
|
||||||
|
log::warn!(
|
||||||
|
"Model at path {} had a top-level Name field. \
|
||||||
|
This field has been ignored since Rojo 6.0.\n\
|
||||||
|
Consider removing this field and renaming the file to {}.",
|
||||||
|
new_name,
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.name = Some(name.to_owned());
|
||||||
|
|
||||||
let mut snapshot = instance
|
let mut snapshot = instance
|
||||||
.core
|
.into_snapshot()
|
||||||
.into_snapshot(name.to_owned())
|
|
||||||
.with_context(|| format!("Could not load JSON model: {}", path.display()))?;
|
.with_context(|| format!("Could not load JSON model: {}", path.display()))?;
|
||||||
|
|
||||||
snapshot.metadata = snapshot
|
snapshot.metadata = snapshot
|
||||||
@@ -44,42 +57,37 @@ pub fn snapshot_json_model(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct JsonModel {
|
struct JsonModel {
|
||||||
|
#[serde(alias = "Name")]
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(alias = "ClassName")]
|
||||||
core: JsonModelCore,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
struct JsonModelInstance {
|
|
||||||
name: String,
|
|
||||||
|
|
||||||
#[serde(flatten)]
|
|
||||||
core: JsonModelCore,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
struct JsonModelCore {
|
|
||||||
class_name: String,
|
class_name: String,
|
||||||
|
|
||||||
#[serde(default = "Vec::new", skip_serializing_if = "Vec::is_empty")]
|
#[serde(
|
||||||
children: Vec<JsonModelInstance>,
|
alias = "Children",
|
||||||
|
default = "Vec::new",
|
||||||
|
skip_serializing_if = "Vec::is_empty"
|
||||||
|
)]
|
||||||
|
children: Vec<JsonModel>,
|
||||||
|
|
||||||
#[serde(default = "HashMap::new", skip_serializing_if = "HashMap::is_empty")]
|
#[serde(
|
||||||
|
alias = "Properties",
|
||||||
|
default = "HashMap::new",
|
||||||
|
skip_serializing_if = "HashMap::is_empty"
|
||||||
|
)]
|
||||||
properties: HashMap<String, UnresolvedValue>,
|
properties: HashMap<String, UnresolvedValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JsonModelCore {
|
impl JsonModel {
|
||||||
fn into_snapshot(self, name: String) -> anyhow::Result<InstanceSnapshot> {
|
fn into_snapshot(self) -> anyhow::Result<InstanceSnapshot> {
|
||||||
|
let name = self.name.unwrap_or_else(|| self.class_name.clone());
|
||||||
let class_name = self.class_name;
|
let class_name = self.class_name;
|
||||||
|
|
||||||
let mut children = Vec::with_capacity(self.children.len());
|
let mut children = Vec::with_capacity(self.children.len());
|
||||||
for child in self.children {
|
for child in self.children {
|
||||||
children.push(child.core.into_snapshot(child.name)?);
|
children.push(child.into_snapshot()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut properties = HashMap::with_capacity(self.properties.len());
|
let mut properties = HashMap::with_capacity(self.properties.len());
|
||||||
@@ -113,7 +121,43 @@ mod test {
|
|||||||
VfsSnapshot::file(
|
VfsSnapshot::file(
|
||||||
r#"
|
r#"
|
||||||
{
|
{
|
||||||
"Name": "children",
|
"className": "IntValue",
|
||||||
|
"properties": {
|
||||||
|
"Value": 5
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "The Child",
|
||||||
|
"className": "StringValue"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let vfs = Vfs::new(imfs);
|
||||||
|
|
||||||
|
let instance_snapshot = snapshot_json_model(
|
||||||
|
&InstanceContext::default(),
|
||||||
|
&vfs,
|
||||||
|
Path::new("/foo.model.json"),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
insta::assert_yaml_snapshot!(instance_snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn model_from_vfs_legacy() {
|
||||||
|
let mut imfs = InMemoryFs::new();
|
||||||
|
imfs.load_snapshot(
|
||||||
|
"/foo.model.json",
|
||||||
|
VfsSnapshot::file(
|
||||||
|
r#"
|
||||||
|
{
|
||||||
"ClassName": "IntValue",
|
"ClassName": "IntValue",
|
||||||
"Properties": {
|
"Properties": {
|
||||||
"Value": 5
|
"Value": 5
|
||||||
@@ -130,11 +174,11 @@ mod test {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut vfs = Vfs::new(imfs);
|
let vfs = Vfs::new(imfs);
|
||||||
|
|
||||||
let instance_snapshot = snapshot_json_model(
|
let instance_snapshot = snapshot_json_model(
|
||||||
&InstanceContext::default(),
|
&InstanceContext::default(),
|
||||||
&mut vfs,
|
&vfs,
|
||||||
Path::new("/foo.model.json"),
|
Path::new("/foo.model.json"),
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ use memofs::{IoResultExt, Vfs};
|
|||||||
|
|
||||||
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
|
use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
|
||||||
|
|
||||||
use super::{dir::snapshot_dir, meta_file::AdjacentMetadata, util::match_trailing};
|
use super::{
|
||||||
|
dir::{dir_meta, snapshot_dir_no_meta},
|
||||||
|
meta_file::AdjacentMetadata,
|
||||||
|
util::match_trailing,
|
||||||
|
};
|
||||||
|
|
||||||
/// Core routine for turning Lua files into snapshots.
|
/// Core routine for turning Lua files into snapshots.
|
||||||
pub fn snapshot_lua(
|
pub fn snapshot_lua(
|
||||||
@@ -23,6 +27,12 @@ pub fn snapshot_lua(
|
|||||||
("LocalScript", name)
|
("LocalScript", name)
|
||||||
} else if let Some(name) = match_trailing(&file_name, ".lua") {
|
} else if let Some(name) = match_trailing(&file_name, ".lua") {
|
||||||
("ModuleScript", name)
|
("ModuleScript", name)
|
||||||
|
} else if let Some(name) = match_trailing(&file_name, ".server.luau") {
|
||||||
|
("Script", name)
|
||||||
|
} else if let Some(name) = match_trailing(&file_name, ".client.luau") {
|
||||||
|
("LocalScript", name)
|
||||||
|
} else if let Some(name) = match_trailing(&file_name, ".luau") {
|
||||||
|
("ModuleScript", name)
|
||||||
} else {
|
} else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
@@ -66,7 +76,7 @@ pub fn snapshot_lua_init(
|
|||||||
init_path: &Path,
|
init_path: &Path,
|
||||||
) -> anyhow::Result<Option<InstanceSnapshot>> {
|
) -> anyhow::Result<Option<InstanceSnapshot>> {
|
||||||
let folder_path = init_path.parent().unwrap();
|
let folder_path = init_path.parent().unwrap();
|
||||||
let dir_snapshot = snapshot_dir(context, vfs, folder_path)?.unwrap();
|
let dir_snapshot = snapshot_dir_no_meta(context, vfs, folder_path)?.unwrap();
|
||||||
|
|
||||||
if dir_snapshot.class_name != "Folder" {
|
if dir_snapshot.class_name != "Folder" {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
@@ -86,6 +96,10 @@ pub fn snapshot_lua_init(
|
|||||||
init_snapshot.children = dir_snapshot.children;
|
init_snapshot.children = dir_snapshot.children;
|
||||||
init_snapshot.metadata = dir_snapshot.metadata;
|
init_snapshot.metadata = dir_snapshot.metadata;
|
||||||
|
|
||||||
|
if let Some(mut meta) = dir_meta(vfs, folder_path)? {
|
||||||
|
meta.apply_all(&mut init_snapshot)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Some(init_snapshot))
|
Ok(Some(init_snapshot))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ pub use self::project::snapshot_project_node;
|
|||||||
|
|
||||||
/// The main entrypoint to the snapshot function. This function can be pointed
|
/// The main entrypoint to the snapshot function. This function can be pointed
|
||||||
/// at any path and will return something if Rojo knows how to deal with it.
|
/// at any path and will return something if Rojo knows how to deal with it.
|
||||||
|
#[profiling::function]
|
||||||
pub fn snapshot_from_vfs(
|
pub fn snapshot_from_vfs(
|
||||||
context: &InstanceContext,
|
context: &InstanceContext,
|
||||||
vfs: &Vfs,
|
vfs: &Vfs,
|
||||||
@@ -56,16 +57,31 @@ pub fn snapshot_from_vfs(
|
|||||||
return snapshot_project(context, vfs, &project_path);
|
return snapshot_project(context, vfs, &project_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let init_path = path.join("init.luau");
|
||||||
|
if vfs.metadata(&init_path).with_not_found()?.is_some() {
|
||||||
|
return snapshot_lua_init(context, vfs, &init_path);
|
||||||
|
}
|
||||||
|
|
||||||
let init_path = path.join("init.lua");
|
let init_path = path.join("init.lua");
|
||||||
if vfs.metadata(&init_path).with_not_found()?.is_some() {
|
if vfs.metadata(&init_path).with_not_found()?.is_some() {
|
||||||
return snapshot_lua_init(context, vfs, &init_path);
|
return snapshot_lua_init(context, vfs, &init_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let init_path = path.join("init.server.luau");
|
||||||
|
if vfs.metadata(&init_path).with_not_found()?.is_some() {
|
||||||
|
return snapshot_lua_init(context, vfs, &init_path);
|
||||||
|
}
|
||||||
|
|
||||||
let init_path = path.join("init.server.lua");
|
let init_path = path.join("init.server.lua");
|
||||||
if vfs.metadata(&init_path).with_not_found()?.is_some() {
|
if vfs.metadata(&init_path).with_not_found()?.is_some() {
|
||||||
return snapshot_lua_init(context, vfs, &init_path);
|
return snapshot_lua_init(context, vfs, &init_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let init_path = path.join("init.client.luau");
|
||||||
|
if vfs.metadata(&init_path).with_not_found()?.is_some() {
|
||||||
|
return snapshot_lua_init(context, vfs, &init_path);
|
||||||
|
}
|
||||||
|
|
||||||
let init_path = path.join("init.client.lua");
|
let init_path = path.join("init.client.lua");
|
||||||
if vfs.metadata(&init_path).with_not_found()?.is_some() {
|
if vfs.metadata(&init_path).with_not_found()?.is_some() {
|
||||||
return snapshot_lua_init(context, vfs, &init_path);
|
return snapshot_lua_init(context, vfs, &init_path);
|
||||||
@@ -73,7 +89,11 @@ pub fn snapshot_from_vfs(
|
|||||||
|
|
||||||
snapshot_dir(context, vfs, path)
|
snapshot_dir(context, vfs, path)
|
||||||
} else {
|
} else {
|
||||||
if let Ok(name) = path.file_name_trim_end(".lua") {
|
let script_name = path
|
||||||
|
.file_name_trim_end(".lua")
|
||||||
|
.or_else(|_| path.file_name_trim_end(".luau"));
|
||||||
|
|
||||||
|
if let Ok(name) = script_name {
|
||||||
match name {
|
match name {
|
||||||
// init scripts are handled elsewhere and should not turn into
|
// init scripts are handled elsewhere and should not turn into
|
||||||
// their own children.
|
// their own children.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::snapshot::{InstanceContext, InstanceMetadata, InstanceSnapshot};
|
|||||||
|
|
||||||
use super::util::PathExt;
|
use super::util::PathExt;
|
||||||
|
|
||||||
|
#[profiling::function]
|
||||||
pub fn snapshot_rbxm(
|
pub fn snapshot_rbxm(
|
||||||
context: &InstanceContext,
|
context: &InstanceContext,
|
||||||
vfs: &Vfs,
|
vfs: &Vfs,
|
||||||
@@ -21,7 +22,8 @@ pub fn snapshot_rbxm(
|
|||||||
let children = root_instance.children();
|
let children = root_instance.children();
|
||||||
|
|
||||||
if children.len() == 1 {
|
if children.len() == 1 {
|
||||||
let snapshot = InstanceSnapshot::from_tree(&temp_tree, children[0])
|
let child = children[0];
|
||||||
|
let snapshot = InstanceSnapshot::from_tree(temp_tree, child)
|
||||||
.name(name)
|
.name(name)
|
||||||
.metadata(
|
.metadata(
|
||||||
InstanceMetadata::new()
|
InstanceMetadata::new()
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ pub fn snapshot_rbxmx(
|
|||||||
let children = root_instance.children();
|
let children = root_instance.children();
|
||||||
|
|
||||||
if children.len() == 1 {
|
if children.len() == 1 {
|
||||||
let snapshot = InstanceSnapshot::from_tree(&temp_tree, children[0])
|
let child = children[0];
|
||||||
|
let snapshot = InstanceSnapshot::from_tree(temp_tree, child)
|
||||||
.name(name)
|
.name(name)
|
||||||
.metadata(
|
.metadata(
|
||||||
InstanceMetadata::new()
|
InstanceMetadata::new()
|
||||||
|
|||||||
@@ -11,10 +11,14 @@ metadata:
|
|||||||
- /foo
|
- /foo
|
||||||
- /foo/init.meta.json
|
- /foo/init.meta.json
|
||||||
- /foo/init.lua
|
- /foo/init.lua
|
||||||
|
- /foo/init.luau
|
||||||
- /foo/init.server.lua
|
- /foo/init.server.lua
|
||||||
|
- /foo/init.server.luau
|
||||||
- /foo/init.client.lua
|
- /foo/init.client.lua
|
||||||
|
- /foo/init.client.luau
|
||||||
context: {}
|
context: {}
|
||||||
name: foo
|
name: foo
|
||||||
class_name: Folder
|
class_name: Folder
|
||||||
properties: {}
|
properties: {}
|
||||||
children: []
|
children: []
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,11 @@ metadata:
|
|||||||
- /foo
|
- /foo
|
||||||
- /foo/init.meta.json
|
- /foo/init.meta.json
|
||||||
- /foo/init.lua
|
- /foo/init.lua
|
||||||
|
- /foo/init.luau
|
||||||
- /foo/init.server.lua
|
- /foo/init.server.lua
|
||||||
|
- /foo/init.server.luau
|
||||||
- /foo/init.client.lua
|
- /foo/init.client.lua
|
||||||
|
- /foo/init.client.luau
|
||||||
context: {}
|
context: {}
|
||||||
name: foo
|
name: foo
|
||||||
class_name: Folder
|
class_name: Folder
|
||||||
@@ -27,10 +30,14 @@ children:
|
|||||||
- /foo/Child
|
- /foo/Child
|
||||||
- /foo/Child/init.meta.json
|
- /foo/Child/init.meta.json
|
||||||
- /foo/Child/init.lua
|
- /foo/Child/init.lua
|
||||||
|
- /foo/Child/init.luau
|
||||||
- /foo/Child/init.server.lua
|
- /foo/Child/init.server.lua
|
||||||
|
- /foo/Child/init.server.luau
|
||||||
- /foo/Child/init.client.lua
|
- /foo/Child/init.client.lua
|
||||||
|
- /foo/Child/init.client.luau
|
||||||
context: {}
|
context: {}
|
||||||
name: Child
|
name: Child
|
||||||
class_name: Folder
|
class_name: Folder
|
||||||
properties: {}
|
properties: {}
|
||||||
children: []
|
children: []
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
source: src/snapshot_middleware/json_model.rs
|
||||||
|
assertion_line: 186
|
||||||
|
expression: instance_snapshot
|
||||||
|
---
|
||||||
|
snapshot_id: ~
|
||||||
|
metadata:
|
||||||
|
ignore_unknown_instances: false
|
||||||
|
instigating_source:
|
||||||
|
Path: /foo.model.json
|
||||||
|
relevant_paths:
|
||||||
|
- /foo.model.json
|
||||||
|
context: {}
|
||||||
|
name: foo
|
||||||
|
class_name: IntValue
|
||||||
|
properties:
|
||||||
|
Value:
|
||||||
|
Int64: 5
|
||||||
|
children:
|
||||||
|
- snapshot_id: ~
|
||||||
|
metadata:
|
||||||
|
ignore_unknown_instances: false
|
||||||
|
relevant_paths: []
|
||||||
|
context: {}
|
||||||
|
name: The Child
|
||||||
|
class_name: StringValue
|
||||||
|
properties: {}
|
||||||
|
children: []
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ impl ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If this instance is represented by a script, try to find the correct .lua
|
/// If this instance is represented by a script, try to find the correct .lua or .luau
|
||||||
/// file to open to edit it.
|
/// file to open to edit it.
|
||||||
fn pick_script_path(instance: InstanceWithMeta<'_>) -> Option<PathBuf> {
|
fn pick_script_path(instance: InstanceWithMeta<'_>) -> Option<PathBuf> {
|
||||||
match instance.class_name() {
|
match instance.class_name() {
|
||||||
@@ -252,16 +252,17 @@ fn pick_script_path(instance: InstanceWithMeta<'_>) -> Option<PathBuf> {
|
|||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick the first listed relevant path that has an extension of .lua that
|
// Pick the first listed relevant path that has an extension of .lua or .luau that
|
||||||
// exists.
|
// exists.
|
||||||
instance
|
instance
|
||||||
.metadata()
|
.metadata()
|
||||||
.relevant_paths
|
.relevant_paths
|
||||||
.iter()
|
.iter()
|
||||||
.find(|path| {
|
.find(|path| {
|
||||||
// We should only ever open Lua files to be safe.
|
// We should only ever open Lua or Luau files to be safe.
|
||||||
match path.extension().and_then(|ext| ext.to_str()) {
|
match path.extension().and_then(|ext| ext.to_str()) {
|
||||||
Some("lua") => {}
|
Some("lua") => {}
|
||||||
|
Some("luau") => {}
|
||||||
_ => return false,
|
_ => return false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
test-projects/attributes/default.project.json
Normal file
16
test-projects/attributes/default.project.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "attributes",
|
||||||
|
"tree": {
|
||||||
|
"$className": "DataModel",
|
||||||
|
"Workspace": {
|
||||||
|
"Folder": {
|
||||||
|
"$className": "Folder",
|
||||||
|
"$properties": {
|
||||||
|
"Attributes": {
|
||||||
|
"Hello": { "Vector3": [1, 2, 3] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
test-projects/tags/default.project.json
Normal file
14
test-projects/tags/default.project.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "tags",
|
||||||
|
"tree": {
|
||||||
|
"$className": "DataModel",
|
||||||
|
"Workspace": {
|
||||||
|
"Folder": {
|
||||||
|
"$className": "Folder",
|
||||||
|
"$properties": {
|
||||||
|
"Tags": ["Hello", "World"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
testez.toml
66
testez.toml
@@ -1,66 +0,0 @@
|
|||||||
[[afterAll.args]]
|
|
||||||
type = "function"
|
|
||||||
|
|
||||||
[[afterEach.args]]
|
|
||||||
type = "function"
|
|
||||||
|
|
||||||
[[beforeAll.args]]
|
|
||||||
type = "function"
|
|
||||||
|
|
||||||
[[beforeEach.args]]
|
|
||||||
type = "function"
|
|
||||||
|
|
||||||
[[describe.args]]
|
|
||||||
type = "string"
|
|
||||||
|
|
||||||
[[describe.args]]
|
|
||||||
type = "function"
|
|
||||||
|
|
||||||
[[describeFOCUS.args]]
|
|
||||||
type = "string"
|
|
||||||
|
|
||||||
[[describeFOCUS.args]]
|
|
||||||
type = "function"
|
|
||||||
|
|
||||||
[[describeSKIP.args]]
|
|
||||||
type = "string"
|
|
||||||
|
|
||||||
[[describeSKIP.args]]
|
|
||||||
type = "function"
|
|
||||||
|
|
||||||
[[expect.args]]
|
|
||||||
type = "any"
|
|
||||||
|
|
||||||
[[FIXME.args]]
|
|
||||||
type = "string"
|
|
||||||
required = false
|
|
||||||
|
|
||||||
[FOCUS]
|
|
||||||
args = []
|
|
||||||
|
|
||||||
[[it.args]]
|
|
||||||
type = "string"
|
|
||||||
|
|
||||||
[[it.args]]
|
|
||||||
type = "function"
|
|
||||||
|
|
||||||
[[itFIXME.args]]
|
|
||||||
type = "string"
|
|
||||||
|
|
||||||
[[itFIXME.args]]
|
|
||||||
type = "function"
|
|
||||||
|
|
||||||
[[itFOCUS.args]]
|
|
||||||
type = "string"
|
|
||||||
|
|
||||||
[[itFOCUS.args]]
|
|
||||||
type = "function"
|
|
||||||
|
|
||||||
[[itSKIP.args]]
|
|
||||||
type = "string"
|
|
||||||
|
|
||||||
[[itSKIP.args]]
|
|
||||||
type = "function"
|
|
||||||
|
|
||||||
[SKIP]
|
|
||||||
args = []
|
|
||||||
53
testez.yml
Normal file
53
testez.yml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
globals:
|
||||||
|
FIXME:
|
||||||
|
args:
|
||||||
|
- required: false
|
||||||
|
type: string
|
||||||
|
FOCUS:
|
||||||
|
args: []
|
||||||
|
SKIP:
|
||||||
|
args: []
|
||||||
|
afterAll:
|
||||||
|
args:
|
||||||
|
- type: function
|
||||||
|
afterEach:
|
||||||
|
args:
|
||||||
|
- type: function
|
||||||
|
beforeAll:
|
||||||
|
args:
|
||||||
|
- type: function
|
||||||
|
beforeEach:
|
||||||
|
args:
|
||||||
|
- type: function
|
||||||
|
describe:
|
||||||
|
args:
|
||||||
|
- type: string
|
||||||
|
- type: function
|
||||||
|
describeFOCUS:
|
||||||
|
args:
|
||||||
|
- type: string
|
||||||
|
- type: function
|
||||||
|
describeSKIP:
|
||||||
|
args:
|
||||||
|
- type: string
|
||||||
|
- type: function
|
||||||
|
expect:
|
||||||
|
args:
|
||||||
|
- type: any
|
||||||
|
it:
|
||||||
|
args:
|
||||||
|
- type: string
|
||||||
|
- type: function
|
||||||
|
itFIXME:
|
||||||
|
args:
|
||||||
|
- type: string
|
||||||
|
- type: function
|
||||||
|
itFOCUS:
|
||||||
|
args:
|
||||||
|
- type: string
|
||||||
|
- type: function
|
||||||
|
itSKIP:
|
||||||
|
args:
|
||||||
|
- type: string
|
||||||
|
- type: function
|
||||||
@@ -141,14 +141,14 @@ impl TestServeSession {
|
|||||||
|
|
||||||
pub fn get_api_rojo(&self) -> Result<ServerInfoResponse, reqwest::Error> {
|
pub fn get_api_rojo(&self) -> Result<ServerInfoResponse, reqwest::Error> {
|
||||||
let url = format!("http://localhost:{}/api/rojo", self.port);
|
let url = format!("http://localhost:{}/api/rojo", self.port);
|
||||||
let body = reqwest::get(&url)?.text()?;
|
let body = reqwest::blocking::get(&url)?.text()?;
|
||||||
|
|
||||||
Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
|
Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_api_read(&self, id: Ref) -> Result<ReadResponse, reqwest::Error> {
|
pub fn get_api_read(&self, id: Ref) -> Result<ReadResponse, reqwest::Error> {
|
||||||
let url = format!("http://localhost:{}/api/read/{}", self.port, id);
|
let url = format!("http://localhost:{}/api/read/{}", self.port, id);
|
||||||
let body = reqwest::get(&url)?.text()?;
|
let body = reqwest::blocking::get(&url)?.text()?;
|
||||||
|
|
||||||
Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
|
Ok(serde_json::from_str(&body).expect("Server returned malformed response"))
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,7 @@ impl TestServeSession {
|
|||||||
) -> Result<SubscribeResponse<'static>, reqwest::Error> {
|
) -> Result<SubscribeResponse<'static>, reqwest::Error> {
|
||||||
let url = format!("http://localhost:{}/api/subscribe/{}", self.port, cursor);
|
let url = format!("http://localhost:{}/api/subscribe/{}", self.port, cursor);
|
||||||
|
|
||||||
reqwest::get(&url)?.json()
|
reqwest::blocking::get(&url)?.json()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ macro_rules! gen_build_tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gen_build_tests! {
|
gen_build_tests! {
|
||||||
|
attributes,
|
||||||
client_in_folder,
|
client_in_folder,
|
||||||
client_init,
|
client_init,
|
||||||
csv_bug_145,
|
csv_bug_145,
|
||||||
@@ -36,11 +37,13 @@ gen_build_tests! {
|
|||||||
init_meta_class_name,
|
init_meta_class_name,
|
||||||
init_meta_properties,
|
init_meta_properties,
|
||||||
init_with_children,
|
init_with_children,
|
||||||
|
issue_546,
|
||||||
json_as_lua,
|
json_as_lua,
|
||||||
json_model_in_folder,
|
json_model_in_folder,
|
||||||
json_model_legacy_name,
|
json_model_legacy_name,
|
||||||
module_in_folder,
|
module_in_folder,
|
||||||
module_init,
|
module_init,
|
||||||
|
optional,
|
||||||
project_composed_default,
|
project_composed_default,
|
||||||
project_composed_file,
|
project_composed_file,
|
||||||
project_root_name,
|
project_root_name,
|
||||||
@@ -53,7 +56,6 @@ gen_build_tests! {
|
|||||||
txt,
|
txt,
|
||||||
txt_in_folder,
|
txt_in_folder,
|
||||||
unresolved_values,
|
unresolved_values,
|
||||||
optional,
|
|
||||||
weldconstraint,
|
weldconstraint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user