mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-21 05:06:29 +00:00
Compare commits
65 Commits
v7.0.0-rc.
...
v7.2.1
| 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 | ||
|
|
a5a69fd9fc | ||
|
|
f1d0f1c1c9 | ||
|
|
83492d7495 | ||
|
|
10abc2254a | ||
|
|
5d5536a95e | ||
|
|
fe81e55925 | ||
|
|
654690d73e | ||
|
|
256aba4bc1 | ||
|
|
49f8845105 | ||
|
|
12370846b4 | ||
|
|
07637dfe96 | ||
|
|
f389a4a1db | ||
|
|
af077c796c | ||
|
|
1c319f2fa8 | ||
|
|
e8afa03f7b | ||
|
|
9b22545842 | ||
|
|
adc733d25c | ||
|
|
6896257647 | ||
|
|
1d9845a6cb | ||
|
|
8461339e9a | ||
|
|
9904d94e4c | ||
|
|
da25c80d0b | ||
|
|
5fa63733fd | ||
|
|
8b54bf0ba1 | ||
|
|
173dc12cb3 | ||
|
|
e136529ff0 |
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:
|
||||
build:
|
||||
|
||||
name: Build and Test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
rust_version: [stable, "1.46.0"]
|
||||
rust_version: [stable, 1.57.0]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: rustup default ${{ matrix.rust_version }}
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust_version }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- name: Build
|
||||
run: cargo build --locked --verbose
|
||||
|
||||
- name: Run tests
|
||||
- name: Test
|
||||
run: cargo test --locked --verbose
|
||||
|
||||
- name: Rustfmt and Clippy
|
||||
run: |
|
||||
cargo fmt -- --check
|
||||
cargo clippy
|
||||
if: matrix.rust_version == 'stable'
|
||||
lint:
|
||||
name: Rustfmt and Clippy
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
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:
|
||||
push:
|
||||
tags: ["*"]
|
||||
tags: ["v*"]
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
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:
|
||||
create-release:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
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
|
||||
run: cargo build --locked --verbose --release
|
||||
env:
|
||||
OPENSSL_STATIC: 1
|
||||
build-plugin:
|
||||
needs: ["create-release"]
|
||||
name: Build Roblox Studio Plugin
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: rojo-linux
|
||||
path: target/release/rojo
|
||||
- name: Setup Foreman
|
||||
uses: Roblox/setup-foreman@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- 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
|
||||
**/*.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",
|
||||
}
|
||||
69
CHANGELOG.md
69
CHANGELOG.md
@@ -2,6 +2,73 @@
|
||||
|
||||
## 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
|
||||
* 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 the new Open Cloud API when uploading. ([#504])
|
||||
* Added `sourcemap` command for generating sourcemaps to feed into other tools. ([#530])
|
||||
* Added PluginActions for connecting/disconnecting a session ([#537])
|
||||
* Added changing toolbar icon to indicate state ([#538])
|
||||
|
||||
[#472]: https://github.com/rojo-rbx/rojo/pull/472
|
||||
[#504]: https://github.com/rojo-rbx/rojo/pull/504
|
||||
[#507]: https://github.com/rojo-rbx/rojo/pull/507
|
||||
[#530]: https://github.com/rojo-rbx/rojo/pull/530
|
||||
[#537]: https://github.com/rojo-rbx/rojo/pull/537
|
||||
[#538]: https://github.com/rojo-rbx/rojo/pull/538
|
||||
[7.1.0]: https://github.com/rojo-rbx/rojo/releases/tag/v7.1.0
|
||||
|
||||
## [7.0.0] - December 10, 2021
|
||||
* Fixed Rojo's interactions with properties enabled by FFlags that are not yet enabled. ([#493])
|
||||
* Improved output in Roblox Studio plugin when bad property data is encountered.
|
||||
* Reintroduced support for CFrame shorthand syntax in Rojo project and `.meta.json` files, matching Rojo 6. ([#430])
|
||||
* Connection settings are now remembered when reconnecting in Roblox Studio. ([#500])
|
||||
* Updated reflection database to Roblox v503.
|
||||
|
||||
[#430]: https://github.com/rojo-rbx/rojo/issues/430
|
||||
[#493]: https://github.com/rojo-rbx/rojo/pull/493
|
||||
[#500]: https://github.com/rojo-rbx/rojo/pull/500
|
||||
[7.0.0]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0
|
||||
|
||||
## [7.0.0-rc.3] - October 19, 2021
|
||||
This is the last release candidate for Rojo 7. In an effort to get Rojo 7 out the door, we'll be freezing features from here on out, something we should've done a couple months ago.
|
||||
|
||||
@@ -75,7 +142,7 @@ The shorthand property format that most users use is not impacted. For reference
|
||||
## [7.0.0-alpha.4][7.0.0-alpha.4] (May 5, 2021)
|
||||
* Added the `gameId` and `placeId` optional properties to project files.
|
||||
* When connecting from the Rojo Roblox Studio plugin, Rojo will set the game and place ID of the current place to these values, if set.
|
||||
* This is equivalent to running `game:SetUniverseId(...)` and `game:SetPlaceId(...)` from the command bar in Studio.
|
||||
* This is equivalent to running `game:SetUniverseId(...)` and `game:SetPlaceId(...)` from the command bar in Studio.
|
||||
* Added "EXPERIMENTAL!" label to two-way sync toggle in Rojo's Roblox Studio plugin.
|
||||
* Fixed `Name` and `Parent` properties being allowed in Rojo projects. ([#413][pr-413])
|
||||
* Fixed "Open Scripts Externally" feature crashing Studio. ([#369][issue-369])
|
||||
|
||||
@@ -29,6 +29,11 @@ Sometimes there's something that Rojo doesn't do that it probably should.
|
||||
|
||||
Please file issues and we'll try to help figure out what the best way forward is.
|
||||
|
||||
## Local Development Gotchas
|
||||
|
||||
If your build fails with "Error: failed to open file `D:\code\rojo\plugin\modules\roact\src`" you need to update your Git submodules.
|
||||
Run the command and try building again: `git submodule update --init --recursive`.
|
||||
|
||||
## Pushing a Rojo Release
|
||||
The Rojo release process is pretty manual right now. If you need to do it, here's how:
|
||||
|
||||
@@ -44,11 +49,9 @@ The Rojo release process is pretty manual right now. If you need to do it, here'
|
||||
* `cargo publish`
|
||||
8. Publish the Plugin
|
||||
* `cargo run -- upload plugin --asset_id 6415005344`
|
||||
* `cargo run -- build plugin --output Rojo.rbxm`
|
||||
9. Push commits and tags
|
||||
* `git push && git push --tags`
|
||||
10. Copy GitHub release content from previous release
|
||||
* Update the leading text with a summary about the release
|
||||
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md)
|
||||
* Write a small summary of each major feature
|
||||
* Attach release artifacts from GitHub Actions for each platform
|
||||
* Write a small summary of each major feature
|
||||
1960
Cargo.lock
generated
1960
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
38
Cargo.toml
38
Cargo.toml
@@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "rojo"
|
||||
version = "7.0.0-rc.3"
|
||||
version = "7.2.1"
|
||||
rust-version = "1.57.0"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
description = "Enables professional-grade development tools for Roblox developers"
|
||||
license = "MPL-2.0"
|
||||
@@ -8,7 +9,7 @@ homepage = "https://rojo.space"
|
||||
documentation = "https://rojo.space/docs"
|
||||
repository = "https://github.com/rojo-rbx/rojo"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
exclude = [
|
||||
@@ -27,11 +28,10 @@ default = []
|
||||
# Enable this feature to live-reload assets from the web UI.
|
||||
dev_live_assets = []
|
||||
|
||||
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client"]
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"rojo-insta-ext",
|
||||
"memofs",
|
||||
]
|
||||
members = ["crates/*"]
|
||||
|
||||
[lib]
|
||||
name = "librojo"
|
||||
@@ -42,7 +42,7 @@ name = "build"
|
||||
harness = false
|
||||
|
||||
[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
|
||||
# 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_xml = { path = "../rbx-dom/rbx_xml" }
|
||||
|
||||
rbx_binary = "0.6.4"
|
||||
rbx_dom_weak = "2.3.0"
|
||||
rbx_binary = "0.6.5"
|
||||
rbx_dom_weak = "2.4.0"
|
||||
rbx_reflection = "4.2.0"
|
||||
rbx_reflection_database = "0.2.2"
|
||||
rbx_xml = "0.12.3"
|
||||
@@ -69,29 +69,28 @@ globset = "0.4.8"
|
||||
humantime = "2.1.0"
|
||||
hyper = { version = "0.14.13", features = ["server", "tcp", "http1"] }
|
||||
jod-thread = "0.1.2"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.14"
|
||||
maplit = "1.0.2"
|
||||
notify = "4.0.17"
|
||||
opener = "0.5.0"
|
||||
regex = "1.5.4"
|
||||
reqwest = "0.9.24"
|
||||
reqwest = { version = "0.11.10", features = ["blocking", "json"] }
|
||||
ritz = "0.1.0"
|
||||
rlua = "0.17.1"
|
||||
roblox_install = "1.0.0"
|
||||
serde = { version = "1.0.130", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.68"
|
||||
structopt = "0.3.23"
|
||||
termcolor = "1.1.2"
|
||||
thiserror = "1.0.30"
|
||||
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]
|
||||
winreg = "0.9.0"
|
||||
winreg = "0.10.1"
|
||||
|
||||
[build-dependencies]
|
||||
memofs = { version = "0.2.0", path = "memofs" }
|
||||
memofs = { version = "0.2.0", path = "crates/memofs" }
|
||||
|
||||
embed-resource = "1.6.4"
|
||||
anyhow = "1.0.44"
|
||||
@@ -100,13 +99,12 @@ fs-err = "2.6.0"
|
||||
maplit = "1.0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
rojo-insta-ext = { path = "rojo-insta-ext" }
|
||||
rojo-insta-ext = { path = "crates/rojo-insta-ext" }
|
||||
|
||||
criterion = "0.3.5"
|
||||
insta = { version = "1.8.0", features = ["redactions"] }
|
||||
lazy_static = "1.4.0"
|
||||
paste = "1.0.5"
|
||||
pretty_assertions = "0.7.2"
|
||||
pretty_assertions = "1.2.1"
|
||||
serde_yaml = "0.8.21"
|
||||
tempfile = "3.2.0"
|
||||
walkdir = "2.3.2"
|
||||
|
||||
@@ -40,7 +40,7 @@ Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions fo
|
||||
|
||||
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
|
||||
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.
BIN
assets/icon-link-32.png
Normal file
BIN
assets/icon-link-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/icon-warn-32.png
Normal file
BIN
assets/icon-warn-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -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
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
[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" }
|
||||
selene = { source = "Kampfkarren/selene", version = "0.18.2" }
|
||||
|
||||
@@ -23,8 +23,45 @@ end
|
||||
local ALL_AXES = {"X", "Y", "Z"}
|
||||
local ALL_FACES = {"Right", "Top", "Back", "Left", "Bottom", "Front"}
|
||||
|
||||
local EncodedValue = {}
|
||||
|
||||
local 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 = {
|
||||
fromPod = function(pod)
|
||||
local axes = {}
|
||||
@@ -433,8 +470,6 @@ types = {
|
||||
},
|
||||
}
|
||||
|
||||
local EncodedValue = {}
|
||||
|
||||
function EncodedValue.decode(encodedValue)
|
||||
local ty, value = next(encodedValue)
|
||||
|
||||
@@ -459,4 +494,19 @@ function EncodedValue.encode(rbxValue, propertyType)
|
||||
}
|
||||
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
|
||||
|
||||
@@ -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": {
|
||||
"value": {
|
||||
"Axes": [
|
||||
|
||||
@@ -5,6 +5,26 @@ local CollectionService = game:GetService("CollectionService")
|
||||
-- The reflection database refers to these as having scriptability = "Custom"
|
||||
return {
|
||||
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 = {
|
||||
read = function(instance)
|
||||
return true, CollectionService:GetTags(instance)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
40
plugin/src/App/Components/Studio/StudioPluginAction.lua
Normal file
40
plugin/src/App/Components/Studio/StudioPluginAction.lua
Normal file
@@ -0,0 +1,40 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
local StudioPluginContext = require(script.Parent.StudioPluginContext)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local StudioPluginAction = Roact.Component:extend("StudioPluginAction")
|
||||
|
||||
function StudioPluginAction:init()
|
||||
self.pluginAction = self.props.plugin:CreatePluginAction(
|
||||
self.props.name, self.props.title, self.props.description, self.props.icon, self.props.bindable
|
||||
)
|
||||
|
||||
self.pluginAction.Triggered:Connect(self.props.onTriggered)
|
||||
end
|
||||
|
||||
function StudioPluginAction:render()
|
||||
return nil
|
||||
end
|
||||
|
||||
function StudioPluginAction:willUnmount()
|
||||
self.pluginAction:Destroy()
|
||||
end
|
||||
|
||||
local function StudioPluginActionWrapper(props)
|
||||
return e(StudioPluginContext.Consumer, {
|
||||
render = function(plugin)
|
||||
return e(StudioPluginAction, Dictionary.merge(props, {
|
||||
plugin = plugin,
|
||||
}))
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return StudioPluginActionWrapper
|
||||
@@ -44,6 +44,10 @@ function StudioToggleButton:didUpdate(lastProps)
|
||||
self.button.Enabled = self.props.enabled
|
||||
end
|
||||
|
||||
if self.props.icon ~= lastProps.icon then
|
||||
self.button.Icon = self.props.icon
|
||||
end
|
||||
|
||||
if self.props.active ~= lastProps.active then
|
||||
self.button:SetActive(self.props.active)
|
||||
end
|
||||
@@ -63,4 +67,4 @@ local function StudioToggleButtonWrapper(props)
|
||||
})
|
||||
end
|
||||
|
||||
return StudioToggleButtonWrapper
|
||||
return StudioToggleButtonWrapper
|
||||
|
||||
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 = {
|
||||
openScriptsExternally = false,
|
||||
twoWaySync = false,
|
||||
showNotifications = true,
|
||||
playSounds = true,
|
||||
}
|
||||
|
||||
local Settings = {}
|
||||
@@ -118,4 +120,4 @@ end
|
||||
return {
|
||||
StudioProvider = StudioProvider,
|
||||
with = with,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ local function AddressEntry(props)
|
||||
layoutOrder = props.layoutOrder,
|
||||
}, {
|
||||
Host = e("TextBox", {
|
||||
Text = "",
|
||||
Text = props.host or "",
|
||||
Font = Enum.Font.Code,
|
||||
TextSize = 18,
|
||||
TextColor3 = theme.AddressEntry.TextColor,
|
||||
@@ -32,6 +32,7 @@ local function AddressEntry(props)
|
||||
TextTransparency = props.transparency,
|
||||
PlaceholderText = Config.defaultHost,
|
||||
PlaceholderColor3 = theme.AddressEntry.PlaceholderColor,
|
||||
ClearTextOnFocus = false,
|
||||
|
||||
Size = UDim2.new(1, -(HOST_OFFSET + DIVIDER_WIDTH + PORT_WIDTH), 1, 0),
|
||||
Position = UDim2.new(0, HOST_OFFSET, 0, 0),
|
||||
@@ -39,17 +40,22 @@ local function AddressEntry(props)
|
||||
ClipsDescendants = true,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Ref] = props.hostRef,
|
||||
[Roact.Change.Text] = function(object)
|
||||
if props.onHostChange ~= nil then
|
||||
props.onHostChange(object.Text)
|
||||
end
|
||||
end
|
||||
}),
|
||||
|
||||
Port = e("TextBox", {
|
||||
Text = "",
|
||||
Text = props.port or "",
|
||||
Font = Enum.Font.Code,
|
||||
TextSize = 18,
|
||||
TextColor3 = theme.AddressEntry.TextColor,
|
||||
TextTransparency = props.transparency,
|
||||
PlaceholderText = Config.defaultPort,
|
||||
PlaceholderColor3 = theme.AddressEntry.PlaceholderColor,
|
||||
ClearTextOnFocus = false,
|
||||
|
||||
Size = UDim2.new(0, PORT_WIDTH, 1, 0),
|
||||
Position = UDim2.new(1, 0, 0, 0),
|
||||
@@ -58,12 +64,14 @@ local function AddressEntry(props)
|
||||
ClipsDescendants = true,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Ref] = props.portRef,
|
||||
|
||||
[Roact.Change.Text] = function(object)
|
||||
local text = object.Text
|
||||
text = text:gsub("%D", "")
|
||||
object.Text = text
|
||||
|
||||
if props.onPortChange ~= nil then
|
||||
props.onPortChange(text)
|
||||
end
|
||||
end,
|
||||
}, {
|
||||
Divider = e("Frame", {
|
||||
@@ -80,11 +88,6 @@ end
|
||||
|
||||
local NotConnectedPage = Roact.Component:extend("NotConnectedPage")
|
||||
|
||||
function NotConnectedPage:init()
|
||||
self.hostRef = Roact.createRef()
|
||||
self.portRef = Roact.createRef()
|
||||
end
|
||||
|
||||
function NotConnectedPage:render()
|
||||
return Roact.createFragment({
|
||||
Header = e(Header, {
|
||||
@@ -93,8 +96,10 @@ function NotConnectedPage:render()
|
||||
}),
|
||||
|
||||
AddressEntry = e(AddressEntry, {
|
||||
hostRef = self.hostRef,
|
||||
portRef = self.portRef,
|
||||
host = self.props.host,
|
||||
port = self.props.port,
|
||||
onHostChange = self.props.onHostChange,
|
||||
onPortChange = self.props.onPortChange,
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
}),
|
||||
@@ -117,15 +122,7 @@ function NotConnectedPage:render()
|
||||
style = "Solid",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
onClick = function()
|
||||
local hostText = self.hostRef.current.Text
|
||||
local portText = self.portRef.current.Text
|
||||
|
||||
self.props.onConnect(
|
||||
#hostText > 0 and hostText or Config.defaultHost,
|
||||
#portText > 0 and portText or Config.defaultPort
|
||||
)
|
||||
end,
|
||||
onClick = self.props.onConnect,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
|
||||
@@ -202,12 +202,28 @@ function SettingsPage:render()
|
||||
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, {
|
||||
id = "twoWaySync",
|
||||
name = "Two-Way Sync",
|
||||
description = "EXPERIMENTAL! Editing files in Studio will sync them into the filesystem",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
layoutOrder = 4,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
@@ -227,4 +243,4 @@ function SettingsPage:render()
|
||||
end)
|
||||
end
|
||||
|
||||
return SettingsPage
|
||||
return SettingsPage
|
||||
|
||||
@@ -103,6 +103,10 @@ local lightTheme = strict("LightTheme", {
|
||||
LogoColor = BRAND_COLOR,
|
||||
VersionColor = hexColor(0x727272),
|
||||
},
|
||||
Notification = {
|
||||
InfoColor = hexColor(0x00000),
|
||||
CloseColor = BRAND_COLOR,
|
||||
},
|
||||
ErrorColor = hexColor(0x000000),
|
||||
ScrollBarColor = hexColor(0x000000),
|
||||
})
|
||||
@@ -177,6 +181,10 @@ local darkTheme = strict("DarkTheme", {
|
||||
LogoColor = BRAND_COLOR,
|
||||
VersionColor = hexColor(0xD3D3D3)
|
||||
},
|
||||
Notification = {
|
||||
InfoColor = hexColor(0xFFFFFF),
|
||||
CloseColor = hexColor(0xFFFFFF),
|
||||
},
|
||||
ErrorColor = hexColor(0xFFFFFF),
|
||||
ScrollBarColor = hexColor(0xFFFFFF),
|
||||
})
|
||||
|
||||
@@ -12,10 +12,13 @@ local Dictionary = require(Plugin.Dictionary)
|
||||
local ServeSession = require(Plugin.ServeSession)
|
||||
local ApiContext = require(Plugin.ApiContext)
|
||||
local preloadAssets = require(Plugin.preloadAssets)
|
||||
local soundPlayer = require(Plugin.soundPlayer)
|
||||
local Theme = require(script.Theme)
|
||||
local PluginSettings = require(script.PluginSettings)
|
||||
|
||||
local Page = require(script.Page)
|
||||
local Notifications = require(script.Notifications)
|
||||
local StudioPluginAction = require(script.Components.Studio.StudioPluginAction)
|
||||
local StudioToolbar = require(script.Components.Studio.StudioToolbar)
|
||||
local StudioToggleButton = require(script.Components.Studio.StudioToggleButton)
|
||||
local StudioPluginGui = require(script.Components.Studio.StudioPluginGui)
|
||||
@@ -37,13 +40,61 @@ local App = Roact.Component:extend("App")
|
||||
function App:init()
|
||||
preloadAssets()
|
||||
|
||||
self.host, self.setHost = Roact.createBinding("")
|
||||
self.port, self.setPort = Roact.createBinding("")
|
||||
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
guiEnabled = false,
|
||||
notifications = {},
|
||||
toolbarIcon = Assets.Images.PluginButton,
|
||||
})
|
||||
end
|
||||
|
||||
function App:startSession(host, port, sessionOptions)
|
||||
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()
|
||||
local host = self.host:getValue()
|
||||
local port = self.port:getValue()
|
||||
|
||||
local host = if #host > 0 then host else Config.defaultHost
|
||||
local port = if #port > 0 then port else Config.defaultPort
|
||||
|
||||
return host, port
|
||||
end
|
||||
|
||||
function App:startSession()
|
||||
local host, port = self:getHostAndPort()
|
||||
|
||||
local sessionOptions = {
|
||||
openScriptsExternally = self.props.settings:get("openScriptsExternally"),
|
||||
twoWaySync = self.props.settings:get("twoWaySync"),
|
||||
}
|
||||
|
||||
local baseUrl = ("http://%s:%s"):format(host, port)
|
||||
local apiContext = ApiContext.new(baseUrl)
|
||||
|
||||
@@ -57,14 +108,18 @@ function App:startSession(host, port, sessionOptions)
|
||||
if status == ServeSession.Status.Connecting then
|
||||
self:setState({
|
||||
appStatus = AppStatus.Connecting,
|
||||
toolbarIcon = Assets.Images.PluginButton,
|
||||
})
|
||||
self:addNotification("Connecting to session...")
|
||||
elseif status == ServeSession.Status.Connected then
|
||||
local address = ("%s:%s"):format(host, port)
|
||||
self:setState({
|
||||
appStatus = AppStatus.Connected,
|
||||
projectName = details,
|
||||
address = address,
|
||||
toolbarIcon = Assets.Images.PluginButtonConnected,
|
||||
})
|
||||
self:addNotification(string.format("Connected to session '%s' at %s.", details, address), 5)
|
||||
elseif status == ServeSession.Status.Disconnected then
|
||||
self.serveSession = nil
|
||||
|
||||
@@ -76,11 +131,15 @@ function App:startSession(host, port, sessionOptions)
|
||||
self:setState({
|
||||
appStatus = AppStatus.Error,
|
||||
errorMessage = tostring(details),
|
||||
toolbarIcon = Assets.Images.PluginButtonWarning,
|
||||
})
|
||||
self:addNotification(tostring(details), 10)
|
||||
else
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
toolbarIcon = Assets.Images.PluginButton,
|
||||
})
|
||||
self:addNotification("Disconnected from session.")
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -90,6 +149,22 @@ function App:startSession(host, port, sessionOptions)
|
||||
self.serveSession = serveSession
|
||||
end
|
||||
|
||||
function App:endSession()
|
||||
if self.serveSession == nil then
|
||||
return
|
||||
end
|
||||
|
||||
Log.trace("Disconnecting session")
|
||||
|
||||
self.serveSession:stop()
|
||||
self.serveSession = nil
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
})
|
||||
|
||||
Log.trace("Session terminated by user")
|
||||
end
|
||||
|
||||
function App:render()
|
||||
local pluginName = "Rojo " .. Version.display(Config.version)
|
||||
|
||||
@@ -108,119 +183,177 @@ function App:render()
|
||||
value = self.props.plugin,
|
||||
}, {
|
||||
e(Theme.StudioProvider, nil, {
|
||||
e(PluginSettings.StudioProvider, {
|
||||
plugin = self.props.plugin,
|
||||
gui = e(StudioPluginGui, {
|
||||
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, {
|
||||
id = pluginName,
|
||||
title = pluginName,
|
||||
active = self.state.guiEnabled,
|
||||
NotConnectedPage = createPageElement(AppStatus.NotConnected, {
|
||||
host = self.host,
|
||||
onHostChange = self.setHost,
|
||||
port = self.port,
|
||||
onPortChange = self.setPort,
|
||||
|
||||
initDockState = Enum.InitialDockState.Right,
|
||||
initEnabled = false,
|
||||
overridePreviousState = false,
|
||||
floatingSize = Vector2.new(300, 200),
|
||||
minimumSize = Vector2.new(300, 200),
|
||||
onConnect = function()
|
||||
self:startSession()
|
||||
end,
|
||||
|
||||
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
|
||||
onInitialState = function(initialState)
|
||||
onNavigateSettings = function()
|
||||
self:setState({
|
||||
guiEnabled = initialState,
|
||||
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({
|
||||
guiEnabled = false,
|
||||
appStatus = AppStatus.NotConnected,
|
||||
toolbarIcon = Assets.Images.PluginButton,
|
||||
})
|
||||
end,
|
||||
}, {
|
||||
NotConnectedPage = PluginSettings.with(function(settings)
|
||||
return createPageElement(AppStatus.NotConnected, {
|
||||
onConnect = function(host, port)
|
||||
self:startSession(host, port, {
|
||||
openScriptsExternally = settings:get("openScriptsExternally"),
|
||||
twoWaySync = settings:get("twoWaySync"),
|
||||
})
|
||||
end,
|
||||
|
||||
onNavigateSettings = function()
|
||||
self:setState({
|
||||
appStatus = AppStatus.Settings,
|
||||
})
|
||||
end,
|
||||
})
|
||||
end),
|
||||
|
||||
Connecting = createPageElement(AppStatus.Connecting),
|
||||
|
||||
Connected = createPageElement(AppStatus.Connected, {
|
||||
projectName = self.state.projectName,
|
||||
address = self.state.address,
|
||||
|
||||
onDisconnect = function()
|
||||
Log.trace("Disconnecting session")
|
||||
|
||||
self.serveSession:stop()
|
||||
self.serveSession = nil
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
})
|
||||
|
||||
Log.trace("Session terminated by user")
|
||||
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,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
|
||||
Background = Theme.with(function(theme)
|
||||
return e("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundColor3 = theme.BackgroundColor,
|
||||
ZIndex = 0,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
end),
|
||||
}),
|
||||
|
||||
toolbar = e(StudioToolbar, {
|
||||
name = pluginName,
|
||||
}, {
|
||||
button = e(StudioToggleButton, {
|
||||
name = "Rojo",
|
||||
tooltip = "Show or hide the Rojo panel",
|
||||
icon = Assets.Images.PluginButton,
|
||||
active = self.state.guiEnabled,
|
||||
enabled = true,
|
||||
onClick = function()
|
||||
self:setState(function(state)
|
||||
return {
|
||||
guiEnabled = not state.guiEnabled,
|
||||
}
|
||||
end)
|
||||
end,
|
||||
Background = Theme.with(function(theme)
|
||||
return e("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundColor3 = theme.BackgroundColor,
|
||||
ZIndex = 0,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
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,
|
||||
})
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return App
|
||||
return function(props)
|
||||
return e(PluginSettings.StudioProvider, {
|
||||
plugin = props.plugin,
|
||||
}, {
|
||||
App = PluginSettings.with(function(settings)
|
||||
local mergedProps = Dictionary.merge(props, {
|
||||
settings = settings,
|
||||
soundPlayer = soundPlayer.new(settings),
|
||||
})
|
||||
return e(App, mergedProps)
|
||||
end),
|
||||
})
|
||||
end
|
||||
|
||||
@@ -18,6 +18,8 @@ local Assets = {
|
||||
Images = {
|
||||
Logo = "rbxassetid://5990772764",
|
||||
PluginButton = "rbxassetid://3405341609",
|
||||
PluginButtonConnected = "rbxassetid://9529783993",
|
||||
PluginButtonWarning = "rbxassetid://9529784530",
|
||||
Icons = {
|
||||
Close = "rbxassetid://6012985953",
|
||||
Back = "rbxassetid://6017213752",
|
||||
@@ -43,6 +45,9 @@ local Assets = {
|
||||
[500] = "rbxassetid://2609138523"
|
||||
},
|
||||
},
|
||||
Sounds = {
|
||||
Notification = "rbxassetid://203785492",
|
||||
},
|
||||
StartSession = "",
|
||||
SessionActive = "",
|
||||
Configure = "",
|
||||
@@ -60,4 +65,4 @@ end
|
||||
|
||||
guardForTypos("Assets", Assets)
|
||||
|
||||
return Assets
|
||||
return Assets
|
||||
|
||||
@@ -5,8 +5,8 @@ local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
|
||||
return strict("Config", {
|
||||
isDevBuild = isDevBuild,
|
||||
codename = "Epiphany",
|
||||
version = {7, 0, 0, "-rc.3"},
|
||||
expectedServerVersionString = "7.0 or newer",
|
||||
version = {7, 2, 1},
|
||||
expectedServerVersionString = "7.2 or newer",
|
||||
protocolVersion = 4,
|
||||
defaultHost = "localhost",
|
||||
defaultPort = 34872,
|
||||
|
||||
@@ -63,7 +63,7 @@ local function applyPatch(instanceMap, patch)
|
||||
local failedToReify = reify(instanceMap, patch.added, id, parentInstance)
|
||||
|
||||
if not PatchSet.isEmpty(failedToReify) then
|
||||
Log.debug("Failed to reify as part of applying a patch: {}", failedToReify)
|
||||
Log.debug("Failed to reify as part of applying a patch: {:#?}", failedToReify)
|
||||
PatchSet.assign(unappliedPatch, failedToReify)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -75,9 +75,13 @@ local function diff(instanceMap, virtualInstances, rootId)
|
||||
changedProperties[propertyName] = virtualValue
|
||||
end
|
||||
else
|
||||
-- virtualValue can be empty in certain cases, and this may print out nil to the user.
|
||||
local propertyType = next(virtualValue)
|
||||
Log.warn("Failed to decode property of type {}", propertyType)
|
||||
Log.warn(
|
||||
"Failed to decode property {}.{}. Encoded property was: {:#?}",
|
||||
virtualInstance.ClassName,
|
||||
propertyName,
|
||||
virtualValue
|
||||
)
|
||||
end
|
||||
else
|
||||
local err = existingValueOrErr
|
||||
|
||||
@@ -40,6 +40,13 @@ local function getProperty(instance, propertyName)
|
||||
})
|
||||
end
|
||||
|
||||
if err.kind == RbxDom.Error.Kind.Roblox and err.extra:find("is not a valid member of") then
|
||||
return false, Error.new(Error.UnknownProperty, {
|
||||
className = instance.ClassName,
|
||||
propertyName = propertyName,
|
||||
})
|
||||
end
|
||||
|
||||
return false, Error.new(Error.OtherPropertyError, {
|
||||
className = instance.ClassName,
|
||||
propertyName = propertyName,
|
||||
|
||||
@@ -113,6 +113,10 @@ function ServeSession:__fmtDebug(output)
|
||||
output:write("}")
|
||||
end
|
||||
|
||||
function ServeSession:getStatus()
|
||||
return self.__status
|
||||
end
|
||||
|
||||
function ServeSession:onStatusChanged(callback)
|
||||
self.__statusChangedCallback = callback
|
||||
end
|
||||
|
||||
@@ -18,7 +18,7 @@ local App = require(script.App)
|
||||
local app = Roact.createElement(App, {
|
||||
plugin = plugin
|
||||
})
|
||||
local tree = Roact.mount(app, nil, "Rojo UI")
|
||||
local tree = Roact.mount(app, game:GetService("CoreGui"), "Rojo UI")
|
||||
|
||||
plugin.Unloading:Connect(function()
|
||||
Roact.unmount(tree)
|
||||
@@ -28,4 +28,4 @@ if Config.isDevBuild then
|
||||
local TestEZ = require(script.Parent.TestEZ)
|
||||
|
||||
require(script.runTests)(TestEZ)
|
||||
end
|
||||
end
|
||||
|
||||
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>
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
source: tests/tests/build.rs
|
||||
expression: contents
|
||||
|
||||
---
|
||||
<roblox version="4">
|
||||
<Item class="Folder" referent="0">
|
||||
<Properties>
|
||||
<string name="Name">optional</string>
|
||||
</Properties>
|
||||
<Item class="StringValue" referent="1">
|
||||
<Properties>
|
||||
<string name="Name">foo-optional</string>
|
||||
<string name="Value">Hello, from foo.txt!</string>
|
||||
</Properties>
|
||||
</Item>
|
||||
<Item class="StringValue" referent="2">
|
||||
<Properties>
|
||||
<string name="Name">foo-required</string>
|
||||
<string name="Value">Hello, from foo.txt!</string>
|
||||
</Properties>
|
||||
</Item>
|
||||
</Item>
|
||||
</roblox>
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
source: tests/tests/build.rs
|
||||
expression: contents
|
||||
|
||||
---
|
||||
<roblox version="4">
|
||||
<Item class="Folder" referent="0">
|
||||
<Properties>
|
||||
<string name="Name">root</string>
|
||||
</Properties>
|
||||
<Item class="Folder" referent="1">
|
||||
<Properties>
|
||||
<string name="Name">folder</string>
|
||||
</Properties>
|
||||
</Item>
|
||||
</Item>
|
||||
</roblox>
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: tests/tests/build.rs
|
||||
expression: contents
|
||||
|
||||
---
|
||||
<roblox version="4">
|
||||
<Item class="Folder" referent="0">
|
||||
<Properties>
|
||||
<string name="Name">root</string>
|
||||
</Properties>
|
||||
<Item class="Folder" referent="1">
|
||||
<Properties>
|
||||
<string name="Name">folder</string>
|
||||
</Properties>
|
||||
<Item class="Folder" referent="2">
|
||||
<Properties>
|
||||
<string name="Name">child-projectname</string>
|
||||
</Properties>
|
||||
</Item>
|
||||
</Item>
|
||||
</Item>
|
||||
</roblox>
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
source: tests/tests/build.rs
|
||||
expression: contents
|
||||
|
||||
---
|
||||
<roblox version="4">
|
||||
<Item class="Folder" referent="0">
|
||||
<Properties>
|
||||
<string name="Name">root</string>
|
||||
</Properties>
|
||||
</Item>
|
||||
</roblox>
|
||||
@@ -32,6 +32,20 @@ expression: contents
|
||||
<Item class="Part" referent="4">
|
||||
<Properties>
|
||||
<string name="Name">Color</string>
|
||||
<CoordinateFrame name="CFrame">
|
||||
<X>1</X>
|
||||
<Y>2</Y>
|
||||
<Z>3</Z>
|
||||
<R00>0</R00>
|
||||
<R01>1</R01>
|
||||
<R02>0</R02>
|
||||
<R10>0</R10>
|
||||
<R11>0</R11>
|
||||
<R12>1</R12>
|
||||
<R20>1</R20>
|
||||
<R21>0</R21>
|
||||
<R22>0</R22>
|
||||
</CoordinateFrame>
|
||||
<Color3uint8 name="Color3uint8">8404992</Color3uint8>
|
||||
</Properties>
|
||||
</Item>
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
---
|
||||
source: tests/tests/build.rs
|
||||
assertion_line: 99
|
||||
expression: contents
|
||||
---
|
||||
<roblox version="4">
|
||||
<Item class="Folder" referent="0">
|
||||
<Properties>
|
||||
<string name="Name">weldconstraint</string>
|
||||
<BinaryString name="AttributesSerialize"></BinaryString>
|
||||
<int64 name="SourceAssetId">-1</int64>
|
||||
<BinaryString name="Tags"></BinaryString>
|
||||
</Properties>
|
||||
<Item class="Part" referent="1">
|
||||
<Properties>
|
||||
<string name="Name">A</string>
|
||||
<bool name="Anchored">false</bool>
|
||||
<BinaryString name="AttributesSerialize"></BinaryString>
|
||||
<float name="BackParamA">-0.5</float>
|
||||
<float name="BackParamB">0.5</float>
|
||||
<token name="BackSurface">0</token>
|
||||
<token name="BackSurfaceInput">0</token>
|
||||
<float name="BottomParamA">-0.5</float>
|
||||
<float name="BottomParamB">0.5</float>
|
||||
<token name="BottomSurface">0</token>
|
||||
<token name="BottomSurfaceInput">0</token>
|
||||
<CoordinateFrame name="CFrame">
|
||||
<X>-14</X>
|
||||
<Y>0.5</Y>
|
||||
<Z>-5</Z>
|
||||
<R00>1</R00>
|
||||
<R01>0</R01>
|
||||
<R02>0</R02>
|
||||
<R10>0</R10>
|
||||
<R11>1</R11>
|
||||
<R12>0</R12>
|
||||
<R20>0</R20>
|
||||
<R21>0</R21>
|
||||
<R22>1</R22>
|
||||
</CoordinateFrame>
|
||||
<bool name="CanCollide">true</bool>
|
||||
<bool name="CanQuery">true</bool>
|
||||
<bool name="CanTouch">true</bool>
|
||||
<bool name="CastShadow">true</bool>
|
||||
<int name="CollisionGroupId">0</int>
|
||||
<Color3uint8 name="Color3uint8">10724005</Color3uint8>
|
||||
<PhysicalProperties name="CustomPhysicalProperties">
|
||||
<CustomPhysics>false</CustomPhysics>
|
||||
</PhysicalProperties>
|
||||
<token name="formFactorRaw">1</token>
|
||||
<float name="FrontParamA">-0.5</float>
|
||||
<float name="FrontParamB">0.5</float>
|
||||
<token name="FrontSurface">0</token>
|
||||
<token name="FrontSurfaceInput">0</token>
|
||||
<float name="LeftParamA">-0.5</float>
|
||||
<float name="LeftParamB">0.5</float>
|
||||
<token name="LeftSurface">0</token>
|
||||
<token name="LeftSurfaceInput">0</token>
|
||||
<bool name="Locked">false</bool>
|
||||
<bool name="Massless">false</bool>
|
||||
<token name="Material">256</token>
|
||||
<CoordinateFrame name="PivotOffset">
|
||||
<X>0</X>
|
||||
<Y>0</Y>
|
||||
<Z>0</Z>
|
||||
<R00>1</R00>
|
||||
<R01>0</R01>
|
||||
<R02>0</R02>
|
||||
<R10>0</R10>
|
||||
<R11>1</R11>
|
||||
<R12>0</R12>
|
||||
<R20>0</R20>
|
||||
<R21>0</R21>
|
||||
<R22>1</R22>
|
||||
</CoordinateFrame>
|
||||
<float name="Reflectance">0</float>
|
||||
<float name="RightParamA">-0.5</float>
|
||||
<float name="RightParamB">0.5</float>
|
||||
<token name="RightSurface">0</token>
|
||||
<token name="RightSurfaceInput">0</token>
|
||||
<int name="RootPriority">0</int>
|
||||
<Vector3 name="RotVelocity">
|
||||
<X>0</X>
|
||||
<Y>0</Y>
|
||||
<Z>0</Z>
|
||||
</Vector3>
|
||||
<token name="shape">1</token>
|
||||
<Vector3 name="size">
|
||||
<X>4</X>
|
||||
<Y>1</Y>
|
||||
<Z>2</Z>
|
||||
</Vector3>
|
||||
<int64 name="SourceAssetId">-1</int64>
|
||||
<BinaryString name="Tags"></BinaryString>
|
||||
<float name="TopParamA">-0.5</float>
|
||||
<float name="TopParamB">0.5</float>
|
||||
<token name="TopSurface">0</token>
|
||||
<token name="TopSurfaceInput">0</token>
|
||||
<float name="Transparency">0</float>
|
||||
<Vector3 name="Velocity">
|
||||
<X>0</X>
|
||||
<Y>0</Y>
|
||||
<Z>0</Z>
|
||||
</Vector3>
|
||||
</Properties>
|
||||
<Item class="WeldConstraint" referent="2">
|
||||
<Properties>
|
||||
<string name="Name">WeldConstraint</string>
|
||||
<BinaryString name="AttributesSerialize"></BinaryString>
|
||||
<CoordinateFrame name="CFrame0">
|
||||
<X>7</X>
|
||||
<Y>0.000001013279</Y>
|
||||
<Z>-3</Z>
|
||||
<R00>1</R00>
|
||||
<R01>0</R01>
|
||||
<R02>0</R02>
|
||||
<R10>0</R10>
|
||||
<R11>1</R11>
|
||||
<R12>0</R12>
|
||||
<R20>0</R20>
|
||||
<R21>0</R21>
|
||||
<R22>1</R22>
|
||||
</CoordinateFrame>
|
||||
<Ref name="Part0Internal">1</Ref>
|
||||
<Ref name="Part1Internal">3</Ref>
|
||||
<int64 name="SourceAssetId">-1</int64>
|
||||
<int name="State">3</int>
|
||||
<BinaryString name="Tags"></BinaryString>
|
||||
</Properties>
|
||||
</Item>
|
||||
</Item>
|
||||
<Item class="Part" referent="3">
|
||||
<Properties>
|
||||
<string name="Name">B</string>
|
||||
<bool name="Anchored">false</bool>
|
||||
<BinaryString name="AttributesSerialize"></BinaryString>
|
||||
<float name="BackParamA">-0.5</float>
|
||||
<float name="BackParamB">0.5</float>
|
||||
<token name="BackSurface">0</token>
|
||||
<token name="BackSurfaceInput">0</token>
|
||||
<float name="BottomParamA">-0.5</float>
|
||||
<float name="BottomParamB">0.5</float>
|
||||
<token name="BottomSurface">0</token>
|
||||
<token name="BottomSurfaceInput">0</token>
|
||||
<CoordinateFrame name="CFrame">
|
||||
<X>-7</X>
|
||||
<Y>0.500001</Y>
|
||||
<Z>-8</Z>
|
||||
<R00>1</R00>
|
||||
<R01>0</R01>
|
||||
<R02>0</R02>
|
||||
<R10>0</R10>
|
||||
<R11>1</R11>
|
||||
<R12>0</R12>
|
||||
<R20>0</R20>
|
||||
<R21>0</R21>
|
||||
<R22>1</R22>
|
||||
</CoordinateFrame>
|
||||
<bool name="CanCollide">true</bool>
|
||||
<bool name="CanQuery">true</bool>
|
||||
<bool name="CanTouch">true</bool>
|
||||
<bool name="CastShadow">true</bool>
|
||||
<int name="CollisionGroupId">0</int>
|
||||
<Color3uint8 name="Color3uint8">10724005</Color3uint8>
|
||||
<PhysicalProperties name="CustomPhysicalProperties">
|
||||
<CustomPhysics>false</CustomPhysics>
|
||||
</PhysicalProperties>
|
||||
<token name="formFactorRaw">1</token>
|
||||
<float name="FrontParamA">-0.5</float>
|
||||
<float name="FrontParamB">0.5</float>
|
||||
<token name="FrontSurface">0</token>
|
||||
<token name="FrontSurfaceInput">0</token>
|
||||
<float name="LeftParamA">-0.5</float>
|
||||
<float name="LeftParamB">0.5</float>
|
||||
<token name="LeftSurface">0</token>
|
||||
<token name="LeftSurfaceInput">0</token>
|
||||
<bool name="Locked">false</bool>
|
||||
<bool name="Massless">false</bool>
|
||||
<token name="Material">256</token>
|
||||
<CoordinateFrame name="PivotOffset">
|
||||
<X>0</X>
|
||||
<Y>0</Y>
|
||||
<Z>0</Z>
|
||||
<R00>1</R00>
|
||||
<R01>0</R01>
|
||||
<R02>0</R02>
|
||||
<R10>0</R10>
|
||||
<R11>1</R11>
|
||||
<R12>0</R12>
|
||||
<R20>0</R20>
|
||||
<R21>0</R21>
|
||||
<R22>1</R22>
|
||||
</CoordinateFrame>
|
||||
<float name="Reflectance">0</float>
|
||||
<float name="RightParamA">-0.5</float>
|
||||
<float name="RightParamB">0.5</float>
|
||||
<token name="RightSurface">0</token>
|
||||
<token name="RightSurfaceInput">0</token>
|
||||
<int name="RootPriority">0</int>
|
||||
<Vector3 name="RotVelocity">
|
||||
<X>0</X>
|
||||
<Y>0</Y>
|
||||
<Z>0</Z>
|
||||
</Vector3>
|
||||
<token name="shape">1</token>
|
||||
<Vector3 name="size">
|
||||
<X>4</X>
|
||||
<Y>1</Y>
|
||||
<Z>2</Z>
|
||||
</Vector3>
|
||||
<int64 name="SourceAssetId">-1</int64>
|
||||
<BinaryString name="Tags"></BinaryString>
|
||||
<float name="TopParamA">-0.5</float>
|
||||
<float name="TopParamB">0.5</float>
|
||||
<token name="TopSurface">0</token>
|
||||
<token name="TopSurfaceInput">0</token>
|
||||
<float name="Transparency">0</float>
|
||||
<Vector3 name="Velocity">
|
||||
<X>0</X>
|
||||
<Y>0</Y>
|
||||
<Z>0</Z>
|
||||
</Vector3>
|
||||
</Properties>
|
||||
</Item>
|
||||
</Item>
|
||||
</roblox>
|
||||
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
|
||||
}
|
||||
}
|
||||
15
rojo-test/build-tests/optional/default.project.json
Normal file
15
rojo-test/build-tests/optional/default.project.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "optional",
|
||||
"tree": {
|
||||
"$className": "Folder",
|
||||
"foo-required": {
|
||||
"$path": "foo.txt"
|
||||
},
|
||||
"foo-optional":{
|
||||
"$path": { "optional": "foo.txt" }
|
||||
},
|
||||
"bar-optional":{
|
||||
"$path": { "optional": "bar.txt" }
|
||||
}
|
||||
}
|
||||
}
|
||||
1
rojo-test/build-tests/optional/foo.txt
Normal file
1
rojo-test/build-tests/optional/foo.txt
Normal file
@@ -0,0 +1 @@
|
||||
Hello, from foo.txt!
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "root",
|
||||
"tree": {
|
||||
"$className": "Folder",
|
||||
"folder": {
|
||||
"$path": "folder"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "child-projectname",
|
||||
"tree": {
|
||||
"$className": "Folder"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "root",
|
||||
"tree": {
|
||||
"$className": "Folder",
|
||||
"folder": {
|
||||
"$path": "folder"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "child-projectname",
|
||||
"tree": {
|
||||
"$className": "Folder"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "root",
|
||||
"tree": {
|
||||
"$className": "Folder"
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,13 @@
|
||||
"Color": {
|
||||
"$className": "Part",
|
||||
"$properties": {
|
||||
"Color": [0.5, 0.25, 0]
|
||||
"Color": [0.5, 0.25, 0],
|
||||
"CFrame": [
|
||||
1, 2, 3,
|
||||
0, 1, 0,
|
||||
0, 0, 1,
|
||||
1, 0, 0
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "weldconstraint",
|
||||
"tree": {
|
||||
"$path": "two-parts-welded.rbxmx"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: tests/tests/serve.rs
|
||||
expression: redactions.redacted_yaml(info)
|
||||
|
||||
---
|
||||
expectedPlaceIds: ~
|
||||
gameId: ~
|
||||
placeId: ~
|
||||
projectName: optional
|
||||
protocolVersion: 4
|
||||
rootInstanceId: id-2
|
||||
serverVersion: "[server-version]"
|
||||
sessionId: id-1
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
source: tests/tests/serve.rs
|
||||
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||
|
||||
---
|
||||
instances:
|
||||
id-2:
|
||||
Children:
|
||||
- id-3
|
||||
ClassName: Folder
|
||||
Id: id-2
|
||||
Metadata:
|
||||
ignoreUnknownInstances: true
|
||||
Name: optional
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties: {}
|
||||
id-3:
|
||||
Children:
|
||||
- id-4
|
||||
- id-5
|
||||
ClassName: Folder
|
||||
Id: id-3
|
||||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: src
|
||||
Parent: id-2
|
||||
Properties: {}
|
||||
id-4:
|
||||
Children: []
|
||||
ClassName: StringValue
|
||||
Id: id-4
|
||||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: foo
|
||||
Parent: id-3
|
||||
Properties:
|
||||
Value:
|
||||
String: "Hello, from foo.txt!"
|
||||
id-5:
|
||||
Children:
|
||||
- id-6
|
||||
ClassName: Folder
|
||||
Id: id-5
|
||||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: node_modules
|
||||
Parent: id-3
|
||||
Properties: {}
|
||||
id-6:
|
||||
Children: []
|
||||
ClassName: StringValue
|
||||
Id: id-6
|
||||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: bar
|
||||
Parent: id-5
|
||||
Properties:
|
||||
Value:
|
||||
String: Hello from bar.txt
|
||||
messageCursor: 2
|
||||
sessionId: id-1
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
source: tests/tests/serve.rs
|
||||
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||
|
||||
---
|
||||
instances:
|
||||
id-2:
|
||||
Children:
|
||||
- id-3
|
||||
ClassName: Folder
|
||||
Id: id-2
|
||||
Metadata:
|
||||
ignoreUnknownInstances: true
|
||||
Name: optional
|
||||
Parent: "00000000000000000000000000000000"
|
||||
Properties: {}
|
||||
id-3:
|
||||
Children:
|
||||
- id-4
|
||||
ClassName: Folder
|
||||
Id: id-3
|
||||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: src
|
||||
Parent: id-2
|
||||
Properties: {}
|
||||
id-4:
|
||||
Children: []
|
||||
ClassName: StringValue
|
||||
Id: id-4
|
||||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: foo
|
||||
Parent: id-3
|
||||
Properties:
|
||||
Value:
|
||||
String: "Hello, from foo.txt!"
|
||||
messageCursor: 0
|
||||
sessionId: id-1
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
source: tests/tests/serve.rs
|
||||
expression: "subscribe_response.intern_and_redact(&mut redactions, ())"
|
||||
|
||||
---
|
||||
messageCursor: 2
|
||||
messages:
|
||||
- added:
|
||||
id-5:
|
||||
Children:
|
||||
- id-6
|
||||
ClassName: Folder
|
||||
Id: id-5
|
||||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: node_modules
|
||||
Parent: id-3
|
||||
Properties: {}
|
||||
id-6:
|
||||
Children: []
|
||||
ClassName: StringValue
|
||||
Id: id-6
|
||||
Metadata:
|
||||
ignoreUnknownInstances: false
|
||||
Name: bar
|
||||
Parent: id-5
|
||||
Properties:
|
||||
Value:
|
||||
String: Hello from bar.txt
|
||||
removed: []
|
||||
updated: []
|
||||
- added: {}
|
||||
removed: []
|
||||
updated: []
|
||||
sessionId: id-1
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "optional",
|
||||
"tree": {
|
||||
"$className": "Folder",
|
||||
"create-later": {
|
||||
"$path": { "optional": "create-later" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
std = "roblox"
|
||||
std = "roblox+testez"
|
||||
|
||||
[config]
|
||||
unused_variable = { allow_unused_self = true }
|
||||
|
||||
@@ -284,22 +284,14 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: Ref) -> Option<
|
||||
// that path and use it as the source for our patch.
|
||||
|
||||
let snapshot = match snapshot_from_vfs(&metadata.context, &vfs, &path) {
|
||||
Ok(Some(snapshot)) => snapshot,
|
||||
Ok(None) => {
|
||||
log::error!(
|
||||
"Snapshot did not return an instance from path {}",
|
||||
path.display()
|
||||
);
|
||||
log::error!("This may be a bug!");
|
||||
return None;
|
||||
}
|
||||
Ok(snapshot) => snapshot,
|
||||
Err(err) => {
|
||||
log::error!("Snapshot error: {:?}", err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let patch_set = compute_patch_set(&snapshot, &tree, id);
|
||||
let patch_set = compute_patch_set(snapshot, &tree, id);
|
||||
apply_patch_set(tree, patch_set)
|
||||
}
|
||||
Ok(None) => {
|
||||
@@ -335,19 +327,14 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: Ref) -> Option<
|
||||
);
|
||||
|
||||
let snapshot = match snapshot_result {
|
||||
Ok(Some(snapshot)) => snapshot,
|
||||
Ok(None) => {
|
||||
log::error!("Snapshot did not return an instance from a project node.");
|
||||
log::error!("This is a bug!");
|
||||
return None;
|
||||
}
|
||||
Ok(snapshot) => snapshot,
|
||||
Err(err) => {
|
||||
log::error!("{:?}", err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let patch_set = compute_patch_set(&snapshot, &tree, id);
|
||||
let patch_set = compute_patch_set(snapshot, &tree, id);
|
||||
apply_patch_set(tree, patch_set)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::{
|
||||
io::{BufWriter, Write},
|
||||
mem::forget,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use fs_err::File;
|
||||
use memofs::Vfs;
|
||||
use structopt::StructOpt;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
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.";
|
||||
|
||||
/// Generates a model or place file from the Rojo project.
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct BuildCommand {
|
||||
/// Path to the project to serve. Defaults to the current directory.
|
||||
#[structopt(default_value = "")]
|
||||
#[clap(default_value = "")]
|
||||
pub project: PathBuf,
|
||||
|
||||
/// Where to output the result.
|
||||
///
|
||||
/// Should end in .rbxm, .rbxl, .rbxmx, or .rbxlx.
|
||||
#[structopt(long, short)]
|
||||
#[clap(long, short)]
|
||||
pub output: PathBuf,
|
||||
|
||||
/// Whether to automatically rebuild when any input files change.
|
||||
#[structopt(long)]
|
||||
#[clap(long)]
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -97,6 +102,7 @@ fn xml_encode_config() -> rbx_xml::EncodeOptions {
|
||||
rbx_xml::EncodeOptions::new().property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown)
|
||||
}
|
||||
|
||||
#[profiling::function]
|
||||
fn write_model(
|
||||
session: &ServeSession,
|
||||
output: &Path,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use structopt::StructOpt;
|
||||
use clap::Parser;
|
||||
|
||||
/// Open Rojo's documentation in your browser.
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct DocCommand {}
|
||||
|
||||
impl DocCommand {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Context;
|
||||
use structopt::StructOpt;
|
||||
use clap::Parser;
|
||||
|
||||
use crate::project::Project;
|
||||
|
||||
/// Reformat a Rojo project using the standard JSON formatting rules.
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct FmtProjectCommand {
|
||||
/// Path to the project to format. Defaults to the current directory.
|
||||
#[structopt(default_value = "")]
|
||||
#[clap(default_value = "")]
|
||||
pub project: PathBuf,
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ use std::process::{Command, Stdio};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{bail, format_err};
|
||||
use clap::Parser;
|
||||
use fs_err as fs;
|
||||
use fs_err::OpenOptions;
|
||||
use structopt::StructOpt;
|
||||
|
||||
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");
|
||||
|
||||
/// Initializes a new Rojo project.
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct InitCommand {
|
||||
/// Path to the place to create the project. Defaults to the current directory.
|
||||
#[structopt(default_value = "")]
|
||||
#[clap(default_value = "")]
|
||||
pub path: PathBuf,
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Defines Rojo's CLI through structopt types.
|
||||
//! Defines Rojo's CLI through clap types.
|
||||
|
||||
mod build;
|
||||
mod doc;
|
||||
@@ -6,11 +6,12 @@ mod fmt_project;
|
||||
mod init;
|
||||
mod plugin;
|
||||
mod serve;
|
||||
mod sourcemap;
|
||||
mod upload;
|
||||
|
||||
use std::{borrow::Cow, env, path::Path, str::FromStr};
|
||||
|
||||
use structopt::StructOpt;
|
||||
use clap::Parser;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use self::build::BuildCommand;
|
||||
@@ -19,17 +20,18 @@ pub use self::fmt_project::FmtProjectCommand;
|
||||
pub use self::init::{InitCommand, InitKind};
|
||||
pub use self::plugin::{PluginCommand, PluginSubcommand};
|
||||
pub use self::serve::ServeCommand;
|
||||
pub use self::sourcemap::SourcemapCommand;
|
||||
pub use self::upload::UploadCommand;
|
||||
|
||||
/// Command line options that Rojo accepts, defined using the structopt crate.
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(name = "Rojo", about, author)]
|
||||
/// Command line options that Rojo accepts, defined using the clap crate.
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(name = "Rojo", version, about, author)]
|
||||
pub struct Options {
|
||||
#[structopt(flatten)]
|
||||
#[clap(flatten)]
|
||||
pub global: GlobalOptions,
|
||||
|
||||
/// Subcommand to run in this invocation.
|
||||
#[structopt(subcommand)]
|
||||
#[clap(subcommand)]
|
||||
pub subcommand: Subcommand,
|
||||
}
|
||||
|
||||
@@ -40,6 +42,7 @@ impl Options {
|
||||
Subcommand::Serve(subcommand) => subcommand.run(self.global),
|
||||
Subcommand::Build(subcommand) => subcommand.run(),
|
||||
Subcommand::Upload(subcommand) => subcommand.run(),
|
||||
Subcommand::Sourcemap(subcommand) => subcommand.run(),
|
||||
Subcommand::FmtProject(subcommand) => subcommand.run(),
|
||||
Subcommand::Doc(subcommand) => subcommand.run(),
|
||||
Subcommand::Plugin(subcommand) => subcommand.run(),
|
||||
@@ -47,14 +50,14 @@ impl Options {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct GlobalOptions {
|
||||
/// 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,
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
@@ -106,12 +109,13 @@ pub struct ColorChoiceParseError {
|
||||
attempted: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub enum Subcommand {
|
||||
Init(InitCommand),
|
||||
Serve(ServeCommand),
|
||||
Build(BuildCommand),
|
||||
Upload(UploadCommand),
|
||||
Sourcemap(SourcemapCommand),
|
||||
FmtProject(FmtProjectCommand),
|
||||
Doc(DocCommand),
|
||||
Plugin(PluginCommand),
|
||||
|
||||
@@ -3,9 +3,9 @@ use std::{
|
||||
io::BufWriter,
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use memofs::{InMemoryFs, Vfs, VfsSnapshot};
|
||||
use roblox_install::RobloxStudio;
|
||||
use structopt::StructOpt;
|
||||
|
||||
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";
|
||||
|
||||
/// Install Rojo's plugin.
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct PluginCommand {
|
||||
#[structopt(subcommand)]
|
||||
#[clap(subcommand)]
|
||||
subcommand: PluginSubcommand,
|
||||
}
|
||||
|
||||
/// Manages Rojo's Roblox Studio plugin.
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub enum PluginSubcommand {
|
||||
/// Install the plugin in Roblox Studio's plugins folder. If the plugin is
|
||||
/// already installed, installing it again will overwrite the current plugin
|
||||
|
||||
@@ -5,8 +5,8 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use memofs::Vfs;
|
||||
use structopt::StructOpt;
|
||||
use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
|
||||
|
||||
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;
|
||||
|
||||
/// Expose a Rojo project to the Rojo Studio plugin.
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct ServeCommand {
|
||||
/// Path to the project to serve. Defaults to the current directory.
|
||||
#[structopt(default_value = "")]
|
||||
#[clap(default_value = "")]
|
||||
pub project: PathBuf,
|
||||
|
||||
/// The IP address to listen on. Defaults to `127.0.0.1`.
|
||||
#[structopt(long)]
|
||||
#[clap(long)]
|
||||
pub address: Option<IpAddr>,
|
||||
|
||||
/// The port to listen on. Defaults to the project's preference, or `34872` if
|
||||
/// it has none.
|
||||
#[structopt(long)]
|
||||
#[clap(long)]
|
||||
pub port: Option<u16>,
|
||||
}
|
||||
|
||||
@@ -41,7 +41,10 @@ impl ServeCommand {
|
||||
|
||||
let session = Arc::new(ServeSession::new(vfs, &project_path)?);
|
||||
|
||||
let ip = self.address.unwrap_or(DEFAULT_BIND_ADDRESS.into());
|
||||
let ip = self
|
||||
.address
|
||||
.or_else(|| session.serve_address())
|
||||
.unwrap_or(DEFAULT_BIND_ADDRESS.into());
|
||||
|
||||
let port = self
|
||||
.port
|
||||
@@ -64,15 +67,17 @@ fn show_start_message(bind_address: IpAddr, port: u16, color: ColorChoice) -> io
|
||||
let writer = BufferWriter::stdout(color);
|
||||
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:")?;
|
||||
|
||||
write!(&mut buffer, " Address: ")?;
|
||||
buffer.set_color(&green)?;
|
||||
if bind_address.is_loopback() {
|
||||
writeln!(&mut buffer, "localhost")?;
|
||||
} else {
|
||||
writeln!(&mut buffer, "{}", bind_address)?;
|
||||
}
|
||||
writeln!(&mut buffer, "{}", address_string)?;
|
||||
|
||||
buffer.set_color(&ColorSpec::new())?;
|
||||
write!(&mut buffer, " Port: ")?;
|
||||
@@ -85,7 +90,7 @@ fn show_start_message(bind_address: IpAddr, port: u16, color: ColorChoice) -> io
|
||||
write!(&mut buffer, "Visit ")?;
|
||||
|
||||
buffer.set_color(&green)?;
|
||||
write!(&mut buffer, "http://localhost:{}/", port)?;
|
||||
write!(&mut buffer, "http://{}:{}/", address_string, port)?;
|
||||
|
||||
buffer.set_color(&ColorSpec::new())?;
|
||||
writeln!(&mut buffer, " in your browser for more information.")?;
|
||||
|
||||
136
src/cli/sourcemap.rs
Normal file
136
src/cli/sourcemap.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use std::{
|
||||
io::{BufWriter, Write},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use fs_err::File;
|
||||
use memofs::Vfs;
|
||||
use rbx_dom_weak::types::Ref;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
serve_session::ServeSession,
|
||||
snapshot::{InstanceWithMeta, RojoTree},
|
||||
};
|
||||
|
||||
use super::resolve_path;
|
||||
|
||||
const PATH_STRIP_FAILED_ERR: &str = "Failed to create relative paths for project file!";
|
||||
|
||||
/// Representation of a node in the generated sourcemap tree.
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct SourcemapNode {
|
||||
name: String,
|
||||
class_name: String,
|
||||
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
file_paths: Vec<PathBuf>,
|
||||
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
children: Vec<SourcemapNode>,
|
||||
}
|
||||
|
||||
/// Generates a sourcemap file from the Rojo project.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct SourcemapCommand {
|
||||
/// Path to the project to use for the sourcemap. Defaults to the current
|
||||
/// directory.
|
||||
#[clap(default_value = "")]
|
||||
pub project: PathBuf,
|
||||
|
||||
/// Where to output the sourcemap. Omit this to use stdout instead of
|
||||
/// writing to a file.
|
||||
///
|
||||
/// Should end in .json.
|
||||
#[clap(long, short)]
|
||||
pub output: Option<PathBuf>,
|
||||
|
||||
/// If non-script files should be included or not. Defaults to false.
|
||||
#[clap(long)]
|
||||
pub include_non_scripts: bool,
|
||||
}
|
||||
|
||||
impl SourcemapCommand {
|
||||
pub fn run(self) -> anyhow::Result<()> {
|
||||
let project_path = resolve_path(&self.project);
|
||||
|
||||
log::trace!("Constructing in-memory filesystem");
|
||||
let vfs = Vfs::new_default();
|
||||
|
||||
let session = ServeSession::new(vfs, &project_path)?;
|
||||
let tree = session.tree();
|
||||
|
||||
let filter = if self.include_non_scripts {
|
||||
filter_nothing
|
||||
} else {
|
||||
filter_non_scripts
|
||||
};
|
||||
|
||||
let root_node = recurse_create_node(&tree, tree.get_root_id(), session.root_dir(), filter);
|
||||
|
||||
if let Some(output_path) = self.output {
|
||||
let mut file = BufWriter::new(File::create(&output_path)?);
|
||||
serde_json::to_writer(&mut file, &root_node)?;
|
||||
file.flush()?;
|
||||
|
||||
println!("Created sourcemap at {}", output_path.display());
|
||||
} else {
|
||||
let output = serde_json::to_string(&root_node)?;
|
||||
println!("{}", output);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_nothing(_instance: &InstanceWithMeta) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn filter_non_scripts(instance: &InstanceWithMeta) -> bool {
|
||||
match instance.class_name() {
|
||||
"Script" | "LocalScript" | "ModuleScript" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn recurse_create_node(
|
||||
tree: &RojoTree,
|
||||
referent: Ref,
|
||||
project_dir: &Path,
|
||||
filter: fn(&InstanceWithMeta) -> bool,
|
||||
) -> Option<SourcemapNode> {
|
||||
let instance = tree.get_instance(referent).expect("instance did not exist");
|
||||
|
||||
let mut children = Vec::new();
|
||||
for &child_id in instance.children() {
|
||||
if let Some(child_node) = recurse_create_node(tree, child_id, &project_dir, filter) {
|
||||
children.push(child_node);
|
||||
}
|
||||
}
|
||||
|
||||
// If this object has no children and doesn't pass the filter, it doesn't
|
||||
// contain any information we're looking for.
|
||||
if children.is_empty() && !filter(&instance) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let file_paths = instance
|
||||
.metadata()
|
||||
.relevant_paths
|
||||
.iter()
|
||||
// Not all paths listed as relevant are guaranteed to exist.
|
||||
.filter(|path| path.is_file())
|
||||
.map(|path| path.strip_prefix(project_dir).expect(PATH_STRIP_FAILED_ERR))
|
||||
.map(|path| path.to_path_buf())
|
||||
.collect();
|
||||
|
||||
Some(SourcemapNode {
|
||||
name: instance.name().to_string(),
|
||||
class_name: instance.class_name().to_string(),
|
||||
file_paths,
|
||||
children,
|
||||
})
|
||||
}
|
||||
@@ -2,30 +2,38 @@ use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{bail, format_err, Context};
|
||||
use clap::Parser;
|
||||
use memofs::Vfs;
|
||||
use reqwest::{
|
||||
header::{ACCEPT, CONTENT_TYPE, COOKIE, USER_AGENT},
|
||||
StatusCode,
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
|
||||
use crate::{auth_cookie::get_auth_cookie, serve_session::ServeSession};
|
||||
|
||||
use super::resolve_path;
|
||||
|
||||
/// Builds the project and uploads it to Roblox.
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct UploadCommand {
|
||||
/// Path to the project to upload. Defaults to the current directory.
|
||||
#[structopt(default_value = "")]
|
||||
#[clap(default_value = "")]
|
||||
pub project: PathBuf,
|
||||
|
||||
/// 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>,
|
||||
|
||||
/// 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.
|
||||
#[clap(long = "api_key")]
|
||||
pub api_key: Option<String>,
|
||||
|
||||
/// The Universe ID of the given place. Required when using the Open Cloud API.
|
||||
#[clap(long = "universe_id")]
|
||||
pub universe_id: Option<u64>,
|
||||
|
||||
/// Asset ID to upload to.
|
||||
#[structopt(long = "asset_id")]
|
||||
#[clap(long = "asset_id")]
|
||||
pub asset_id: u64,
|
||||
}
|
||||
|
||||
@@ -33,10 +41,6 @@ impl UploadCommand {
|
||||
pub fn run(self) -> Result<(), anyhow::Error> {
|
||||
let project_path = resolve_path(&self.project);
|
||||
|
||||
let cookie = self.cookie.or_else(get_auth_cookie).context(
|
||||
"Rojo could not find your Roblox auth cookie. Please pass one via --cookie.",
|
||||
)?;
|
||||
|
||||
let vfs = Vfs::new_default();
|
||||
|
||||
let session = ServeSession::new(vfs, project_path)?;
|
||||
@@ -54,7 +58,36 @@ impl UploadCommand {
|
||||
|
||||
log::trace!("Encoding binary model");
|
||||
rbx_binary::to_writer(&mut buffer, tree.inner(), &encode_ids)?;
|
||||
do_upload(buffer, self.asset_id, &cookie)
|
||||
|
||||
match (self.cookie, self.api_key, self.universe_id) {
|
||||
(cookie, None, universe) => {
|
||||
// using legacy. notify if universe is provided.
|
||||
if universe.is_some() {
|
||||
log::warn!(
|
||||
"--universe_id was provided but is ignored when using legacy upload"
|
||||
);
|
||||
}
|
||||
|
||||
let cookie = cookie.or_else(get_auth_cookie).context(
|
||||
"Rojo could not find your Roblox auth cookie. Please pass one via --cookie.",
|
||||
)?;
|
||||
do_upload(buffer, self.asset_id, &cookie)
|
||||
}
|
||||
|
||||
(cookie, Some(api_key), Some(universe_id)) => {
|
||||
// using open cloud. notify if cookie is provided.
|
||||
if cookie.is_some() {
|
||||
log::warn!("--cookie was provided but is ignored when using Open Cloud API");
|
||||
}
|
||||
|
||||
do_upload_open_cloud(buffer, universe_id, self.asset_id, &api_key)
|
||||
}
|
||||
|
||||
(_, Some(_), None) => {
|
||||
// API key is provided, universe id is not.
|
||||
bail!("--universe_id must be provided to use the Open Cloud API");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +123,7 @@ fn do_upload(buffer: Vec<u8>, asset_id: u64, cookie: &str) -> anyhow::Result<()>
|
||||
asset_id
|
||||
);
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let client = reqwest::blocking::Client::new();
|
||||
|
||||
let build_request = move || {
|
||||
client
|
||||
@@ -125,3 +158,38 @@ fn do_upload(buffer: Vec<u8>, asset_id: u64, cookie: &str) -> anyhow::Result<()>
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Implementation of do_upload that supports the new open cloud api.
|
||||
/// see https://developer.roblox.com/en-us/articles/open-cloud
|
||||
fn do_upload_open_cloud(
|
||||
buffer: Vec<u8>,
|
||||
universe_id: u64,
|
||||
asset_id: u64,
|
||||
api_key: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let url = format!(
|
||||
"https://apis.roblox.com/universes/v1/{}/places/{}/versions?versionType=Published",
|
||||
universe_id, asset_id
|
||||
);
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
|
||||
log::debug!("Uploading to Roblox...");
|
||||
let response = client
|
||||
.post(&url)
|
||||
.header("x-api-key", api_key)
|
||||
.header(CONTENT_TYPE, "application/xml")
|
||||
.header(ACCEPT, "application/json")
|
||||
.body(buffer)
|
||||
.send()?;
|
||||
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
bail!(
|
||||
"The Roblox API returned an unexpected error: {}",
|
||||
response.text()?
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use std::{env, panic, process};
|
||||
|
||||
use backtrace::Backtrace;
|
||||
use structopt::StructOpt;
|
||||
use clap::Parser;
|
||||
|
||||
use librojo::cli::Options;
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "profile-with-tracy")]
|
||||
tracy_client::Client::start();
|
||||
|
||||
panic::set_hook(Box::new(|panic_info| {
|
||||
// PanicInfo's payload is usually a &'static str or String.
|
||||
// See: https://doc.rust-lang.org/beta/std/panic/struct.PanicInfo.html#method.payload
|
||||
@@ -49,7 +52,7 @@ fn main() {
|
||||
process::exit(1);
|
||||
}));
|
||||
|
||||
let options = Options::from_args();
|
||||
let options = Options::parse();
|
||||
|
||||
let log_filter = match options.global.verbosity {
|
||||
0 => "info",
|
||||
|
||||
@@ -64,6 +64,7 @@ impl<T: Clone> MessageQueue<T> {
|
||||
/// This method is only useful in tests. Non-test code should use subscribe
|
||||
/// instead.
|
||||
#[cfg(test)]
|
||||
#[allow(unused)]
|
||||
pub fn subscribe_any(&self) -> oneshot::Receiver<(u32, Vec<T>)> {
|
||||
let cursor = {
|
||||
let messages = self.messages.read().unwrap();
|
||||
|
||||
@@ -36,17 +36,3 @@ where
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
146
src/project.rs
146
src/project.rs
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
fs, io,
|
||||
net::IpAddr,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
@@ -67,6 +68,11 @@ pub struct Project {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub game_id: Option<u64>,
|
||||
|
||||
/// If specified, this address will be used in place of the default address
|
||||
/// As long as --address is unprovided.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub serve_address: Option<IpAddr>,
|
||||
|
||||
/// A list of globs, relative to the folder the project file is in, that
|
||||
/// match files that should be excluded if Rojo encounters them.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
@@ -168,6 +174,35 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct OptionalPathNode {
|
||||
#[serde(serialize_with = "crate::path_serializer::serialize_absolute")]
|
||||
pub optional: PathBuf,
|
||||
}
|
||||
|
||||
impl OptionalPathNode {
|
||||
pub fn new(optional: PathBuf) -> Self {
|
||||
OptionalPathNode { optional }
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a path that is either optional or required
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum PathNode {
|
||||
Required(#[serde(serialize_with = "crate::path_serializer::serialize_absolute")] PathBuf),
|
||||
Optional(OptionalPathNode),
|
||||
}
|
||||
|
||||
impl PathNode {
|
||||
pub fn path(&self) -> &Path {
|
||||
match self {
|
||||
PathNode::Required(pathbuf) => &pathbuf,
|
||||
PathNode::Optional(OptionalPathNode { optional }) => &optional,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes an instance and its descendants in a project.
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct ProjectNode {
|
||||
@@ -218,12 +253,8 @@ pub struct ProjectNode {
|
||||
/// path can point to any file type supported by Rojo, including Lua files
|
||||
/// (`.lua`), Roblox models (`.rbxm`, `.rbxmx`), and localization table
|
||||
/// spreadsheets (`.csv`).
|
||||
#[serde(
|
||||
rename = "$path",
|
||||
serialize_with = "crate::path_serializer::serialize_option_absolute",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub path: Option<PathBuf>,
|
||||
#[serde(rename = "$path", skip_serializing_if = "Option::is_none")]
|
||||
pub path: Option<PathNode>,
|
||||
}
|
||||
|
||||
impl ProjectNode {
|
||||
@@ -243,3 +274,106 @@ impl ProjectNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn path_node_required() {
|
||||
let path_node: PathNode = serde_json::from_str(r#""src""#).unwrap();
|
||||
assert_eq!(path_node, PathNode::Required(PathBuf::from("src")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_node_optional() {
|
||||
let path_node: PathNode = serde_json::from_str(r#"{ "optional": "src" }"#).unwrap();
|
||||
assert_eq!(
|
||||
path_node,
|
||||
PathNode::Optional(OptionalPathNode::new(PathBuf::from("src")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_node_required() {
|
||||
let project_node: ProjectNode = serde_json::from_str(
|
||||
r#"{
|
||||
"$path": "src"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
project_node.path,
|
||||
Some(PathNode::Required(PathBuf::from("src")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_node_optional() {
|
||||
let project_node: ProjectNode = serde_json::from_str(
|
||||
r#"{
|
||||
"$path": { "optional": "src" }
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
project_node.path,
|
||||
Some(PathNode::Optional(OptionalPathNode::new(PathBuf::from(
|
||||
"src"
|
||||
))))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_node_none() {
|
||||
let project_node: ProjectNode = serde_json::from_str(
|
||||
r#"{
|
||||
"$className": "Folder"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(project_node.path, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_node_optional_serialize_absolute() {
|
||||
let project_node: ProjectNode = serde_json::from_str(
|
||||
r#"{
|
||||
"$path": { "optional": "..\\src" }
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let serialized = serde_json::to_string(&project_node).unwrap();
|
||||
assert_eq!(serialized, r#"{"$path":{"optional":"../src"}}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_node_optional_serialize_absolute_no_change() {
|
||||
let project_node: ProjectNode = serde_json::from_str(
|
||||
r#"{
|
||||
"$path": { "optional": "../src" }
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let serialized = serde_json::to_string(&project_node).unwrap();
|
||||
assert_eq!(serialized, r#"{"$path":{"optional":"../src"}}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_node_optional_serialize_optional() {
|
||||
let project_node: ProjectNode = serde_json::from_str(
|
||||
r#"{
|
||||
"$path": "..\\src"
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let serialized = serde_json::to_string(&project_node).unwrap();
|
||||
assert_eq!(serialized, r#"{"$path":"../src"}"#);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user