mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 20:55:50 +00:00
Compare commits
46 Commits
project-cr
...
v7.2.1-sta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0198e626b | ||
|
|
142705f386 | ||
|
|
4cb49c7825 | ||
|
|
05adb82dda | ||
|
|
faf7671799 | ||
|
|
d64db329dd | ||
|
|
e34d2339ad | ||
|
|
d196c5091c | ||
|
|
3e83f92532 | ||
|
|
41d7aaf323 | ||
|
|
e110f3726a | ||
|
|
eb5c897ac0 | ||
|
|
e864cf0c7d | ||
|
|
565c12405e | ||
|
|
2a6a8b42a6 | ||
|
|
5cb4cc0d1d | ||
|
|
62eb4f026f | ||
|
|
411d1a89c1 | ||
|
|
6ae0bf366a | ||
|
|
178cdc9dfa | ||
|
|
5bf1f86886 | ||
|
|
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 |
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -16,12 +16,10 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust_version: [stable, 1.55.0]
|
rust_version: [stable, 1.58.1]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
@@ -30,6 +28,17 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
profile: minimal
|
profile: minimal
|
||||||
|
|
||||||
|
- name: Setup Foreman
|
||||||
|
uses: Roblox/setup-foreman@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install packages
|
||||||
|
run: |
|
||||||
|
cd plugin
|
||||||
|
wally install
|
||||||
|
cd ..
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --locked --verbose
|
run: cargo build --locked --verbose
|
||||||
|
|
||||||
@@ -42,8 +51,6 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
@@ -52,8 +59,19 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Setup Foreman
|
||||||
|
uses: Roblox/setup-foreman@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install packages
|
||||||
|
run: |
|
||||||
|
cd plugin
|
||||||
|
wally install
|
||||||
|
cd ..
|
||||||
|
|
||||||
- name: Rustfmt
|
- name: Rustfmt
|
||||||
run: cargo fmt -- --check
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy
|
run: cargo clippy
|
||||||
|
|||||||
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -28,14 +28,18 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Setup Foreman
|
- name: Setup Foreman
|
||||||
uses: Roblox/setup-foreman@v1
|
uses: Roblox/setup-foreman@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install packages
|
||||||
|
run: |
|
||||||
|
cd plugin
|
||||||
|
wally install
|
||||||
|
cd ..
|
||||||
|
|
||||||
- name: Build Plugin
|
- name: Build Plugin
|
||||||
run: rojo build plugin --output Rojo.rbxm
|
run: rojo build plugin --output Rojo.rbxm
|
||||||
|
|
||||||
@@ -61,25 +65,21 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# https://doc.rust-lang.org/rustc/platform-support.html
|
# 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:
|
include:
|
||||||
- host: linux
|
- host: linux
|
||||||
os: ubuntu-latest
|
os: ubuntu-20.04
|
||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu
|
||||||
label: linux
|
label: linux-x86_64
|
||||||
|
|
||||||
- host: windows
|
- host: windows
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
target: x86_64-pc-windows-msvc
|
target: x86_64-pc-windows-msvc
|
||||||
label: win64
|
label: windows-x86_64
|
||||||
|
|
||||||
- host: macos
|
- host: macos
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
label: macos
|
label: macos-x86_64
|
||||||
|
|
||||||
- host: macos
|
- host: macos
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
@@ -92,8 +92,6 @@ jobs:
|
|||||||
BIN: rojo
|
BIN: rojo
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Get Version from Tag
|
- name: Get Version from Tag
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -110,6 +108,17 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
profile: minimal
|
profile: minimal
|
||||||
|
|
||||||
|
- name: Setup Foreman
|
||||||
|
uses: Roblox/setup-foreman@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install packages
|
||||||
|
run: |
|
||||||
|
cd plugin
|
||||||
|
wally install
|
||||||
|
cd ..
|
||||||
|
|
||||||
- name: Build Release
|
- name: Build Release
|
||||||
run: cargo build --release --locked --verbose
|
run: cargo build --release --locked --verbose
|
||||||
env:
|
env:
|
||||||
|
|||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -13,12 +13,12 @@
|
|||||||
# Test places for the Roblox Studio Plugin
|
# Test places for the Roblox Studio Plugin
|
||||||
/plugin/*.rbxlx
|
/plugin/*.rbxlx
|
||||||
|
|
||||||
|
# Packages for the Roblox Studio Plugin
|
||||||
|
/plugin/*Packages
|
||||||
|
|
||||||
# Roblox Studio holds 'lock' files on places
|
# Roblox Studio holds 'lock' files on places
|
||||||
*.rbxl.lock
|
*.rbxl.lock
|
||||||
*.rbxlx.lock
|
*.rbxlx.lock
|
||||||
|
|
||||||
# Snapshot files from the 'insta' Rust crate
|
# Snapshot files from the 'insta' Rust crate
|
||||||
**/*.snap.new
|
**/*.snap.new
|
||||||
|
|
||||||
# Selene generates a roblox.toml file that should not be checked in.
|
|
||||||
/roblox.toml
|
|
||||||
15
.gitmodules
vendored
15
.gitmodules
vendored
@@ -1,15 +0,0 @@
|
|||||||
[submodule "plugin/modules/roact"]
|
|
||||||
path = plugin/modules/roact
|
|
||||||
url = https://github.com/Roblox/roact.git
|
|
||||||
[submodule "plugin/modules/testez"]
|
|
||||||
path = plugin/modules/testez
|
|
||||||
url = https://github.com/Roblox/testez.git
|
|
||||||
[submodule "plugin/modules/promise"]
|
|
||||||
path = plugin/modules/promise
|
|
||||||
url = https://github.com/LPGhatguy/roblox-lua-promise.git
|
|
||||||
[submodule "plugin/modules/t"]
|
|
||||||
path = plugin/modules/t
|
|
||||||
url = https://github.com/osyrisrblx/t.git
|
|
||||||
[submodule "plugin/modules/flipper"]
|
|
||||||
path = plugin/modules/flipper
|
|
||||||
url = https://github.com/Reselim/Flipper
|
|
||||||
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",
|
|
||||||
}
|
|
||||||
33
CHANGELOG.md
33
CHANGELOG.md
@@ -1,7 +1,40 @@
|
|||||||
# Rojo Changelog
|
# Rojo Changelog
|
||||||
|
|
||||||
## Unreleased Changes
|
## Unreleased Changes
|
||||||
|
* Added `--watch` flag to `rojo sourcemap` ([#602])
|
||||||
|
|
||||||
|
[#602]: https://github.com/rojo-rbx/rojo/pull/602
|
||||||
|
|
||||||
|
## [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.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.2.1
|
||||||
|
|
||||||
|
## [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.
|
* 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
|
## [7.1.1] - May 26, 2022
|
||||||
* Fixed sourcemap command not stripping paths correctly ([#544])
|
* Fixed sourcemap command not stripping paths correctly ([#544])
|
||||||
|
|||||||
567
Cargo.lock
generated
567
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
16
Cargo.toml
@@ -1,6 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rojo"
|
name = "rojo"
|
||||||
version = "7.1.1"
|
version = "7.2.1"
|
||||||
|
rust-version = "1.58.1"
|
||||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||||
description = "Enables professional-grade development tools for Roblox developers"
|
description = "Enables professional-grade development tools for Roblox developers"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
@@ -8,7 +9,7 @@ homepage = "https://rojo.space"
|
|||||||
documentation = "https://rojo.space/docs"
|
documentation = "https://rojo.space/docs"
|
||||||
repository = "https://github.com/rojo-rbx/rojo"
|
repository = "https://github.com/rojo-rbx/rojo"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
exclude = [
|
exclude = [
|
||||||
@@ -27,6 +28,8 @@ default = []
|
|||||||
# Enable this feature to live-reload assets from the web UI.
|
# Enable this feature to live-reload assets from the web UI.
|
||||||
dev_live_assets = []
|
dev_live_assets = []
|
||||||
|
|
||||||
|
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*"]
|
members = ["crates/*"]
|
||||||
|
|
||||||
@@ -39,7 +42,6 @@ name = "build"
|
|||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rojo-project = { path = "crates/rojo-project" }
|
|
||||||
memofs = { version = "0.2.0", path = "crates/memofs" }
|
memofs = { version = "0.2.0", path = "crates/memofs" }
|
||||||
|
|
||||||
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
||||||
@@ -49,8 +51,8 @@ memofs = { version = "0.2.0", path = "crates/memofs" }
|
|||||||
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
||||||
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
||||||
|
|
||||||
rbx_binary = "0.6.4"
|
rbx_binary = "0.6.5"
|
||||||
rbx_dom_weak = "2.3.0"
|
rbx_dom_weak = "2.4.0"
|
||||||
rbx_reflection = "4.2.0"
|
rbx_reflection = "4.2.0"
|
||||||
rbx_reflection_database = "0.2.2"
|
rbx_reflection_database = "0.2.2"
|
||||||
rbx_xml = "0.12.3"
|
rbx_xml = "0.12.3"
|
||||||
@@ -71,7 +73,7 @@ log = "0.4.14"
|
|||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
notify = "4.0.17"
|
notify = "4.0.17"
|
||||||
opener = "0.5.0"
|
opener = "0.5.0"
|
||||||
reqwest = { version = "0.11.10", features = ["blocking", "json"] }
|
reqwest = { version = "0.11.10", features = ["blocking", "json", "native-tls-vendored"] }
|
||||||
ritz = "0.1.0"
|
ritz = "0.1.0"
|
||||||
roblox_install = "1.0.0"
|
roblox_install = "1.0.0"
|
||||||
serde = { version = "1.0.130", features = ["derive", "rc"] }
|
serde = { version = "1.0.130", features = ["derive", "rc"] }
|
||||||
@@ -81,6 +83,8 @@ thiserror = "1.0.30"
|
|||||||
tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] }
|
tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] }
|
||||||
uuid = { version = "1.0.0", features = ["v4", "serde"] }
|
uuid = { version = "1.0.0", features = ["v4", "serde"] }
|
||||||
clap = { version = "3.1.18", features = ["derive"] }
|
clap = { version = "3.1.18", features = ["derive"] }
|
||||||
|
profiling = "1.0.6"
|
||||||
|
tracy-client = { version = "0.13.2", optional = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.10.1"
|
winreg = "0.10.1"
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions fo
|
|||||||
|
|
||||||
Pull requests are welcome!
|
Pull requests are welcome!
|
||||||
|
|
||||||
Rojo supports Rust 1.46.0 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
Rojo supports Rust 1.58.1 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
||||||
BIN
assets/NotificationPop.mp3
Normal file
BIN
assets/NotificationPop.mp3
Normal file
Binary file not shown.
@@ -4,7 +4,7 @@
|
|||||||
"$className": "DataModel",
|
"$className": "DataModel",
|
||||||
|
|
||||||
"ReplicatedStorage": {
|
"ReplicatedStorage": {
|
||||||
"Common": {
|
"Shared": {
|
||||||
"$path": "src/shared"
|
"$path": "src/shared"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::path::Path;
|
|||||||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||||
use tempfile::{tempdir, TempDir};
|
use tempfile::{tempdir, TempDir};
|
||||||
|
|
||||||
use librojo::cli::{build, BuildCommand};
|
use librojo::cli::BuildCommand;
|
||||||
|
|
||||||
pub fn benchmark_small_place(c: &mut Criterion) {
|
pub fn benchmark_small_place(c: &mut Criterion) {
|
||||||
bench_build_place(c, "Small Place", "test-projects/benchmark_small_place")
|
bench_build_place(c, "Small Place", "test-projects/benchmark_small_place")
|
||||||
@@ -20,7 +20,7 @@ fn bench_build_place(c: &mut Criterion, name: &str, path: &str) {
|
|||||||
group.bench_function("build", |b| {
|
group.bench_function("build", |b| {
|
||||||
b.iter_batched(
|
b.iter_batched(
|
||||||
|| place_setup(path),
|
|| place_setup(path),
|
||||||
|(_dir, options)| build(options).unwrap(),
|
|(_dir, options)| options.run().unwrap(),
|
||||||
BatchSize::SmallInput,
|
BatchSize::SmallInput,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|||||||
19
build.rs
19
build.rs
@@ -21,7 +21,7 @@ fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
|||||||
|
|
||||||
// We can skip any TestEZ test files since they aren't necessary for
|
// We can skip any TestEZ test files since they aren't necessary for
|
||||||
// the plugin to run.
|
// the plugin to run.
|
||||||
if file_name.ends_with(".spec.lua") {
|
if file_name.ends_with(".spec.lua") || file_name.ends_with(".spec.luau") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +43,6 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
let root_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
|
let root_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
|
||||||
let plugin_root = PathBuf::from(root_dir).join("plugin");
|
let plugin_root = PathBuf::from(root_dir).join("plugin");
|
||||||
|
|
||||||
let plugin_modules = plugin_root.join("modules");
|
|
||||||
|
|
||||||
let snapshot = VfsSnapshot::dir(hashmap! {
|
let snapshot = VfsSnapshot::dir(hashmap! {
|
||||||
"default.project.json" => snapshot_from_fs_path(&plugin_root.join("default.project.json"))?,
|
"default.project.json" => snapshot_from_fs_path(&plugin_root.join("default.project.json"))?,
|
||||||
"fmt" => snapshot_from_fs_path(&plugin_root.join("fmt"))?,
|
"fmt" => snapshot_from_fs_path(&plugin_root.join("fmt"))?,
|
||||||
@@ -52,20 +50,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
"log" => snapshot_from_fs_path(&plugin_root.join("log"))?,
|
"log" => snapshot_from_fs_path(&plugin_root.join("log"))?,
|
||||||
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_root.join("rbx_dom_lua"))?,
|
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_root.join("rbx_dom_lua"))?,
|
||||||
"src" => snapshot_from_fs_path(&plugin_root.join("src"))?,
|
"src" => snapshot_from_fs_path(&plugin_root.join("src"))?,
|
||||||
"modules" => VfsSnapshot::dir(hashmap! {
|
"Packages" => snapshot_from_fs_path(&plugin_root.join("Packages"))?,
|
||||||
"roact" => VfsSnapshot::dir(hashmap! {
|
|
||||||
"src" => snapshot_from_fs_path(&plugin_modules.join("roact").join("src"))?
|
|
||||||
}),
|
|
||||||
"promise" => VfsSnapshot::dir(hashmap! {
|
|
||||||
"lib" => snapshot_from_fs_path(&plugin_modules.join("promise").join("lib"))?
|
|
||||||
}),
|
|
||||||
"t" => VfsSnapshot::dir(hashmap! {
|
|
||||||
"lib" => snapshot_from_fs_path(&plugin_modules.join("t").join("lib"))?
|
|
||||||
}),
|
|
||||||
"flipper" => VfsSnapshot::dir(hashmap! {
|
|
||||||
"src" => snapshot_from_fs_path(&plugin_modules.join("flipper").join("src"))?
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let out_path = Path::new(&out_dir).join("plugin.bincode");
|
let out_path = Path::new(&out_dir).join("plugin.bincode");
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "rojo-project"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1.0.57"
|
|
||||||
globset = { version = "0.4.8", features = ["serde1"] }
|
|
||||||
log = "0.4.17"
|
|
||||||
rbx_dom_weak = "2.3.0"
|
|
||||||
rbx_reflection = "4.2.0"
|
|
||||||
rbx_reflection_database = "0.2.4"
|
|
||||||
serde = { version = "1.0.137", features = ["derive"] }
|
|
||||||
serde_json = "1.0.81"
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# rojo-project
|
|
||||||
Project file format crate for [Rojo].
|
|
||||||
|
|
||||||
[Rojo]: https://rojo.space
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
pub mod glob;
|
|
||||||
mod path_serializer;
|
|
||||||
mod project;
|
|
||||||
mod resolution;
|
|
||||||
|
|
||||||
pub use project::{OptionalPathNode, PathNode, Project, ProjectNode};
|
|
||||||
pub use resolution::{AmbiguousValue, UnresolvedValue};
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
//! Path serializer is used to serialize absolute paths in a cross-platform way,
|
|
||||||
//! by replacing all directory separators with /.
|
|
||||||
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use serde::Serializer;
|
|
||||||
|
|
||||||
pub fn serialize_absolute<S, T>(path: T, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
T: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let as_str = path
|
|
||||||
.as_ref()
|
|
||||||
.as_os_str()
|
|
||||||
.to_str()
|
|
||||||
.expect("Invalid Unicode in file path, cannot serialize");
|
|
||||||
let replaced = as_str.replace("\\", "/");
|
|
||||||
|
|
||||||
serializer.serialize_str(&replaced)
|
|
||||||
}
|
|
||||||
@@ -1,363 +0,0 @@
|
|||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
|
||||||
use std::fs;
|
|
||||||
use std::net::IpAddr;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::glob::Glob;
|
|
||||||
use crate::resolution::UnresolvedValue;
|
|
||||||
|
|
||||||
static PROJECT_FILENAME: &str = "default.project.json";
|
|
||||||
|
|
||||||
/// Contains all of the configuration for a Rojo-managed project.
|
|
||||||
///
|
|
||||||
/// Rojo project files are stored in `.project.json` files.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
|
||||||
pub struct Project {
|
|
||||||
/// The name of the top-level instance described by the project.
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
/// The tree of instances described by this project. Projects always
|
|
||||||
/// describe at least one instance.
|
|
||||||
pub tree: ProjectNode,
|
|
||||||
|
|
||||||
/// If specified, sets the default port that `rojo serve` should use when
|
|
||||||
/// using this project for live sync.
|
|
||||||
///
|
|
||||||
/// Can be overriden with the `--port` flag.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub serve_port: Option<u16>,
|
|
||||||
|
|
||||||
/// If specified, sets the default IP address that `rojo serve` should use
|
|
||||||
/// when using this project for live sync.
|
|
||||||
///
|
|
||||||
/// Can be overridden with the `--address` flag.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub serve_address: Option<IpAddr>,
|
|
||||||
|
|
||||||
/// If specified, contains the set of place IDs that this project is
|
|
||||||
/// compatible with when doing live sync.
|
|
||||||
///
|
|
||||||
/// This setting is intended to help prevent syncing a Rojo project into the
|
|
||||||
/// wrong Roblox place.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub serve_place_ids: Option<HashSet<u64>>,
|
|
||||||
|
|
||||||
/// If specified, sets the current place's place ID when connecting to the
|
|
||||||
/// Rojo server from Roblox Studio.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub place_id: Option<u64>,
|
|
||||||
|
|
||||||
/// If specified, sets the current place's game ID when connecting to the
|
|
||||||
/// Rojo server from Roblox Studio.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub game_id: Option<u64>,
|
|
||||||
|
|
||||||
/// 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")]
|
|
||||||
pub glob_ignore_paths: Vec<Glob>,
|
|
||||||
|
|
||||||
/// The path to the file that this project came from. Relative paths in the
|
|
||||||
/// project should be considered relative to the parent of this field, also
|
|
||||||
/// given by `Project::folder_location`.
|
|
||||||
#[serde(skip)]
|
|
||||||
pub file_location: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Project {
|
|
||||||
/// Tells whether the given path describes a Rojo project.
|
|
||||||
pub fn is_project_file(path: &Path) -> bool {
|
|
||||||
path.file_name()
|
|
||||||
.and_then(|name| name.to_str())
|
|
||||||
.map(|name| name.ends_with(".project.json"))
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads a project file from a slice and a path that indicates where the
|
|
||||||
/// project should resolve paths relative to.
|
|
||||||
pub fn load_from_slice(contents: &[u8], project_file_location: &Path) -> anyhow::Result<Self> {
|
|
||||||
let mut project: Self = serde_json::from_slice(&contents).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"Error parsing Rojo project at {}",
|
|
||||||
project_file_location.display()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
project.file_location = project_file_location.to_path_buf();
|
|
||||||
project.check_compatibility();
|
|
||||||
Ok(project)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fuzzy-find a Rojo project and load it.
|
|
||||||
pub fn load_fuzzy(fuzzy_project_location: &Path) -> anyhow::Result<Option<Self>> {
|
|
||||||
if let Some(project_path) = Self::locate(fuzzy_project_location) {
|
|
||||||
let project = Self::load_exact(&project_path)?;
|
|
||||||
|
|
||||||
Ok(Some(project))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gives the path that all project file paths should resolve relative to.
|
|
||||||
pub fn folder_location(&self) -> &Path {
|
|
||||||
self.file_location.parent().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to locate a project represented by the given path.
|
|
||||||
///
|
|
||||||
/// This will find a project if the path refers to a `.project.json` file,
|
|
||||||
/// or is a folder that contains a `default.project.json` file.
|
|
||||||
fn locate(path: &Path) -> Option<PathBuf> {
|
|
||||||
let meta = fs::metadata(path).ok()?;
|
|
||||||
|
|
||||||
if meta.is_file() {
|
|
||||||
if Project::is_project_file(path) {
|
|
||||||
Some(path.to_path_buf())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let child_path = path.join(PROJECT_FILENAME);
|
|
||||||
let child_meta = fs::metadata(&child_path).ok()?;
|
|
||||||
|
|
||||||
if child_meta.is_file() {
|
|
||||||
Some(child_path)
|
|
||||||
} else {
|
|
||||||
// This is a folder with the same name as a Rojo default project
|
|
||||||
// file.
|
|
||||||
//
|
|
||||||
// That's pretty weird, but we can roll with it.
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_exact(project_file_location: &Path) -> anyhow::Result<Self> {
|
|
||||||
let contents = fs::read_to_string(project_file_location)?;
|
|
||||||
|
|
||||||
let mut project: Project = serde_json::from_str(&contents).with_context(|| {
|
|
||||||
format!(
|
|
||||||
"Error parsing Rojo project at {}",
|
|
||||||
project_file_location.display()
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
project.file_location = project_file_location.to_path_buf();
|
|
||||||
project.check_compatibility();
|
|
||||||
|
|
||||||
Ok(project)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if there are any compatibility issues with this project file and
|
|
||||||
/// warns the user if there are any.
|
|
||||||
fn check_compatibility(&self) {
|
|
||||||
self.tree.validate_reserved_names();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 {
|
|
||||||
/// If set, defines the ClassName of the described instance.
|
|
||||||
///
|
|
||||||
/// `$className` MUST be set if `$path` is not set.
|
|
||||||
///
|
|
||||||
/// `$className` CANNOT be set if `$path` is set and the instance described
|
|
||||||
/// by that path has a ClassName other than Folder.
|
|
||||||
#[serde(rename = "$className", skip_serializing_if = "Option::is_none")]
|
|
||||||
pub class_name: Option<String>,
|
|
||||||
|
|
||||||
/// Contains all of the children of the described instance.
|
|
||||||
#[serde(flatten)]
|
|
||||||
pub children: BTreeMap<String, ProjectNode>,
|
|
||||||
|
|
||||||
/// The properties that will be assigned to the resulting instance.
|
|
||||||
#[serde(
|
|
||||||
rename = "$properties",
|
|
||||||
default,
|
|
||||||
skip_serializing_if = "HashMap::is_empty"
|
|
||||||
)]
|
|
||||||
pub properties: HashMap<String, UnresolvedValue>,
|
|
||||||
|
|
||||||
/// Defines the behavior when Rojo encounters unknown instances in Roblox
|
|
||||||
/// Studio during live sync. `$ignoreUnknownInstances` should be considered
|
|
||||||
/// a large hammer and used with care.
|
|
||||||
///
|
|
||||||
/// If set to `true`, those instances will be left alone. This may cause
|
|
||||||
/// issues when files that turn into instances are removed while Rojo is not
|
|
||||||
/// running.
|
|
||||||
///
|
|
||||||
/// If set to `false`, Rojo will destroy any instances it does not
|
|
||||||
/// recognize.
|
|
||||||
///
|
|
||||||
/// If unset, its default value depends on other settings:
|
|
||||||
/// - If `$path` is not set, defaults to `true`
|
|
||||||
/// - If `$path` is set, defaults to `false`
|
|
||||||
#[serde(
|
|
||||||
rename = "$ignoreUnknownInstances",
|
|
||||||
skip_serializing_if = "Option::is_none"
|
|
||||||
)]
|
|
||||||
pub ignore_unknown_instances: Option<bool>,
|
|
||||||
|
|
||||||
/// Defines that this instance should come from the given file path. This
|
|
||||||
/// 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", skip_serializing_if = "Option::is_none")]
|
|
||||||
pub path: Option<PathNode>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProjectNode {
|
|
||||||
fn validate_reserved_names(&self) {
|
|
||||||
for (name, child) in &self.children {
|
|
||||||
if name.starts_with('$') {
|
|
||||||
log::warn!(
|
|
||||||
"Keys starting with '$' are reserved by Rojo to ensure forward compatibility."
|
|
||||||
);
|
|
||||||
log::warn!(
|
|
||||||
"This project uses the key '{}', which should be renamed.",
|
|
||||||
name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
child.validate_reserved_names();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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"}"#);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,294 +0,0 @@
|
|||||||
use std::borrow::Borrow;
|
|
||||||
|
|
||||||
use anyhow::format_err;
|
|
||||||
use rbx_dom_weak::types::{
|
|
||||||
CFrame, Color3, Content, Enum, Matrix3, Tags, Variant, VariantType, Vector2, Vector3,
|
|
||||||
};
|
|
||||||
use rbx_reflection::{DataType, PropertyDescriptor};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// A user-friendly version of `Variant` that supports specifying ambiguous
|
|
||||||
/// values. Ambiguous values need a reflection database to be resolved to a
|
|
||||||
/// usable value.
|
|
||||||
///
|
|
||||||
/// This type is used in Rojo projects and JSON models to make specifying the
|
|
||||||
/// most common types of properties, like strings or vectors, much easier.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum UnresolvedValue {
|
|
||||||
FullyQualified(Variant),
|
|
||||||
Ambiguous(AmbiguousValue),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UnresolvedValue {
|
|
||||||
pub fn resolve(self, class_name: &str, prop_name: &str) -> anyhow::Result<Variant> {
|
|
||||||
match self {
|
|
||||||
UnresolvedValue::FullyQualified(full) => Ok(full),
|
|
||||||
UnresolvedValue::Ambiguous(partial) => partial.resolve(class_name, prop_name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum AmbiguousValue {
|
|
||||||
Bool(bool),
|
|
||||||
String(String),
|
|
||||||
StringArray(Vec<String>),
|
|
||||||
Number(f64),
|
|
||||||
Array2([f64; 2]),
|
|
||||||
Array3([f64; 3]),
|
|
||||||
Array4([f64; 4]),
|
|
||||||
Array12([f64; 12]),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AmbiguousValue {
|
|
||||||
pub fn resolve(self, class_name: &str, prop_name: &str) -> anyhow::Result<Variant> {
|
|
||||||
let property = find_descriptor(class_name, prop_name)
|
|
||||||
.ok_or_else(|| format_err!("Unknown property {}.{}", class_name, prop_name))?;
|
|
||||||
|
|
||||||
match &property.data_type {
|
|
||||||
DataType::Enum(enum_name) => {
|
|
||||||
let database = rbx_reflection_database::get();
|
|
||||||
|
|
||||||
let enum_descriptor = database.enums.get(enum_name).ok_or_else(|| {
|
|
||||||
format_err!("Unknown enum {}. This is a Rojo bug!", enum_name)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let error = |what: &str| {
|
|
||||||
let mut all_values = enum_descriptor
|
|
||||||
.items
|
|
||||||
.keys()
|
|
||||||
.map(|value| value.borrow())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
all_values.sort();
|
|
||||||
|
|
||||||
let examples = nonexhaustive_list(&all_values);
|
|
||||||
|
|
||||||
format_err!(
|
|
||||||
"Invalid value for property {}.{}. Got {} but \
|
|
||||||
expected a member of the {} enum such as {}",
|
|
||||||
class_name,
|
|
||||||
prop_name,
|
|
||||||
what,
|
|
||||||
enum_name,
|
|
||||||
examples,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = match self {
|
|
||||||
AmbiguousValue::String(value) => value,
|
|
||||||
unresolved => return Err(error(unresolved.describe())),
|
|
||||||
};
|
|
||||||
|
|
||||||
let resolved = enum_descriptor
|
|
||||||
.items
|
|
||||||
.get(value.as_str())
|
|
||||||
.ok_or_else(|| error(value.as_str()))?;
|
|
||||||
|
|
||||||
Ok(Enum::from_u32(*resolved).into())
|
|
||||||
}
|
|
||||||
DataType::Value(variant_ty) => match (variant_ty, self) {
|
|
||||||
(VariantType::Bool, AmbiguousValue::Bool(value)) => Ok(value.into()),
|
|
||||||
|
|
||||||
(VariantType::Float32, AmbiguousValue::Number(value)) => Ok((value as f32).into()),
|
|
||||||
(VariantType::Float64, AmbiguousValue::Number(value)) => Ok(value.into()),
|
|
||||||
(VariantType::Int32, AmbiguousValue::Number(value)) => Ok((value as i32).into()),
|
|
||||||
(VariantType::Int64, AmbiguousValue::Number(value)) => Ok((value as i64).into()),
|
|
||||||
|
|
||||||
(VariantType::String, AmbiguousValue::String(value)) => Ok(value.into()),
|
|
||||||
(VariantType::Tags, AmbiguousValue::StringArray(value)) => {
|
|
||||||
Ok(Tags::from(value).into())
|
|
||||||
}
|
|
||||||
(VariantType::Content, AmbiguousValue::String(value)) => {
|
|
||||||
Ok(Content::from(value).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
(VariantType::Vector2, AmbiguousValue::Array2(value)) => {
|
|
||||||
Ok(Vector2::new(value[0] as f32, value[1] as f32).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
(VariantType::Vector3, AmbiguousValue::Array3(value)) => {
|
|
||||||
Ok(Vector3::new(value[0] as f32, value[1] as f32, value[2] as f32).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
(VariantType::Color3, AmbiguousValue::Array3(value)) => {
|
|
||||||
Ok(Color3::new(value[0] as f32, value[1] as f32, value[2] as f32).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
(VariantType::CFrame, AmbiguousValue::Array12(value)) => {
|
|
||||||
let value = value.map(|v| v as f32);
|
|
||||||
let pos = Vector3::new(value[0], value[1], value[2]);
|
|
||||||
let orientation = Matrix3::new(
|
|
||||||
Vector3::new(value[3], value[4], value[5]),
|
|
||||||
Vector3::new(value[6], value[7], value[8]),
|
|
||||||
Vector3::new(value[9], value[10], value[11]),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(CFrame::new(pos, orientation).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
(_, unresolved) => Err(format_err!(
|
|
||||||
"Wrong type of value for property {}.{}. Expected {:?}, got {}",
|
|
||||||
class_name,
|
|
||||||
prop_name,
|
|
||||||
variant_ty,
|
|
||||||
unresolved.describe(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
_ => Err(format_err!(
|
|
||||||
"Unknown data type for property {}.{}",
|
|
||||||
class_name,
|
|
||||||
prop_name
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn describe(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
AmbiguousValue::Bool(_) => "a bool",
|
|
||||||
AmbiguousValue::String(_) => "a string",
|
|
||||||
AmbiguousValue::StringArray(_) => "an array of strings",
|
|
||||||
AmbiguousValue::Number(_) => "a number",
|
|
||||||
AmbiguousValue::Array2(_) => "an array of two numbers",
|
|
||||||
AmbiguousValue::Array3(_) => "an array of three numbers",
|
|
||||||
AmbiguousValue::Array4(_) => "an array of four numbers",
|
|
||||||
AmbiguousValue::Array12(_) => "an array of twelve numbers",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_descriptor(
|
|
||||||
class_name: &str,
|
|
||||||
prop_name: &str,
|
|
||||||
) -> Option<&'static PropertyDescriptor<'static>> {
|
|
||||||
let database = rbx_reflection_database::get();
|
|
||||||
let mut current_class_name = class_name;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let class = database.classes.get(current_class_name)?;
|
|
||||||
if let Some(descriptor) = class.properties.get(prop_name) {
|
|
||||||
return Some(descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
current_class_name = class.superclass.as_deref()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Outputs a string containing up to MAX_ITEMS entries from the given list. If
|
|
||||||
/// there are more than MAX_ITEMS items, the number of remaining items will be
|
|
||||||
/// listed.
|
|
||||||
fn nonexhaustive_list(values: &[&str]) -> String {
|
|
||||||
use std::fmt::Write;
|
|
||||||
|
|
||||||
const MAX_ITEMS: usize = 8;
|
|
||||||
|
|
||||||
let mut output = String::new();
|
|
||||||
|
|
||||||
let last_index = values.len() - 1;
|
|
||||||
let main_length = last_index.min(9);
|
|
||||||
|
|
||||||
let main_list = &values[..main_length];
|
|
||||||
for value in main_list {
|
|
||||||
output.push_str(value);
|
|
||||||
output.push_str(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
if values.len() > MAX_ITEMS {
|
|
||||||
write!(output, "or {} more", values.len() - main_length).unwrap();
|
|
||||||
} else {
|
|
||||||
output.push_str("or ");
|
|
||||||
output.push_str(values[values.len() - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn resolve(class: &str, prop: &str, json_value: &str) -> Variant {
|
|
||||||
let unresolved: UnresolvedValue = serde_json::from_str(json_value).unwrap();
|
|
||||||
unresolved.resolve(class, prop).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bools() {
|
|
||||||
assert_eq!(resolve("BoolValue", "Value", "false"), Variant::Bool(false));
|
|
||||||
|
|
||||||
// Script.Disabled is inherited from BaseScript
|
|
||||||
assert_eq!(resolve("Script", "Disabled", "true"), Variant::Bool(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn strings() {
|
|
||||||
// String literals can stay as strings
|
|
||||||
assert_eq!(
|
|
||||||
resolve("StringValue", "Value", "\"Hello!\""),
|
|
||||||
Variant::String("Hello!".into()),
|
|
||||||
);
|
|
||||||
|
|
||||||
// String literals can also turn into Content
|
|
||||||
assert_eq!(
|
|
||||||
resolve("Sky", "MoonTextureId", "\"rbxassetid://12345\""),
|
|
||||||
Variant::Content("rbxassetid://12345".into()),
|
|
||||||
);
|
|
||||||
|
|
||||||
// What about BinaryString values? For forward-compatibility reasons, we
|
|
||||||
// don't support any shorthands for BinaryString.
|
|
||||||
//
|
|
||||||
// assert_eq!(
|
|
||||||
// resolve("Folder", "Tags", "\"a\\u0000b\\u0000c\""),
|
|
||||||
// Variant::BinaryString(b"a\0b\0c".to_vec().into()),
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn numbers() {
|
|
||||||
assert_eq!(
|
|
||||||
resolve("Part", "CollisionGroupId", "123"),
|
|
||||||
Variant::Int32(123),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
resolve("Folder", "SourceAssetId", "532413"),
|
|
||||||
Variant::Int64(532413),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(resolve("Part", "Transparency", "1"), Variant::Float32(1.0));
|
|
||||||
assert_eq!(resolve("NumberValue", "Value", "1"), Variant::Float64(1.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn vectors() {
|
|
||||||
assert_eq!(
|
|
||||||
resolve("ParticleEmitter", "SpreadAngle", "[1, 2]"),
|
|
||||||
Variant::Vector2(Vector2::new(1.0, 2.0)),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
resolve("Part", "Position", "[4, 5, 6]"),
|
|
||||||
Variant::Vector3(Vector3::new(4.0, 5.0, 6.0)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn colors() {
|
|
||||||
assert_eq!(
|
|
||||||
resolve("Part", "Color", "[1, 1, 1]"),
|
|
||||||
Variant::Color3(Color3::new(1.0, 1.0, 1.0)),
|
|
||||||
);
|
|
||||||
|
|
||||||
// There aren't any user-facing Color3uint8 properties. If there are
|
|
||||||
// some, we should treat them the same in the future.
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn enums() {
|
|
||||||
assert_eq!(
|
|
||||||
resolve("Lighting", "Technology", "\"Voxel\""),
|
|
||||||
Variant::Enum(Enum::from_u32(1)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
[tools]
|
[tools]
|
||||||
rojo = { source = "rojo-rbx/rojo", version = "7.1.1" }
|
rojo = { source = "rojo-rbx/rojo", version = "7.2.1" }
|
||||||
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }
|
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }
|
||||||
selene = { source = "Kampfkarren/selene", version = "0.17.0" }
|
selene = { source = "Kampfkarren/selene", version = "0.20.0" }
|
||||||
|
wally = { source = "UpliftGames/wally", version = "0.3.1"}
|
||||||
|
|||||||
@@ -1,33 +1,25 @@
|
|||||||
{
|
{
|
||||||
"name": "Rojo",
|
"name": "Rojo",
|
||||||
"tree": {
|
"tree": {
|
||||||
"$className": "Folder",
|
"$className": "Folder",
|
||||||
"Plugin": {
|
"Plugin": {
|
||||||
"$path": "src"
|
"$path": "src"
|
||||||
},
|
},
|
||||||
"Log": {
|
"Packages": {
|
||||||
"$path": "log"
|
"$path": "Packages",
|
||||||
},
|
|
||||||
"Http": {
|
"Log": {
|
||||||
"$path": "http"
|
"$path": "log"
|
||||||
},
|
},
|
||||||
"Fmt": {
|
"Http": {
|
||||||
"$path": "fmt"
|
"$path": "http"
|
||||||
},
|
},
|
||||||
"RbxDom": {
|
"Fmt": {
|
||||||
"$path": "rbx_dom_lua"
|
"$path": "fmt"
|
||||||
},
|
},
|
||||||
"Roact": {
|
"RbxDom": {
|
||||||
"$path": "modules/roact/src"
|
"$path": "rbx_dom_lua"
|
||||||
},
|
}
|
||||||
"Promise": {
|
}
|
||||||
"$path": "modules/promise/lib"
|
}
|
||||||
},
|
}
|
||||||
"t": {
|
|
||||||
"$path": "modules/t/lib"
|
|
||||||
},
|
|
||||||
"Flipper": {
|
|
||||||
"$path": "modules/flipper/src"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Submodule plugin/modules/flipper deleted from 4cf7a03cb6
Submodule plugin/modules/promise deleted from 7fb09d103f
Submodule plugin/modules/roact deleted from f7d2f1ce1d
Submodule plugin/modules/t deleted from f643b50682
Submodule plugin/modules/testez deleted from 25d957d4d5
@@ -23,8 +23,45 @@ end
|
|||||||
local ALL_AXES = {"X", "Y", "Z"}
|
local ALL_AXES = {"X", "Y", "Z"}
|
||||||
local ALL_FACES = {"Right", "Top", "Back", "Left", "Bottom", "Front"}
|
local ALL_FACES = {"Right", "Top", "Back", "Left", "Bottom", "Front"}
|
||||||
|
|
||||||
|
local EncodedValue = {}
|
||||||
|
|
||||||
local types
|
local types
|
||||||
types = {
|
types = {
|
||||||
|
Attributes = {
|
||||||
|
fromPod = function(pod)
|
||||||
|
local output = {}
|
||||||
|
|
||||||
|
for key, value in pairs(pod) do
|
||||||
|
local ok, result = EncodedValue.decode(value)
|
||||||
|
|
||||||
|
if ok then
|
||||||
|
output[key] = result
|
||||||
|
else
|
||||||
|
local warning = ("Could not decode attribute value of type %q: %s"):format(typeof(value), tostring(result))
|
||||||
|
warn(warning)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return output
|
||||||
|
end,
|
||||||
|
toPod = function(roblox)
|
||||||
|
local output = {}
|
||||||
|
|
||||||
|
for key, value in pairs(roblox) do
|
||||||
|
local ok, result = EncodedValue.encodeNaive(value)
|
||||||
|
|
||||||
|
if ok then
|
||||||
|
output[key] = result
|
||||||
|
else
|
||||||
|
local warning = ("Could not encode attribute value of type %q: %s"):format(typeof(value), tostring(result))
|
||||||
|
warn(warning)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return output
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
Axes = {
|
Axes = {
|
||||||
fromPod = function(pod)
|
fromPod = function(pod)
|
||||||
local axes = {}
|
local axes = {}
|
||||||
@@ -433,8 +470,6 @@ types = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
local EncodedValue = {}
|
|
||||||
|
|
||||||
function EncodedValue.decode(encodedValue)
|
function EncodedValue.decode(encodedValue)
|
||||||
local ty, value = next(encodedValue)
|
local ty, value = next(encodedValue)
|
||||||
|
|
||||||
@@ -459,4 +494,19 @@ function EncodedValue.encode(rbxValue, propertyType)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local propertyTypeRenames = {
|
||||||
|
number = "Float64",
|
||||||
|
boolean = "Bool",
|
||||||
|
string = "String",
|
||||||
|
}
|
||||||
|
|
||||||
|
function EncodedValue.encodeNaive(rbxValue)
|
||||||
|
local propertyType = typeof(rbxValue)
|
||||||
|
if propertyTypeRenames[propertyType] ~= nil then
|
||||||
|
propertyType = propertyTypeRenames[propertyType]
|
||||||
|
end
|
||||||
|
|
||||||
|
return EncodedValue.encode(rbxValue, propertyType)
|
||||||
|
end
|
||||||
|
|
||||||
return EncodedValue
|
return EncodedValue
|
||||||
|
|||||||
@@ -1,4 +1,73 @@
|
|||||||
{
|
{
|
||||||
|
"Attributes": {
|
||||||
|
"value": {
|
||||||
|
"Attributes": {
|
||||||
|
"TestBool": {
|
||||||
|
"Bool": true
|
||||||
|
},
|
||||||
|
"TestBrickColor": {
|
||||||
|
"BrickColor": 24
|
||||||
|
},
|
||||||
|
"TestColor3": {
|
||||||
|
"Color3": [
|
||||||
|
1.0,
|
||||||
|
0.5,
|
||||||
|
0.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestNumber": {
|
||||||
|
"Float64": 1337.0
|
||||||
|
},
|
||||||
|
"TestRect": {
|
||||||
|
"Rect": [
|
||||||
|
[
|
||||||
|
1.0,
|
||||||
|
2.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.0,
|
||||||
|
4.0
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestString": {
|
||||||
|
"String": "Test"
|
||||||
|
},
|
||||||
|
"TestUDim": {
|
||||||
|
"UDim": [
|
||||||
|
1.0,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestUDim2": {
|
||||||
|
"UDim2": [
|
||||||
|
[
|
||||||
|
1.0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3.0,
|
||||||
|
4
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestVector2": {
|
||||||
|
"Vector2": [
|
||||||
|
1.0,
|
||||||
|
2.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TestVector3": {
|
||||||
|
"Vector3": [
|
||||||
|
1.0,
|
||||||
|
2.0,
|
||||||
|
3.0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ty": "Attributes"
|
||||||
|
},
|
||||||
"Axes": {
|
"Axes": {
|
||||||
"value": {
|
"value": {
|
||||||
"Axes": [
|
"Axes": [
|
||||||
|
|||||||
@@ -5,6 +5,26 @@ local CollectionService = game:GetService("CollectionService")
|
|||||||
-- The reflection database refers to these as having scriptability = "Custom"
|
-- The reflection database refers to these as having scriptability = "Custom"
|
||||||
return {
|
return {
|
||||||
Instance = {
|
Instance = {
|
||||||
|
Attributes = {
|
||||||
|
read = function(instance)
|
||||||
|
return true, instance:GetAttributes()
|
||||||
|
end,
|
||||||
|
write = function(instance, _, value)
|
||||||
|
local existing = instance:GetAttributes()
|
||||||
|
|
||||||
|
for key, attr in pairs(value) do
|
||||||
|
instance:SetAttribute(key, attr)
|
||||||
|
end
|
||||||
|
|
||||||
|
for key in pairs(existing) do
|
||||||
|
if value[key] == nil then
|
||||||
|
instance:SetAttribute(key, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
},
|
||||||
Tags = {
|
Tags = {
|
||||||
read = function(instance)
|
read = function(instance)
|
||||||
return true, CollectionService:GetTags(instance)
|
return true, CollectionService:GetTags(instance)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
|
||||||
local TestEZ = require(ReplicatedStorage.TestEZ)
|
local TestEZ = require(ReplicatedStorage.Packages.TestEZ)
|
||||||
|
|
||||||
local Rojo = ReplicatedStorage.Rojo
|
local Rojo = ReplicatedStorage.Rojo
|
||||||
|
|
||||||
@@ -16,4 +16,4 @@ require(Rojo.Plugin.runTests)(TestEZ)
|
|||||||
|
|
||||||
if setDevSettings then
|
if setDevSettings then
|
||||||
DevSettings:resetValues()
|
DevSettings:resetValues()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
local Http = require(script.Parent.Parent.Http)
|
local Packages = script.Parent.Parent.Packages
|
||||||
local Log = require(script.Parent.Parent.Log)
|
local Http = require(Packages.Http)
|
||||||
local Promise = require(script.Parent.Parent.Promise)
|
local Log = require(Packages.Log)
|
||||||
|
local Promise = require(Packages.Promise)
|
||||||
|
|
||||||
local Config = require(script.Parent.Config)
|
local Config = require(script.Parent.Config)
|
||||||
local Types = require(script.Parent.Types)
|
local Types = require(script.Parent.Types)
|
||||||
@@ -85,7 +86,7 @@ local ApiContext = {}
|
|||||||
ApiContext.__index = ApiContext
|
ApiContext.__index = ApiContext
|
||||||
|
|
||||||
function ApiContext.new(baseUrl)
|
function ApiContext.new(baseUrl)
|
||||||
assert(type(baseUrl) == "string")
|
assert(type(baseUrl) == "string", "baseUrl must be a string")
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
__baseUrl = baseUrl,
|
__baseUrl = baseUrl,
|
||||||
@@ -248,4 +249,4 @@ function ApiContext:open(id)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return ApiContext
|
return ApiContext
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
@@ -38,4 +39,4 @@ local function BorderedContainer(props)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return BorderedContainer
|
return BorderedContainer
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
local Flipper = require(Rojo.Flipper)
|
local Flipper = require(Packages.Flipper)
|
||||||
|
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
@@ -93,4 +94,4 @@ function Checkbox:render()
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Checkbox
|
return Checkbox
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
@@ -52,4 +53,4 @@ local function Header(props)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Header
|
return Header
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
local Flipper = require(Rojo.Flipper)
|
local Flipper = require(Packages.Flipper)
|
||||||
|
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||||
@@ -76,4 +77,4 @@ function IconButton:render()
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return IconButton
|
return IconButton
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
@@ -39,4 +40,4 @@ local function ScrollingFrame(props)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return ScrollingFrame
|
return ScrollingFrame
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
@@ -26,4 +27,4 @@ local function SlicedImage(props)
|
|||||||
}, props[Roact.Children])
|
}, props[Roact.Children])
|
||||||
end
|
end
|
||||||
|
|
||||||
return SlicedImage
|
return SlicedImage
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ local RunService = game:GetService("RunService")
|
|||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
@@ -63,4 +64,4 @@ function Spinner:willUnmount()
|
|||||||
self.stepper:Disconnect()
|
self.stepper:Disconnect()
|
||||||
end
|
end
|
||||||
|
|
||||||
return Spinner
|
return Spinner
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local Dictionary = require(Plugin.Dictionary)
|
local Dictionary = require(Plugin.Dictionary)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local StudioPluginContext = Roact.createContext(nil)
|
local StudioPluginContext = Roact.createContext(nil)
|
||||||
|
|
||||||
return StudioPluginContext
|
return StudioPluginContext
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local Dictionary = require(Plugin.Dictionary)
|
local Dictionary = require(Plugin.Dictionary)
|
||||||
|
|
||||||
@@ -81,4 +82,4 @@ local function StudioPluginGuiWrapper(props)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return StudioPluginGuiWrapper
|
return StudioPluginGuiWrapper
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local Dictionary = require(Plugin.Dictionary)
|
local Dictionary = require(Plugin.Dictionary)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local Dictionary = require(Plugin.Dictionary)
|
local Dictionary = require(Plugin.Dictionary)
|
||||||
|
|
||||||
@@ -42,4 +43,4 @@ local function StudioToolbarWrapper(props)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return StudioToolbarWrapper
|
return StudioToolbarWrapper
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local StudioToolbarContext = Roact.createContext(nil)
|
local StudioToolbarContext = Roact.createContext(nil)
|
||||||
|
|
||||||
return StudioToolbarContext
|
return StudioToolbarContext
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ local TextService = game:GetService("TextService")
|
|||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
local Flipper = require(Rojo.Flipper)
|
local Flipper = require(Packages.Flipper)
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
@@ -134,4 +135,4 @@ function TextButton:render()
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return TextButton
|
return TextButton
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
local Flipper = require(Rojo.Flipper)
|
local Flipper = require(Packages.Flipper)
|
||||||
|
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||||
@@ -142,4 +143,4 @@ function TouchRipple:render()
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return TouchRipple
|
return TouchRipple
|
||||||
|
|||||||
199
plugin/src/App/Notifications.lua
Normal file
199
plugin/src/App/Notifications.lua
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
local TextService = game:GetService("TextService")
|
||||||
|
local StudioService = game:GetService("StudioService")
|
||||||
|
|
||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
|
local Roact = require(Packages.Roact)
|
||||||
|
local Flipper = require(Packages.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
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
local Flipper = require(Rojo.Flipper)
|
local Flipper = require(Packages.Flipper)
|
||||||
|
|
||||||
local Dictionary = require(Plugin.Dictionary)
|
local Dictionary = require(Plugin.Dictionary)
|
||||||
|
|
||||||
@@ -67,4 +68,4 @@ function Page:didUpdate(lastProps)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return Page
|
return Page
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
--[[
|
|
||||||
Persistent plugin settings that can be accessed via Roact context.
|
|
||||||
]]
|
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
|
||||||
|
|
||||||
local defaultSettings = {
|
|
||||||
openScriptsExternally = false,
|
|
||||||
twoWaySync = false,
|
|
||||||
}
|
|
||||||
|
|
||||||
local Settings = {}
|
|
||||||
Settings.__index = Settings
|
|
||||||
|
|
||||||
function Settings.fromPlugin(plugin)
|
|
||||||
local values = {}
|
|
||||||
|
|
||||||
for name, defaultValue in pairs(defaultSettings) do
|
|
||||||
local savedValue = plugin:GetSetting("Rojo_" .. name)
|
|
||||||
|
|
||||||
if savedValue == nil then
|
|
||||||
plugin:SetSetting("Rojo_" .. name, defaultValue)
|
|
||||||
values[name] = defaultValue
|
|
||||||
else
|
|
||||||
values[name] = savedValue
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return setmetatable({
|
|
||||||
__values = values,
|
|
||||||
__plugin = plugin,
|
|
||||||
__updateListeners = {},
|
|
||||||
}, Settings)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Settings:get(name)
|
|
||||||
if defaultSettings[name] == nil then
|
|
||||||
error("Invalid setings name " .. tostring(name), 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
return self.__values[name]
|
|
||||||
end
|
|
||||||
|
|
||||||
function Settings:set(name, value)
|
|
||||||
self.__plugin:SetSetting("Rojo_" .. name, value)
|
|
||||||
self.__values[name] = value
|
|
||||||
|
|
||||||
for callback in pairs(self.__updateListeners) do
|
|
||||||
callback(name, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Settings:onUpdate(newCallback)
|
|
||||||
local newListeners = {}
|
|
||||||
for callback in pairs(self.__updateListeners) do
|
|
||||||
newListeners[callback] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
newListeners[newCallback] = true
|
|
||||||
self.__updateListeners = newListeners
|
|
||||||
|
|
||||||
return function()
|
|
||||||
local newListeners = {}
|
|
||||||
for callback in pairs(self.__updateListeners) do
|
|
||||||
if callback ~= newCallback then
|
|
||||||
newListeners[callback] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self.__updateListeners = newListeners
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local Context = Roact.createContext(nil)
|
|
||||||
|
|
||||||
local StudioProvider = Roact.Component:extend("StudioProvider")
|
|
||||||
|
|
||||||
function StudioProvider:init()
|
|
||||||
self.settings = Settings.fromPlugin(self.props.plugin)
|
|
||||||
end
|
|
||||||
|
|
||||||
function StudioProvider:render()
|
|
||||||
return Roact.createElement(Context.Provider, {
|
|
||||||
value = self.settings,
|
|
||||||
}, self.props[Roact.Children])
|
|
||||||
end
|
|
||||||
|
|
||||||
local InternalConsumer = Roact.Component:extend("InternalConsumer")
|
|
||||||
|
|
||||||
function InternalConsumer:render()
|
|
||||||
return self.props.render(self.props.settings)
|
|
||||||
end
|
|
||||||
|
|
||||||
function InternalConsumer:didMount()
|
|
||||||
self.disconnect = self.props.settings:onUpdate(function()
|
|
||||||
-- Trigger a dummy state update to update the settings consumer.
|
|
||||||
self:setState({})
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function InternalConsumer:willUnmount()
|
|
||||||
self.disconnect()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function with(callback)
|
|
||||||
return Roact.createElement(Context.Consumer, {
|
|
||||||
render = function(settings)
|
|
||||||
return Roact.createElement(InternalConsumer, {
|
|
||||||
settings = settings,
|
|
||||||
render = callback,
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
StudioProvider = StudioProvider,
|
|
||||||
with = with,
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
@@ -12,6 +13,26 @@ local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
|||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local AGE_UNITS = { {31556909, "year"}, {2629743, "month"}, {604800, "week"}, {86400, "day"}, {3600, "hour"}, {60, "minute"}, }
|
||||||
|
function timeSinceText(elapsed: number): string
|
||||||
|
if elapsed < 3 then
|
||||||
|
return "just now"
|
||||||
|
end
|
||||||
|
|
||||||
|
local ageText = string.format("%d seconds ago", elapsed)
|
||||||
|
|
||||||
|
for _,UnitData in ipairs(AGE_UNITS) do
|
||||||
|
local UnitSeconds, UnitName = UnitData[1], UnitData[2]
|
||||||
|
if elapsed > UnitSeconds then
|
||||||
|
local c = math.floor(elapsed/UnitSeconds)
|
||||||
|
ageText = string.format("%d %s%s ago", c, UnitName, c>1 and "s" or "")
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ageText
|
||||||
|
end
|
||||||
|
|
||||||
local function ConnectionDetails(props)
|
local function ConnectionDetails(props)
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
return e(BorderedContainer, {
|
return e(BorderedContainer, {
|
||||||
@@ -82,33 +103,59 @@ end
|
|||||||
local ConnectedPage = Roact.Component:extend("ConnectedPage")
|
local ConnectedPage = Roact.Component:extend("ConnectedPage")
|
||||||
|
|
||||||
function ConnectedPage:render()
|
function ConnectedPage:render()
|
||||||
return Roact.createFragment({
|
return Theme.with(function(theme)
|
||||||
Header = e(Header, {
|
return Roact.createFragment({
|
||||||
transparency = self.props.transparency,
|
Header = e(Header, {
|
||||||
layoutOrder = 1,
|
transparency = self.props.transparency,
|
||||||
}),
|
layoutOrder = 1,
|
||||||
|
}),
|
||||||
|
|
||||||
ConnectionDetails = e(ConnectionDetails, {
|
ConnectionDetails = e(ConnectionDetails, {
|
||||||
projectName = self.state.projectName,
|
projectName = self.state.projectName,
|
||||||
address = self.state.address,
|
address = self.state.address,
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 2,
|
layoutOrder = 2,
|
||||||
|
|
||||||
onDisconnect = self.props.onDisconnect,
|
onDisconnect = self.props.onDisconnect,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Layout = e("UIListLayout", {
|
Info = e("TextLabel", {
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
Text = self.props.patchInfo:map(function(info)
|
||||||
FillDirection = Enum.FillDirection.Vertical,
|
return string.format(
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
"<i>Synced %d change%s %s</i>",
|
||||||
Padding = UDim.new(0, 10),
|
info.changes,
|
||||||
}),
|
info.changes == 1 and "" or "s",
|
||||||
|
timeSinceText(os.time() - info.timestamp)
|
||||||
|
)
|
||||||
|
end),
|
||||||
|
Font = Enum.Font.Gotham,
|
||||||
|
TextSize = 14,
|
||||||
|
TextWrapped = true,
|
||||||
|
RichText = true,
|
||||||
|
TextColor3 = theme.Header.VersionColor,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
TextYAlignment = Enum.TextYAlignment.Top,
|
||||||
|
TextTransparency = self.props.transparency,
|
||||||
|
|
||||||
Padding = e("UIPadding", {
|
Size = UDim2.new(1, 0, 0, 28),
|
||||||
PaddingLeft = UDim.new(0, 20),
|
|
||||||
PaddingRight = UDim.new(0, 20),
|
LayoutOrder = 3,
|
||||||
}),
|
BackgroundTransparency = 1,
|
||||||
})
|
}),
|
||||||
|
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||||
|
FillDirection = Enum.FillDirection.Vertical,
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
Padding = UDim.new(0, 10),
|
||||||
|
}),
|
||||||
|
|
||||||
|
Padding = e("UIPadding", {
|
||||||
|
PaddingLeft = UDim.new(0, 20),
|
||||||
|
PaddingRight = UDim.new(0, 20),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ConnectedPage.getDerivedStateFromProps(props)
|
function ConnectedPage.getDerivedStateFromProps(props)
|
||||||
@@ -122,4 +169,4 @@ function ConnectedPage.getDerivedStateFromProps(props)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
return ConnectedPage
|
return ConnectedPage
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local Spinner = require(Plugin.App.Components.Spinner)
|
local Spinner = require(Plugin.App.Components.Spinner)
|
||||||
|
|
||||||
@@ -17,4 +18,4 @@ function ConnectingPage:render()
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return ConnectingPage
|
return ConnectingPage
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ local TextService = game:GetService("TextService")
|
|||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
|
|
||||||
@@ -56,8 +57,16 @@ function Error:render()
|
|||||||
end,
|
end,
|
||||||
}, {
|
}, {
|
||||||
ErrorMessage = Theme.with(function(theme)
|
ErrorMessage = Theme.with(function(theme)
|
||||||
return e("TextLabel", {
|
return e("TextBox", {
|
||||||
|
[Roact.Event.InputBegan] = function(rbx, input)
|
||||||
|
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end
|
||||||
|
rbx.SelectionStart = 0
|
||||||
|
rbx.CursorPosition = #rbx.Text+1
|
||||||
|
end,
|
||||||
|
|
||||||
|
|
||||||
Text = self.props.errorMessage,
|
Text = self.props.errorMessage,
|
||||||
|
TextEditable = false,
|
||||||
Font = Enum.Font.Code,
|
Font = Enum.Font.Code,
|
||||||
TextSize = 16,
|
TextSize = 16,
|
||||||
TextColor3 = theme.ErrorColor,
|
TextColor3 = theme.ErrorColor,
|
||||||
@@ -65,10 +74,9 @@ function Error:render()
|
|||||||
TextYAlignment = Enum.TextYAlignment.Top,
|
TextYAlignment = Enum.TextYAlignment.Top,
|
||||||
TextTransparency = self.props.transparency,
|
TextTransparency = self.props.transparency,
|
||||||
TextWrapped = true,
|
TextWrapped = true,
|
||||||
|
ClearTextOnFocus = false,
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.new(1, 0, 1, 0),
|
||||||
})
|
})
|
||||||
end),
|
end),
|
||||||
|
|
||||||
@@ -150,4 +158,4 @@ function ErrorPage.getDerivedStateFromProps(props)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
return ErrorPage
|
return ErrorPage
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local Config = require(Plugin.Config)
|
local Config = require(Plugin.Config)
|
||||||
|
|
||||||
@@ -148,4 +149,4 @@ function NotConnectedPage:render()
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return NotConnectedPage
|
return NotConnectedPage
|
||||||
|
|||||||
@@ -1,230 +0,0 @@
|
|||||||
local TextService = game:GetService("TextService")
|
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
|
||||||
local Plugin = Rojo.Plugin
|
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
|
||||||
|
|
||||||
local Assets = require(Plugin.Assets)
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
|
||||||
local PluginSettings = require(Plugin.App.PluginSettings)
|
|
||||||
|
|
||||||
local Checkbox = require(Plugin.App.Components.Checkbox)
|
|
||||||
local IconButton = require(Plugin.App.Components.IconButton)
|
|
||||||
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
|
||||||
|
|
||||||
local e = Roact.createElement
|
|
||||||
|
|
||||||
local DIVIDER_FADE_SIZE = 0.1
|
|
||||||
|
|
||||||
local function getTextBounds(text, textSize, font, lineHeight, bounds)
|
|
||||||
local textBounds = TextService:GetTextSize(text, textSize, font, bounds)
|
|
||||||
|
|
||||||
local lineCount = textBounds.Y / textSize
|
|
||||||
local lineHeightAbsolute = textSize * lineHeight
|
|
||||||
|
|
||||||
return Vector2.new(textBounds.X, lineHeightAbsolute * lineCount - (lineHeightAbsolute - textSize))
|
|
||||||
end
|
|
||||||
|
|
||||||
local function Navbar(props)
|
|
||||||
return Theme.with(function(theme)
|
|
||||||
theme = theme.Settings.Navbar
|
|
||||||
|
|
||||||
return e("Frame", {
|
|
||||||
Size = UDim2.new(1, 0, 0, 46),
|
|
||||||
LayoutOrder = props.layoutOrder,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
}, {
|
|
||||||
Back = e(IconButton, {
|
|
||||||
icon = Assets.Images.Icons.Back,
|
|
||||||
iconSize = 24,
|
|
||||||
color = theme.BackButtonColor,
|
|
||||||
transparency = props.transparency,
|
|
||||||
|
|
||||||
position = UDim2.new(0, 0, 0.5, 0),
|
|
||||||
anchorPoint = Vector2.new(0, 0.5),
|
|
||||||
|
|
||||||
onClick = props.onBack,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Text = e("TextLabel", {
|
|
||||||
Text = "Settings",
|
|
||||||
Font = Enum.Font.Gotham,
|
|
||||||
TextSize = 18,
|
|
||||||
TextColor3 = theme.TextColor,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
|
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
local Setting = Roact.Component:extend("Setting")
|
|
||||||
|
|
||||||
function Setting:init()
|
|
||||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
|
||||||
self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
|
|
||||||
end
|
|
||||||
|
|
||||||
function Setting:render()
|
|
||||||
return Theme.with(function(theme)
|
|
||||||
theme = theme.Settings
|
|
||||||
|
|
||||||
return PluginSettings.with(function(settings)
|
|
||||||
return e("Frame", {
|
|
||||||
Size = self.contentSize:map(function(value)
|
|
||||||
return UDim2.new(1, 0, 0, 20 + value.Y + 20)
|
|
||||||
end),
|
|
||||||
LayoutOrder = self.props.layoutOrder,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
[Roact.Change.AbsoluteSize] = function(object)
|
|
||||||
self.setContainerSize(object.AbsoluteSize)
|
|
||||||
end,
|
|
||||||
}, {
|
|
||||||
Checkbox = e(Checkbox, {
|
|
||||||
active = settings:get(self.props.id),
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
position = UDim2.new(1, 0, 0.5, 0),
|
|
||||||
anchorPoint = Vector2.new(1, 0.5),
|
|
||||||
onClick = function()
|
|
||||||
local currentValue = settings:get(self.props.id)
|
|
||||||
settings:set(self.props.id, not currentValue)
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Text = e("Frame", {
|
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
}, {
|
|
||||||
Name = e("TextLabel", {
|
|
||||||
Text = self.props.name,
|
|
||||||
Font = Enum.Font.GothamBold,
|
|
||||||
TextSize = 17,
|
|
||||||
TextColor3 = theme.Setting.NameColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = self.props.transparency,
|
|
||||||
|
|
||||||
Size = UDim2.new(1, 0, 0, 17),
|
|
||||||
|
|
||||||
LayoutOrder = 1,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Description = e("TextLabel", {
|
|
||||||
Text = self.props.description,
|
|
||||||
Font = Enum.Font.Gotham,
|
|
||||||
LineHeight = 1.2,
|
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = theme.Setting.DescriptionColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = self.props.transparency,
|
|
||||||
TextWrapped = true,
|
|
||||||
|
|
||||||
Size = self.containerSize:map(function(value)
|
|
||||||
local textBounds = getTextBounds(
|
|
||||||
self.props.description, 14, Enum.Font.Gotham, 1.2,
|
|
||||||
Vector2.new(value.X - 50, math.huge)
|
|
||||||
)
|
|
||||||
return UDim2.new(1, -50, 0, textBounds.Y)
|
|
||||||
end),
|
|
||||||
|
|
||||||
LayoutOrder = 2,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Layout = e("UIListLayout", {
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
FillDirection = Enum.FillDirection.Vertical,
|
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
||||||
Padding = UDim.new(0, 6),
|
|
||||||
|
|
||||||
[Roact.Change.AbsoluteContentSize] = function(object)
|
|
||||||
self.setContentSize(object.AbsoluteContentSize)
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Padding = e("UIPadding", {
|
|
||||||
PaddingTop = UDim.new(0, 20),
|
|
||||||
PaddingBottom = UDim.new(0, 20),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
|
|
||||||
Divider = e("Frame", {
|
|
||||||
BackgroundColor3 = theme.DividerColor,
|
|
||||||
BackgroundTransparency = self.props.transparency,
|
|
||||||
Size = UDim2.new(1, 0, 0, 1),
|
|
||||||
BorderSizePixel = 0,
|
|
||||||
}, {
|
|
||||||
Gradient = e("UIGradient", {
|
|
||||||
Transparency = NumberSequence.new({
|
|
||||||
NumberSequenceKeypoint.new(0, 1),
|
|
||||||
NumberSequenceKeypoint.new(DIVIDER_FADE_SIZE, 0),
|
|
||||||
NumberSequenceKeypoint.new(1 - DIVIDER_FADE_SIZE, 0),
|
|
||||||
NumberSequenceKeypoint.new(1, 1),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
local SettingsPage = Roact.Component:extend("SettingsPage")
|
|
||||||
|
|
||||||
function SettingsPage:init()
|
|
||||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
|
||||||
end
|
|
||||||
|
|
||||||
function SettingsPage:render()
|
|
||||||
return Theme.with(function(theme)
|
|
||||||
theme = theme.Settings
|
|
||||||
|
|
||||||
return e(ScrollingFrame, {
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
|
||||||
contentSize = self.contentSize,
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
}, {
|
|
||||||
Navbar = e(Navbar, {
|
|
||||||
onBack = self.props.onBack,
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
layoutOrder = 0,
|
|
||||||
}),
|
|
||||||
|
|
||||||
OpenScriptsExternally = e(Setting, {
|
|
||||||
id = "openScriptsExternally",
|
|
||||||
name = "Open Scripts Externally",
|
|
||||||
description = "Attempt to open scripts in an external editor",
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
layoutOrder = 1,
|
|
||||||
}),
|
|
||||||
|
|
||||||
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,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Layout = e("UIListLayout", {
|
|
||||||
FillDirection = Enum.FillDirection.Vertical,
|
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
||||||
|
|
||||||
[Roact.Change.AbsoluteContentSize] = function(object)
|
|
||||||
self.setContentSize(object.AbsoluteContentSize)
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Padding = e("UIPadding", {
|
|
||||||
PaddingLeft = UDim.new(0, 20),
|
|
||||||
PaddingRight = UDim.new(0, 20),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return SettingsPage
|
|
||||||
150
plugin/src/App/StatusPages/Settings/Setting.lua
Normal file
150
plugin/src/App/StatusPages/Settings/Setting.lua
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
local TextService = game:GetService("TextService")
|
||||||
|
|
||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
|
local Settings = require(Plugin.Settings)
|
||||||
|
local Theme = require(Plugin.App.Theme)
|
||||||
|
|
||||||
|
local Checkbox = require(Plugin.App.Components.Checkbox)
|
||||||
|
|
||||||
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local DIVIDER_FADE_SIZE = 0.1
|
||||||
|
|
||||||
|
local function getTextBounds(text, textSize, font, lineHeight, bounds)
|
||||||
|
local textBounds = TextService:GetTextSize(text, textSize, font, bounds)
|
||||||
|
|
||||||
|
local lineCount = textBounds.Y / textSize
|
||||||
|
local lineHeightAbsolute = textSize * lineHeight
|
||||||
|
|
||||||
|
return Vector2.new(textBounds.X, lineHeightAbsolute * lineCount - (lineHeightAbsolute - textSize))
|
||||||
|
end
|
||||||
|
|
||||||
|
local Setting = Roact.Component:extend("Setting")
|
||||||
|
|
||||||
|
function Setting:init()
|
||||||
|
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
||||||
|
self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
|
||||||
|
|
||||||
|
self:setState({
|
||||||
|
setting = Settings:get(self.props.id),
|
||||||
|
})
|
||||||
|
|
||||||
|
self.changedCleanup = Settings:onChanged(self.props.id, function(value)
|
||||||
|
self:setState({
|
||||||
|
setting = value,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Setting:willUnmount()
|
||||||
|
self.changedCleanup()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Setting:render()
|
||||||
|
return Theme.with(function(theme)
|
||||||
|
theme = theme.Settings
|
||||||
|
|
||||||
|
return e("Frame", {
|
||||||
|
Size = self.contentSize:map(function(value)
|
||||||
|
return UDim2.new(1, 0, 0, 20 + value.Y + 20)
|
||||||
|
end),
|
||||||
|
LayoutOrder = self.props.layoutOrder,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
|
||||||
|
[Roact.Change.AbsoluteSize] = function(object)
|
||||||
|
self.setContainerSize(object.AbsoluteSize)
|
||||||
|
end,
|
||||||
|
}, {
|
||||||
|
Checkbox = e(Checkbox, {
|
||||||
|
active = self.state.setting,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
position = UDim2.new(1, 0, 0.5, 0),
|
||||||
|
anchorPoint = Vector2.new(1, 0.5),
|
||||||
|
onClick = function()
|
||||||
|
local currentValue = Settings:get(self.props.id)
|
||||||
|
Settings:set(self.props.id, not currentValue)
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Text = e("Frame", {
|
||||||
|
Size = UDim2.new(1, 0, 1, 0),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
Name = e("TextLabel", {
|
||||||
|
Text = self.props.name,
|
||||||
|
Font = Enum.Font.GothamBold,
|
||||||
|
TextSize = 17,
|
||||||
|
TextColor3 = theme.Setting.NameColor,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
TextTransparency = self.props.transparency,
|
||||||
|
|
||||||
|
Size = UDim2.new(1, 0, 0, 17),
|
||||||
|
|
||||||
|
LayoutOrder = 1,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Description = e("TextLabel", {
|
||||||
|
Text = self.props.description,
|
||||||
|
Font = Enum.Font.Gotham,
|
||||||
|
LineHeight = 1.2,
|
||||||
|
TextSize = 14,
|
||||||
|
TextColor3 = theme.Setting.DescriptionColor,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
TextTransparency = self.props.transparency,
|
||||||
|
TextWrapped = true,
|
||||||
|
|
||||||
|
Size = self.containerSize:map(function(value)
|
||||||
|
local textBounds = getTextBounds(
|
||||||
|
self.props.description, 14, Enum.Font.Gotham, 1.2,
|
||||||
|
Vector2.new(value.X - 50, math.huge)
|
||||||
|
)
|
||||||
|
return UDim2.new(1, -50, 0, textBounds.Y)
|
||||||
|
end),
|
||||||
|
|
||||||
|
LayoutOrder = 2,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||||
|
FillDirection = Enum.FillDirection.Vertical,
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
Padding = UDim.new(0, 6),
|
||||||
|
|
||||||
|
[Roact.Change.AbsoluteContentSize] = function(object)
|
||||||
|
self.setContentSize(object.AbsoluteContentSize)
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Padding = e("UIPadding", {
|
||||||
|
PaddingTop = UDim.new(0, 20),
|
||||||
|
PaddingBottom = UDim.new(0, 20),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
|
Divider = e("Frame", {
|
||||||
|
BackgroundColor3 = theme.DividerColor,
|
||||||
|
BackgroundTransparency = self.props.transparency,
|
||||||
|
Size = UDim2.new(1, 0, 0, 1),
|
||||||
|
BorderSizePixel = 0,
|
||||||
|
}, {
|
||||||
|
Gradient = e("UIGradient", {
|
||||||
|
Transparency = NumberSequence.new({
|
||||||
|
NumberSequenceKeypoint.new(0, 1),
|
||||||
|
NumberSequenceKeypoint.new(DIVIDER_FADE_SIZE, 0),
|
||||||
|
NumberSequenceKeypoint.new(1 - DIVIDER_FADE_SIZE, 0),
|
||||||
|
NumberSequenceKeypoint.new(1, 1),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Setting
|
||||||
122
plugin/src/App/StatusPages/Settings/init.lua
Normal file
122
plugin/src/App/StatusPages/Settings/init.lua
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
|
local Assets = require(Plugin.Assets)
|
||||||
|
local Theme = require(Plugin.App.Theme)
|
||||||
|
|
||||||
|
local IconButton = require(Plugin.App.Components.IconButton)
|
||||||
|
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
||||||
|
local Setting = require(script.Setting)
|
||||||
|
|
||||||
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local function Navbar(props)
|
||||||
|
return Theme.with(function(theme)
|
||||||
|
theme = theme.Settings.Navbar
|
||||||
|
|
||||||
|
return e("Frame", {
|
||||||
|
Size = UDim2.new(1, 0, 0, 46),
|
||||||
|
LayoutOrder = props.layoutOrder,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
Back = e(IconButton, {
|
||||||
|
icon = Assets.Images.Icons.Back,
|
||||||
|
iconSize = 24,
|
||||||
|
color = theme.BackButtonColor,
|
||||||
|
transparency = props.transparency,
|
||||||
|
|
||||||
|
position = UDim2.new(0, 0, 0.5, 0),
|
||||||
|
anchorPoint = Vector2.new(0, 0.5),
|
||||||
|
|
||||||
|
onClick = props.onBack,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Text = e("TextLabel", {
|
||||||
|
Text = "Settings",
|
||||||
|
Font = Enum.Font.Gotham,
|
||||||
|
TextSize = 18,
|
||||||
|
TextColor3 = theme.TextColor,
|
||||||
|
TextTransparency = props.transparency,
|
||||||
|
|
||||||
|
Size = UDim2.new(1, 0, 1, 0),
|
||||||
|
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local SettingsPage = Roact.Component:extend("SettingsPage")
|
||||||
|
|
||||||
|
function SettingsPage:init()
|
||||||
|
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
function SettingsPage:render()
|
||||||
|
return Theme.with(function(theme)
|
||||||
|
theme = theme.Settings
|
||||||
|
|
||||||
|
return e(ScrollingFrame, {
|
||||||
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
|
contentSize = self.contentSize,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
}, {
|
||||||
|
Navbar = e(Navbar, {
|
||||||
|
onBack = self.props.onBack,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
layoutOrder = 0,
|
||||||
|
}),
|
||||||
|
|
||||||
|
OpenScriptsExternally = e(Setting, {
|
||||||
|
id = "openScriptsExternally",
|
||||||
|
name = "Open Scripts Externally",
|
||||||
|
description = "Attempt to open scripts in an external editor",
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
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 = 4,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
FillDirection = Enum.FillDirection.Vertical,
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
|
||||||
|
[Roact.Change.AbsoluteContentSize] = function(object)
|
||||||
|
self.setContentSize(object.AbsoluteContentSize)
|
||||||
|
end,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Padding = e("UIPadding", {
|
||||||
|
PaddingLeft = UDim.new(0, 20),
|
||||||
|
PaddingRight = UDim.new(0, 20),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return SettingsPage
|
||||||
@@ -16,9 +16,10 @@ local function getStudio()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
local Log = require(Rojo.Log)
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
local strict = require(script.Parent.Parent.strict)
|
local strict = require(script.Parent.Parent.strict)
|
||||||
|
|
||||||
@@ -103,6 +104,10 @@ local lightTheme = strict("LightTheme", {
|
|||||||
LogoColor = BRAND_COLOR,
|
LogoColor = BRAND_COLOR,
|
||||||
VersionColor = hexColor(0x727272),
|
VersionColor = hexColor(0x727272),
|
||||||
},
|
},
|
||||||
|
Notification = {
|
||||||
|
InfoColor = hexColor(0x00000),
|
||||||
|
CloseColor = BRAND_COLOR,
|
||||||
|
},
|
||||||
ErrorColor = hexColor(0x000000),
|
ErrorColor = hexColor(0x000000),
|
||||||
ScrollBarColor = hexColor(0x000000),
|
ScrollBarColor = hexColor(0x000000),
|
||||||
})
|
})
|
||||||
@@ -177,6 +182,10 @@ local darkTheme = strict("DarkTheme", {
|
|||||||
LogoColor = BRAND_COLOR,
|
LogoColor = BRAND_COLOR,
|
||||||
VersionColor = hexColor(0xD3D3D3)
|
VersionColor = hexColor(0xD3D3D3)
|
||||||
},
|
},
|
||||||
|
Notification = {
|
||||||
|
InfoColor = hexColor(0xFFFFFF),
|
||||||
|
CloseColor = hexColor(0xFFFFFF),
|
||||||
|
},
|
||||||
ErrorColor = hexColor(0xFFFFFF),
|
ErrorColor = hexColor(0xFFFFFF),
|
||||||
ScrollBarColor = hexColor(0xFFFFFF),
|
ScrollBarColor = hexColor(0xFFFFFF),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
local Log = require(Rojo.Log)
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
local LERP_DATA_TYPES = {
|
local LERP_DATA_TYPES = {
|
||||||
Color3 = true,
|
Color3 = true,
|
||||||
@@ -55,4 +56,4 @@ return {
|
|||||||
mapLerp = mapLerp,
|
mapLerp = mapLerp,
|
||||||
deriveProperty = deriveProperty,
|
deriveProperty = deriveProperty,
|
||||||
blendAlpha = blendAlpha,
|
blendAlpha = blendAlpha,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
|
local Players = game:GetService("Players")
|
||||||
|
local ServerStorage = game:GetService("ServerStorage")
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Rojo.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
local Log = require(Rojo.Log)
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
local Version = require(Plugin.Version)
|
local Version = require(Plugin.Version)
|
||||||
local Config = require(Plugin.Config)
|
local Config = require(Plugin.Config)
|
||||||
|
local Settings = require(Plugin.Settings)
|
||||||
local strict = require(Plugin.strict)
|
local strict = require(Plugin.strict)
|
||||||
local Dictionary = require(Plugin.Dictionary)
|
local Dictionary = require(Plugin.Dictionary)
|
||||||
local ServeSession = require(Plugin.ServeSession)
|
local ServeSession = require(Plugin.ServeSession)
|
||||||
local ApiContext = require(Plugin.ApiContext)
|
local ApiContext = require(Plugin.ApiContext)
|
||||||
local preloadAssets = require(Plugin.preloadAssets)
|
local preloadAssets = require(Plugin.preloadAssets)
|
||||||
|
local soundPlayer = require(Plugin.soundPlayer)
|
||||||
local Theme = require(script.Theme)
|
local Theme = require(script.Theme)
|
||||||
local PluginSettings = require(script.PluginSettings)
|
|
||||||
|
|
||||||
local Page = require(script.Page)
|
local Page = require(script.Page)
|
||||||
|
local Notifications = require(script.Notifications)
|
||||||
local StudioPluginAction = require(script.Components.Studio.StudioPluginAction)
|
local StudioPluginAction = require(script.Components.Studio.StudioPluginAction)
|
||||||
local StudioToolbar = require(script.Components.Studio.StudioToolbar)
|
local StudioToolbar = require(script.Components.Studio.StudioToolbar)
|
||||||
local StudioToggleButton = require(script.Components.Studio.StudioToggleButton)
|
local StudioToggleButton = require(script.Components.Studio.StudioToggleButton)
|
||||||
@@ -40,14 +46,45 @@ function App:init()
|
|||||||
|
|
||||||
self.host, self.setHost = Roact.createBinding("")
|
self.host, self.setHost = Roact.createBinding("")
|
||||||
self.port, self.setPort = Roact.createBinding("")
|
self.port, self.setPort = Roact.createBinding("")
|
||||||
|
self.patchInfo, self.setPatchInfo = Roact.createBinding({
|
||||||
|
changes = 0,
|
||||||
|
timestamp = os.time(),
|
||||||
|
})
|
||||||
|
|
||||||
self:setState({
|
self:setState({
|
||||||
appStatus = AppStatus.NotConnected,
|
appStatus = AppStatus.NotConnected,
|
||||||
guiEnabled = false,
|
guiEnabled = false,
|
||||||
|
notifications = {},
|
||||||
toolbarIcon = Assets.Images.PluginButton,
|
toolbarIcon = Assets.Images.PluginButton,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function App:addNotification(text: string, timeout: number?)
|
||||||
|
if not Settings:get("showNotifications") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local notifications = table.clone(self.state.notifications)
|
||||||
|
table.insert(notifications, {
|
||||||
|
text = text,
|
||||||
|
timestamp = DateTime.now().UnixTimestampMillis,
|
||||||
|
timeout = timeout or 3,
|
||||||
|
})
|
||||||
|
|
||||||
|
self:setState({
|
||||||
|
notifications = notifications,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function App:closeNotification(index: number)
|
||||||
|
local notifications = table.clone(self.state.notifications)
|
||||||
|
table.remove(notifications, index)
|
||||||
|
|
||||||
|
self:setState({
|
||||||
|
notifications = notifications,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
function App:getHostAndPort()
|
function App:getHostAndPort()
|
||||||
local host = self.host:getValue()
|
local host = self.host:getValue()
|
||||||
local port = self.port:getValue()
|
local port = self.port:getValue()
|
||||||
@@ -58,12 +95,70 @@ function App:getHostAndPort()
|
|||||||
return host, port
|
return host, port
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function App:claimSyncLock()
|
||||||
|
if #Players:GetPlayers() == 0 then
|
||||||
|
Log.trace("Skipping sync lock because this isn't in Team Create")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local lock = ServerStorage:FindFirstChild("__Rojo_SessionLock")
|
||||||
|
if not lock then
|
||||||
|
lock = Instance.new("ObjectValue")
|
||||||
|
lock.Name = "__Rojo_SessionLock"
|
||||||
|
lock.Archivable = false
|
||||||
|
lock.Value = Players.LocalPlayer
|
||||||
|
lock.Parent = ServerStorage
|
||||||
|
Log.trace("Created and claimed sync lock")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if lock.Value and lock.Value ~= Players.LocalPlayer and lock.Value.Parent then
|
||||||
|
Log.trace("Found existing sync lock owned by {}", lock.Value)
|
||||||
|
return false, lock.Value
|
||||||
|
end
|
||||||
|
|
||||||
|
lock.Value = Players.LocalPlayer
|
||||||
|
Log.trace("Claimed existing sync lock")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function App:releaseSyncLock()
|
||||||
|
local lock = ServerStorage:FindFirstChild("__Rojo_SessionLock")
|
||||||
|
if not lock then
|
||||||
|
Log.trace("No sync lock found, assumed released")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if lock.Value == Players.LocalPlayer then
|
||||||
|
lock.Value = nil
|
||||||
|
Log.trace("Released sync lock")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Log.trace("Could not relase sync lock because it is owned by {}", lock.Value)
|
||||||
|
end
|
||||||
|
|
||||||
function App:startSession()
|
function App:startSession()
|
||||||
|
local claimedLock, priorOwner = self:claimSyncLock()
|
||||||
|
if not claimedLock then
|
||||||
|
local msg = string.format("Could not sync because user '%s' is already syncing", tostring(priorOwner))
|
||||||
|
|
||||||
|
Log.warn(msg)
|
||||||
|
self:addNotification(msg, 10)
|
||||||
|
self:setState({
|
||||||
|
appStatus = AppStatus.Error,
|
||||||
|
errorMessage = msg,
|
||||||
|
toolbarIcon = Assets.Images.PluginButtonWarning,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local host, port = self:getHostAndPort()
|
local host, port = self:getHostAndPort()
|
||||||
|
|
||||||
local sessionOptions = {
|
local sessionOptions = {
|
||||||
openScriptsExternally = self.props.settings:get("openScriptsExternally"),
|
openScriptsExternally = Settings:get("openScriptsExternally"),
|
||||||
twoWaySync = self.props.settings:get("twoWaySync"),
|
twoWaySync = Settings:get("twoWaySync"),
|
||||||
}
|
}
|
||||||
|
|
||||||
local baseUrl = ("http://%s:%s"):format(host, port)
|
local baseUrl = ("http://%s:%s"):format(host, port)
|
||||||
@@ -75,12 +170,41 @@ function App:startSession()
|
|||||||
twoWaySync = sessionOptions.twoWaySync,
|
twoWaySync = sessionOptions.twoWaySync,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
serveSession:onPatchApplied(function(patch, unapplied)
|
||||||
|
local now = os.time()
|
||||||
|
local changes = 0
|
||||||
|
|
||||||
|
for _, set in patch do
|
||||||
|
for _ in set do
|
||||||
|
changes += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, set in unapplied do
|
||||||
|
for _ in set do
|
||||||
|
changes -= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if changes == 0 then return end
|
||||||
|
|
||||||
|
local old = self.patchInfo:getValue()
|
||||||
|
if now - old.timestamp < 2 then
|
||||||
|
changes += old.changes
|
||||||
|
end
|
||||||
|
|
||||||
|
self.setPatchInfo({
|
||||||
|
changes = changes,
|
||||||
|
timestamp = now,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
serveSession:onStatusChanged(function(status, details)
|
serveSession:onStatusChanged(function(status, details)
|
||||||
if status == ServeSession.Status.Connecting then
|
if status == ServeSession.Status.Connecting then
|
||||||
self:setState({
|
self:setState({
|
||||||
appStatus = AppStatus.Connecting,
|
appStatus = AppStatus.Connecting,
|
||||||
toolbarIcon = Assets.Images.PluginButton,
|
toolbarIcon = Assets.Images.PluginButton,
|
||||||
})
|
})
|
||||||
|
self:addNotification("Connecting to session...")
|
||||||
elseif status == ServeSession.Status.Connected then
|
elseif status == ServeSession.Status.Connected then
|
||||||
local address = ("%s:%s"):format(host, port)
|
local address = ("%s:%s"):format(host, port)
|
||||||
self:setState({
|
self:setState({
|
||||||
@@ -89,10 +213,10 @@ function App:startSession()
|
|||||||
address = address,
|
address = address,
|
||||||
toolbarIcon = Assets.Images.PluginButtonConnected,
|
toolbarIcon = Assets.Images.PluginButtonConnected,
|
||||||
})
|
})
|
||||||
|
self:addNotification(string.format("Connected to session '%s' at %s.", details, address), 5)
|
||||||
Log.info("Connected to session '{}' at {}", details, address)
|
|
||||||
elseif status == ServeSession.Status.Disconnected then
|
elseif status == ServeSession.Status.Disconnected then
|
||||||
self.serveSession = nil
|
self.serveSession = nil
|
||||||
|
self:releaseSyncLock()
|
||||||
|
|
||||||
-- Details being present indicates that this
|
-- Details being present indicates that this
|
||||||
-- disconnection was from an error.
|
-- disconnection was from an error.
|
||||||
@@ -104,13 +228,13 @@ function App:startSession()
|
|||||||
errorMessage = tostring(details),
|
errorMessage = tostring(details),
|
||||||
toolbarIcon = Assets.Images.PluginButtonWarning,
|
toolbarIcon = Assets.Images.PluginButtonWarning,
|
||||||
})
|
})
|
||||||
|
self:addNotification(tostring(details), 10)
|
||||||
else
|
else
|
||||||
self:setState({
|
self:setState({
|
||||||
appStatus = AppStatus.NotConnected,
|
appStatus = AppStatus.NotConnected,
|
||||||
toolbarIcon = Assets.Images.PluginButton,
|
toolbarIcon = Assets.Images.PluginButton,
|
||||||
})
|
})
|
||||||
|
self:addNotification("Disconnected from session.")
|
||||||
Log.info("Disconnected session")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
@@ -118,6 +242,16 @@ function App:startSession()
|
|||||||
serveSession:start()
|
serveSession:start()
|
||||||
|
|
||||||
self.serveSession = serveSession
|
self.serveSession = serveSession
|
||||||
|
|
||||||
|
task.defer(function()
|
||||||
|
while self.serveSession == serveSession do
|
||||||
|
-- Trigger rerender to update timestamp text
|
||||||
|
local patchInfo = table.clone(self.patchInfo:getValue())
|
||||||
|
self.setPatchInfo(patchInfo)
|
||||||
|
local elapsed = os.time() - patchInfo.timestamp
|
||||||
|
task.wait(elapsed < 60 and 1 or elapsed/5)
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function App:endSession()
|
function App:endSession()
|
||||||
@@ -201,6 +335,7 @@ function App:render()
|
|||||||
Connected = createPageElement(AppStatus.Connected, {
|
Connected = createPageElement(AppStatus.Connected, {
|
||||||
projectName = self.state.projectName,
|
projectName = self.state.projectName,
|
||||||
address = self.state.address,
|
address = self.state.address,
|
||||||
|
patchInfo = self.patchInfo,
|
||||||
|
|
||||||
onDisconnect = function()
|
onDisconnect = function()
|
||||||
self:endSession()
|
self:endSession()
|
||||||
@@ -236,6 +371,28 @@ function App:render()
|
|||||||
end),
|
end),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
RojoNotifications = e("ScreenGui", {}, {
|
||||||
|
layout = e("UIListLayout", {
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Bottom,
|
||||||
|
Padding = UDim.new(0, 5),
|
||||||
|
}),
|
||||||
|
padding = e("UIPadding", {
|
||||||
|
PaddingTop = UDim.new(0, 5);
|
||||||
|
PaddingBottom = UDim.new(0, 5);
|
||||||
|
PaddingLeft = UDim.new(0, 5);
|
||||||
|
PaddingRight = 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, {
|
toggleAction = e(StudioPluginAction, {
|
||||||
name = "RojoConnection",
|
name = "RojoConnection",
|
||||||
title = "Rojo: Connect/Disconnect",
|
title = "Rojo: Connect/Disconnect",
|
||||||
@@ -300,14 +457,9 @@ function App:render()
|
|||||||
end
|
end
|
||||||
|
|
||||||
return function(props)
|
return function(props)
|
||||||
return e(PluginSettings.StudioProvider, {
|
local mergedProps = Dictionary.merge(props, {
|
||||||
plugin = props.plugin,
|
soundPlayer = soundPlayer.new(Settings),
|
||||||
}, {
|
|
||||||
App = PluginSettings.with(function(settings)
|
|
||||||
local settingsProps = Dictionary.merge(props, {
|
|
||||||
settings = settings,
|
|
||||||
})
|
|
||||||
return e(App, settingsProps)
|
|
||||||
end),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return e(App, mergedProps)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ local Assets = {
|
|||||||
[500] = "rbxassetid://2609138523"
|
[500] = "rbxassetid://2609138523"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Sounds = {
|
||||||
|
Notification = "rbxassetid://203785492",
|
||||||
|
},
|
||||||
StartSession = "",
|
StartSession = "",
|
||||||
SessionActive = "",
|
SessionActive = "",
|
||||||
Configure = "",
|
Configure = "",
|
||||||
@@ -62,4 +65,4 @@ end
|
|||||||
|
|
||||||
guardForTypos("Assets", Assets)
|
guardForTypos("Assets", Assets)
|
||||||
|
|
||||||
return Assets
|
return Assets
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
of instances) and return the patch.
|
of instances) and return the patch.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local Log = require(script.Parent.Parent.Parent.Log)
|
local Packages = script.Parent.Parent.Parent.Packages
|
||||||
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
local PatchSet = require(script.Parent.Parent.PatchSet)
|
local PatchSet = require(script.Parent.Parent.PatchSet)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
local Log = require(script.Parent.Parent.Parent.Log)
|
local Packages = script.Parent.Parent.Parent.Packages
|
||||||
local RbxDom = require(script.Parent.Parent.Parent.RbxDom)
|
local Log = require(Packages.Log)
|
||||||
|
local RbxDom = require(Packages.RbxDom)
|
||||||
|
|
||||||
local encodeProperty = require(script.Parent.encodeProperty)
|
local encodeProperty = require(script.Parent.encodeProperty)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
local Log = require(script.Parent.Parent.Parent.Log)
|
local Packages = script.Parent.Parent.Parent.Packages
|
||||||
local RbxDom = require(script.Parent.Parent.Parent.RbxDom)
|
local Log = require(Packages.Log)
|
||||||
|
local RbxDom = require(Packages.RbxDom)
|
||||||
|
|
||||||
return function(instance, propertyName, propertyDescriptor)
|
return function(instance, propertyName, propertyDescriptor)
|
||||||
local readSuccess, readResult = propertyDescriptor:read(instance)
|
local readSuccess, readResult = propertyDescriptor:read(instance)
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ local isDevBuild = script.Parent.Parent:FindFirstChild("ROJO_DEV_BUILD") ~= nil
|
|||||||
return strict("Config", {
|
return strict("Config", {
|
||||||
isDevBuild = isDevBuild,
|
isDevBuild = isDevBuild,
|
||||||
codename = "Epiphany",
|
codename = "Epiphany",
|
||||||
version = {7, 1, 1},
|
version = {7, 2, 1},
|
||||||
expectedServerVersionString = "7.0 or newer",
|
expectedServerVersionString = "7.2 or newer",
|
||||||
protocolVersion = 4,
|
protocolVersion = 4,
|
||||||
defaultHost = "localhost",
|
defaultHost = "localhost",
|
||||||
defaultPort = 34872,
|
defaultPort = 34872,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
local RunService = game:GetService("RunService")
|
local RunService = game:GetService("RunService")
|
||||||
|
|
||||||
local Log = require(script.Parent.Parent.Log)
|
local Packages = script.Parent.Parent.Packages
|
||||||
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
A bidirectional map between instance IDs and Roblox instances. It lets us
|
A bidirectional map between instance IDs and Roblox instances. It lets us
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
patch returned from the API.
|
patch returned from the API.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local t = require(script.Parent.Parent.t)
|
local Packages = script.Parent.Parent.Packages
|
||||||
|
local t = require(Packages.t)
|
||||||
|
|
||||||
local Types = require(script.Parent.Types)
|
local Types = require(script.Parent.Types)
|
||||||
|
|
||||||
@@ -181,4 +182,4 @@ function PatchSet.humanSummary(instanceMap, patchSet)
|
|||||||
return table.concat(statements, "\n")
|
return table.concat(statements, "\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
return PatchSet
|
return PatchSet
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
Defines the errors that can be returned by the reconciler.
|
Defines the errors that can be returned by the reconciler.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local Fmt = require(script.Parent.Parent.Parent.Fmt)
|
local Packages = script.Parent.Parent.Parent.Packages
|
||||||
|
local Fmt = require(Packages.Fmt)
|
||||||
|
|
||||||
local Error = {}
|
local Error = {}
|
||||||
|
|
||||||
@@ -34,4 +35,4 @@ function Error:__tostring()
|
|||||||
return Fmt.fmt("Error({}): {:#?}", self.kind, self.details)
|
return Fmt.fmt("Error({}): {:#?}", self.kind, self.details)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Error
|
return Error
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
Patches can come from the server or be generated by the client.
|
Patches can come from the server or be generated by the client.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local Log = require(script.Parent.Parent.Parent.Log)
|
local Packages = script.Parent.Parent.Parent.Packages
|
||||||
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
local PatchSet = require(script.Parent.Parent.PatchSet)
|
local PatchSet = require(script.Parent.Parent.PatchSet)
|
||||||
local Types = require(script.Parent.Parent.Types)
|
local Types = require(script.Parent.Parent.Types)
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
usable by Rojo's reconciler, potentially using RbxDom.
|
usable by Rojo's reconciler, potentially using RbxDom.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local RbxDom = require(script.Parent.Parent.Parent.RbxDom)
|
local Packages = script.Parent.Parent.Parent.Packages
|
||||||
|
local RbxDom = require(Packages.RbxDom)
|
||||||
local Error = require(script.Parent.Error)
|
local Error = require(script.Parent.Error)
|
||||||
|
|
||||||
local function decodeValue(encodedValue, instanceMap)
|
local function decodeValue(encodedValue, instanceMap)
|
||||||
@@ -38,4 +39,4 @@ local function decodeValue(encodedValue, instanceMap)
|
|||||||
return true, decodedValue
|
return true, decodedValue
|
||||||
end
|
end
|
||||||
|
|
||||||
return decodeValue
|
return decodeValue
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
patch that can be later applied.
|
patch that can be later applied.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local Log = require(script.Parent.Parent.Parent.Log)
|
local Packages = script.Parent.Parent.Parent.Packages
|
||||||
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
local invariant = require(script.Parent.Parent.invariant)
|
local invariant = require(script.Parent.Parent.invariant)
|
||||||
local getProperty = require(script.Parent.getProperty)
|
local getProperty = require(script.Parent.getProperty)
|
||||||
local Error = require(script.Parent.Error)
|
local Error = require(script.Parent.Error)
|
||||||
@@ -113,6 +115,16 @@ local function diff(instanceMap, virtualInstances, rootId)
|
|||||||
local childId = instanceMap.fromInstances[childInstance]
|
local childId = instanceMap.fromInstances[childInstance]
|
||||||
|
|
||||||
if childId == nil then
|
if childId == nil then
|
||||||
|
-- pcall to avoid security permission errors
|
||||||
|
local success, skip = pcall(function()
|
||||||
|
-- We don't remove instances that aren't going to be saved anyway,
|
||||||
|
-- such as the Rojo session lock value.
|
||||||
|
return childInstance.Archivable == false
|
||||||
|
end)
|
||||||
|
if success and skip then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
-- This is an existing instance not present in the virtual DOM.
|
-- This is an existing instance not present in the virtual DOM.
|
||||||
-- We can mark it for deletion unless the user has asked us not
|
-- We can mark it for deletion unless the user has asked us not
|
||||||
-- to delete unknown stuff.
|
-- to delete unknown stuff.
|
||||||
@@ -152,4 +164,4 @@ local function diff(instanceMap, virtualInstances, rootId)
|
|||||||
return true, patch
|
return true, patch
|
||||||
end
|
end
|
||||||
|
|
||||||
return diff
|
return diff
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
return function()
|
return function()
|
||||||
local Log = require(script.Parent.Parent.Parent.Log)
|
local Packages = script.Parent.Parent.Parent.Packages
|
||||||
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
local InstanceMap = require(script.Parent.Parent.InstanceMap)
|
local InstanceMap = require(script.Parent.Parent.InstanceMap)
|
||||||
local PatchSet = require(script.Parent.Parent.PatchSet)
|
local PatchSet = require(script.Parent.Parent.PatchSet)
|
||||||
@@ -286,4 +287,4 @@ return function()
|
|||||||
expect(size(patch.added)).to.equal(1)
|
expect(size(patch.added)).to.equal(1)
|
||||||
expect(patch.added["CHILD"]).to.equal(virtualInstances["CHILD"])
|
expect(patch.added["CHILD"]).to.equal(virtualInstances["CHILD"])
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
Attempts to read a property from the given instance.
|
Attempts to read a property from the given instance.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local RbxDom = require(script.Parent.Parent.Parent.RbxDom)
|
local Packages = script.Parent.Parent.Parent.Packages
|
||||||
|
local RbxDom = require(Packages.RbxDom)
|
||||||
local Error = require(script.Parent.Error)
|
local Error = require(script.Parent.Error)
|
||||||
|
|
||||||
local function getProperty(instance, propertyName)
|
local function getProperty(instance, propertyName)
|
||||||
@@ -56,4 +57,4 @@ local function getProperty(instance, propertyName)
|
|||||||
return true, valueOrErr
|
return true, valueOrErr
|
||||||
end
|
end
|
||||||
|
|
||||||
return getProperty
|
return getProperty
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
Attempts to set a property on the given instance.
|
Attempts to set a property on the given instance.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local RbxDom = require(script.Parent.Parent.Parent.RbxDom)
|
local Packages = script.Parent.Parent.Parent.Packages
|
||||||
local Log = require(script.Parent.Parent.Parent.Log)
|
local Log = require(Packages.Log)
|
||||||
|
local RbxDom = require(Packages.RbxDom)
|
||||||
local Error = require(script.Parent.Error)
|
local Error = require(script.Parent.Error)
|
||||||
|
|
||||||
local function setProperty(instance, propertyName, value)
|
local function setProperty(instance, propertyName, value)
|
||||||
@@ -45,4 +46,4 @@ local function setProperty(instance, propertyName, value)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
return setProperty
|
return setProperty
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
local StudioService = game:GetService("StudioService")
|
local StudioService = game:GetService("StudioService")
|
||||||
local RunService = game:GetService("RunService")
|
local RunService = game:GetService("RunService")
|
||||||
|
|
||||||
local Log = require(script.Parent.Parent.Log)
|
local Packages = script.Parent.Parent.Packages
|
||||||
local Fmt = require(script.Parent.Parent.Fmt)
|
local Log = require(Packages.Log)
|
||||||
local t = require(script.Parent.Parent.t)
|
local Fmt = require(Packages.Fmt)
|
||||||
|
local t = require(Packages.t)
|
||||||
|
|
||||||
local ChangeBatcher = require(script.Parent.ChangeBatcher)
|
local ChangeBatcher = require(script.Parent.ChangeBatcher)
|
||||||
local InstanceMap = require(script.Parent.InstanceMap)
|
local InstanceMap = require(script.Parent.InstanceMap)
|
||||||
@@ -94,6 +95,7 @@ function ServeSession.new(options)
|
|||||||
__instanceMap = instanceMap,
|
__instanceMap = instanceMap,
|
||||||
__changeBatcher = changeBatcher,
|
__changeBatcher = changeBatcher,
|
||||||
__statusChangedCallback = nil,
|
__statusChangedCallback = nil,
|
||||||
|
__patchAppliedCallback = nil,
|
||||||
__connections = connections,
|
__connections = connections,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +123,10 @@ function ServeSession:onStatusChanged(callback)
|
|||||||
self.__statusChangedCallback = callback
|
self.__statusChangedCallback = callback
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function ServeSession:onPatchApplied(callback)
|
||||||
|
self.__patchAppliedCallback = callback
|
||||||
|
end
|
||||||
|
|
||||||
function ServeSession:start()
|
function ServeSession:start()
|
||||||
self:__setStatus(Status.Connecting)
|
self:__setStatus(Status.Connecting)
|
||||||
|
|
||||||
@@ -137,7 +143,9 @@ function ServeSession:start()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
:catch(function(err)
|
:catch(function(err)
|
||||||
self:__stopInternal(err)
|
if self.__status ~= Status.Disconnected then
|
||||||
|
self:__stopInternal(err)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -231,6 +239,10 @@ function ServeSession:__initialSync(rootInstanceId)
|
|||||||
Log.warn("Could not apply all changes requested by the Rojo server:\n{}",
|
Log.warn("Could not apply all changes requested by the Rojo server:\n{}",
|
||||||
PatchSet.humanSummary(self.__instanceMap, unappliedPatch))
|
PatchSet.humanSummary(self.__instanceMap, unappliedPatch))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if self.__patchAppliedCallback then
|
||||||
|
pcall(self.__patchAppliedCallback, catchUpPatch, unappliedPatch)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -244,6 +256,10 @@ function ServeSession:__mainSyncLoop()
|
|||||||
Log.warn("Could not apply all changes requested by the Rojo server:\n{}",
|
Log.warn("Could not apply all changes requested by the Rojo server:\n{}",
|
||||||
PatchSet.humanSummary(self.__instanceMap, unappliedPatch))
|
PatchSet.humanSummary(self.__instanceMap, unappliedPatch))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if self.__patchAppliedCallback then
|
||||||
|
pcall(self.__patchAppliedCallback, message, unappliedPatch)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.__status ~= Status.Disconnected then
|
if self.__status ~= Status.Disconnected then
|
||||||
|
|||||||
79
plugin/src/Settings.lua
Normal file
79
plugin/src/Settings.lua
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
--[[
|
||||||
|
Persistent plugin settings.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local plugin = plugin or script:FindFirstAncestorWhichIsA("Plugin")
|
||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
|
local defaultSettings = {
|
||||||
|
openScriptsExternally = false,
|
||||||
|
twoWaySync = false,
|
||||||
|
showNotifications = true,
|
||||||
|
playSounds = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local Settings = {}
|
||||||
|
|
||||||
|
Settings._values = table.clone(defaultSettings)
|
||||||
|
Settings._updateListeners = {}
|
||||||
|
|
||||||
|
if plugin then
|
||||||
|
for name, defaultValue in pairs(Settings._values) do
|
||||||
|
local savedValue = plugin:GetSetting("Rojo_" .. name)
|
||||||
|
|
||||||
|
if savedValue == nil then
|
||||||
|
-- plugin:SetSetting hits disc instead of memory, so it can be slow. Spawn so we don't hang.
|
||||||
|
task.spawn(plugin.SetSetting, plugin, "Rojo_" .. name, defaultValue)
|
||||||
|
Settings._values[name] = defaultValue
|
||||||
|
else
|
||||||
|
Settings._values[name] = savedValue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Log.trace("Loaded settings from plugin store")
|
||||||
|
end
|
||||||
|
|
||||||
|
function Settings:get(name)
|
||||||
|
if defaultSettings[name] == nil then
|
||||||
|
error("Invalid setings name " .. tostring(name), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
return self._values[name]
|
||||||
|
end
|
||||||
|
|
||||||
|
function Settings:set(name, value)
|
||||||
|
self._values[name] = value
|
||||||
|
|
||||||
|
if plugin then
|
||||||
|
-- plugin:SetSetting hits disc instead of memory, so it can be slow. Spawn so we don't hang.
|
||||||
|
task.spawn(plugin.SetSetting, plugin, "Rojo_" .. name, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
if self._updateListeners[name] then
|
||||||
|
for callback in pairs(self._updateListeners[name]) do
|
||||||
|
task.spawn(callback, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Log.trace(string.format("Set setting '%s' to '%s'", name, tostring(value)))
|
||||||
|
end
|
||||||
|
|
||||||
|
function Settings:onChanged(name, callback)
|
||||||
|
local listeners = self._updateListeners[name]
|
||||||
|
if listeners == nil then
|
||||||
|
listeners = {}
|
||||||
|
self._updateListeners[name] = listeners
|
||||||
|
end
|
||||||
|
listeners[callback] = true
|
||||||
|
|
||||||
|
Log.trace(string.format("Added listener for setting '%s' changes", name))
|
||||||
|
|
||||||
|
return function()
|
||||||
|
listeners[callback] = nil
|
||||||
|
Log.trace(string.format("Removed listener for setting '%s' changes", name))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return Settings
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
local t = require(script.Parent.Parent.t)
|
local Packages = script.Parent.Parent.Packages
|
||||||
|
local t = require(Packages.t)
|
||||||
local DevSettings = require(script.Parent.DevSettings)
|
local DevSettings = require(script.Parent.DevSettings)
|
||||||
local strict = require(script.Parent.strict)
|
local strict = require(script.Parent.strict)
|
||||||
|
|
||||||
|
|||||||
@@ -2,23 +2,24 @@ if not plugin then
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local Log = require(script.Parent.Log)
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
|
local Log = require(Packages.Log)
|
||||||
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local DevSettings = require(script.DevSettings)
|
local DevSettings = require(script.DevSettings)
|
||||||
|
local Config = require(script.Config)
|
||||||
|
local App = require(script.App)
|
||||||
|
|
||||||
Log.setLogLevelThunk(function()
|
Log.setLogLevelThunk(function()
|
||||||
return DevSettings:getLogLevel()
|
return DevSettings:getLogLevel()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local Roact = require(script.Parent.Roact)
|
|
||||||
|
|
||||||
local Config = require(script.Config)
|
|
||||||
local App = require(script.App)
|
|
||||||
|
|
||||||
local app = Roact.createElement(App, {
|
local app = Roact.createElement(App, {
|
||||||
plugin = plugin
|
plugin = plugin
|
||||||
})
|
})
|
||||||
local tree = Roact.mount(app, nil, "Rojo UI")
|
local tree = Roact.mount(app, game:GetService("CoreGui"), "Rojo UI")
|
||||||
|
|
||||||
plugin.Unloading:Connect(function()
|
plugin.Unloading:Connect(function()
|
||||||
Roact.unmount(tree)
|
Roact.unmount(tree)
|
||||||
@@ -28,4 +29,4 @@ if Config.isDevBuild then
|
|||||||
local TestEZ = require(script.Parent.TestEZ)
|
local TestEZ = require(script.Parent.TestEZ)
|
||||||
|
|
||||||
require(script.runTests)(TestEZ)
|
require(script.runTests)(TestEZ)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
local Fmt = require(script.Parent.Parent.Fmt)
|
|
||||||
|
local Packages = script.Parent.Parent.Packages
|
||||||
|
local Fmt = require(Packages.Fmt)
|
||||||
|
|
||||||
local Config = require(script.Parent.Config)
|
local Config = require(script.Parent.Config)
|
||||||
|
|
||||||
@@ -26,4 +28,4 @@ else
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return invariant
|
return invariant
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
local ContentProvider = game:GetService("ContentProvider")
|
local ContentProvider = game:GetService("ContentProvider")
|
||||||
|
|
||||||
local Log = require(script.Parent.Parent.Log)
|
local Packages = script.Parent.Parent.Packages
|
||||||
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
local Assets = require(script.Parent.Assets)
|
local Assets = require(script.Parent.Assets)
|
||||||
|
|
||||||
@@ -29,4 +30,4 @@ local function preloadAssets()
|
|||||||
end)()
|
end)()
|
||||||
end
|
end
|
||||||
|
|
||||||
return preloadAssets
|
return preloadAssets
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
return function(TestEZ)
|
return function(TestEZ)
|
||||||
local Rojo = script.Parent.Parent
|
local Rojo = script.Parent.Parent
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
TestEZ.TestBootstrap:run({ Rojo.Plugin, Rojo.Http, Rojo.Log })
|
TestEZ.TestBootstrap:run({ Rojo.Plugin, Packages.Http, Packages.Log, Packages.RbxDom })
|
||||||
end
|
end
|
||||||
|
|||||||
38
plugin/src/soundPlayer.lua
Normal file
38
plugin/src/soundPlayer.lua
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
-- Sounds only play in Edit mode when parented to a plugin widget, for some reason
|
||||||
|
local plugin = plugin or script:FindFirstAncestorWhichIsA("Plugin")
|
||||||
|
local widget = nil
|
||||||
|
if plugin then
|
||||||
|
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"
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
"$path": "default.project.json"
|
"$path": "default.project.json"
|
||||||
},
|
},
|
||||||
|
|
||||||
"TestEZ": {
|
"Packages": {
|
||||||
"$path": "modules/testez"
|
"$path": "DevPackages"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
33
plugin/wally.lock
Normal file
33
plugin/wally.lock
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# This file is automatically @generated by Wally.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
registry = "test"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "evaera/promise"
|
||||||
|
version = "4.0.0"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "osyrisrblx/t"
|
||||||
|
version = "3.0.0"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reselim/flipper"
|
||||||
|
version = "2.0.0"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roblox/roact"
|
||||||
|
version = "1.4.4"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roblox/testez"
|
||||||
|
version = "0.4.1"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rojo-rbx/rojo"
|
||||||
|
version = "7.2.1"
|
||||||
|
dependencies = [["Flipper", "reselim/flipper@2.0.0"], ["Promise", "evaera/promise@4.0.0"], ["Roact", "roblox/roact@1.4.4"], ["t", "osyrisrblx/t@3.0.0"]]
|
||||||
17
plugin/wally.toml
Normal file
17
plugin/wally.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "rojo-rbx/rojo"
|
||||||
|
description = "Rojo enables Roblox developers to use professional-grade software engineering tools"
|
||||||
|
version = "7.2.1"
|
||||||
|
license = "MPL-2.0"
|
||||||
|
authors = ["LPGhatguy (https://lpg.space/)"]
|
||||||
|
registry = "https://github.com/upliftgames/wally-index"
|
||||||
|
realm = "shared"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
Flipper = "reselim/flipper@2.0.0"
|
||||||
|
Promise = "evaera/promise@4.0.0"
|
||||||
|
Roact = "roblox/roact@1.4.4"
|
||||||
|
t = "osyrisrblx/t@3.0.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
TestEZ = "roblox/testez@0.4.1"
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/build.rs
|
||||||
|
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">DQAAAAQAAABCb29sAwEKAAAAQnJpY2tDb2xvcg4BAAAABgAAAENvbG9yMw8AAAAAAAAAAAAAAAANAAAAQ29sb3JTZXF1ZW5jZRkCAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAAAAAACAPwAAgD8AAIA/AACAPwcAAABGbG9hdDMyBQAAAAAHAAAARmxvYXQ2NAYAAAAAAAAAAAsAAABOdW1iZXJSYW5nZRsAAAAAAAAAAA4AAABOdW1iZXJTZXF1ZW5jZRcCAAAAAAAAAAAAAAAAAAAAAAAAAAAAgD8AAAAABAAAAFJlY3QcAAAAAAAAAAAAAAAAAAAAAAQAAABVRGltCQAAAAAAAAAABQAAAFVEaW0yCgAAAAAAAAAAAAAAAAAAAAAHAAAAVmVjdG9yMhAAAAAAAAAAAAcAAABWZWN0b3IzEQAAAAAAAAAAAAAAAA==</BinaryString>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
<Item class="Folder" referent="2">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">Implicit</string>
|
||||||
|
<BinaryString name="AttributesSerialize">AwAAAAQAAABCb29sAwEGAAAATnVtYmVyBgAAAAAAAOA/BgAAAFN0cmluZwIEAAAAVGVzdA==</BinaryString>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
<Item class="Folder" referent="3">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">LegacyExplicit</string>
|
||||||
|
<BinaryString name="AttributesSerialize">AgAAAAUAAABIZWxsbwIFAAAAV29ybGQGAAAAVmVjdG9yEQAAgD8AAABAAABAQA==</BinaryString>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
<Item class="Folder" referent="4">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">LegacyImplicit</string>
|
||||||
|
<BinaryString name="AttributesSerialize">AgAAAAMAAABIZXkCBwAAAEdyYW5kbWEGAAAAVmVjdG9yEQAAgEAAAKBAAADAQA==</BinaryString>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/build.rs
|
||||||
|
assertion_line: 100
|
||||||
|
expression: contents
|
||||||
|
---
|
||||||
|
<roblox version="4">
|
||||||
|
<Item class="LocalizationTable" referent="0">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">init_csv_with_children</string>
|
||||||
|
<string name="Contents">[{"key":"init.csv","values":{}}]</string>
|
||||||
|
</Properties>
|
||||||
|
<Item class="LocalizationTable" referent="1">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">other</string>
|
||||||
|
<string name="Contents">[{"key":"other.csv","values":{}}]</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/build.rs
|
||||||
|
assertion_line: 98
|
||||||
|
expression: contents
|
||||||
|
---
|
||||||
|
<roblox version="4">
|
||||||
|
<Item class="LocalScript" referent="0">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">issue_546</string>
|
||||||
|
<bool name="Disabled">true</bool>
|
||||||
|
<string name="Source">print("Hello, world!")</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
---
|
---
|
||||||
source: tests/tests/build.rs
|
source: tests/tests/build.rs
|
||||||
|
assertion_line: 99
|
||||||
expression: contents
|
expression: contents
|
||||||
|
|
||||||
---
|
---
|
||||||
<roblox version="4">
|
<roblox version="4">
|
||||||
<Item class="Folder" referent="0">
|
<Item class="Folder" referent="0">
|
||||||
<Properties>
|
<Properties>
|
||||||
<string name="Name">weldconstraint</string>
|
<string name="Name">weldconstraint</string>
|
||||||
<BinaryString name="AttributesSerialize">
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
||||||
</BinaryString>
|
|
||||||
<int64 name="SourceAssetId">-1</int64>
|
<int64 name="SourceAssetId">-1</int64>
|
||||||
<BinaryString name="Tags"></BinaryString>
|
<BinaryString name="Tags"></BinaryString>
|
||||||
</Properties>
|
</Properties>
|
||||||
@@ -16,8 +15,7 @@ expression: contents
|
|||||||
<Properties>
|
<Properties>
|
||||||
<string name="Name">A</string>
|
<string name="Name">A</string>
|
||||||
<bool name="Anchored">false</bool>
|
<bool name="Anchored">false</bool>
|
||||||
<BinaryString name="AttributesSerialize">
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
||||||
</BinaryString>
|
|
||||||
<float name="BackParamA">-0.5</float>
|
<float name="BackParamA">-0.5</float>
|
||||||
<float name="BackParamB">0.5</float>
|
<float name="BackParamB">0.5</float>
|
||||||
<token name="BackSurface">0</token>
|
<token name="BackSurface">0</token>
|
||||||
@@ -108,8 +106,7 @@ expression: contents
|
|||||||
<Item class="WeldConstraint" referent="2">
|
<Item class="WeldConstraint" referent="2">
|
||||||
<Properties>
|
<Properties>
|
||||||
<string name="Name">WeldConstraint</string>
|
<string name="Name">WeldConstraint</string>
|
||||||
<BinaryString name="AttributesSerialize">
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
||||||
</BinaryString>
|
|
||||||
<CoordinateFrame name="CFrame0">
|
<CoordinateFrame name="CFrame0">
|
||||||
<X>7</X>
|
<X>7</X>
|
||||||
<Y>0.000001013279</Y>
|
<Y>0.000001013279</Y>
|
||||||
@@ -136,8 +133,7 @@ expression: contents
|
|||||||
<Properties>
|
<Properties>
|
||||||
<string name="Name">B</string>
|
<string name="Name">B</string>
|
||||||
<bool name="Anchored">false</bool>
|
<bool name="Anchored">false</bool>
|
||||||
<BinaryString name="AttributesSerialize">
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
||||||
</BinaryString>
|
|
||||||
<float name="BackParamA">-0.5</float>
|
<float name="BackParamA">-0.5</float>
|
||||||
<float name="BackParamB">0.5</float>
|
<float name="BackParamB">0.5</float>
|
||||||
<token name="BackSurface">0</token>
|
<token name="BackSurface">0</token>
|
||||||
|
|||||||
114
rojo-test/build-tests/attributes/default.project.json
Normal file
114
rojo-test/build-tests/attributes/default.project.json
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
{
|
||||||
|
"name": "attributes",
|
||||||
|
"tree": {
|
||||||
|
"$className": "Folder",
|
||||||
|
|
||||||
|
"Implicit": {
|
||||||
|
"$className": "Folder",
|
||||||
|
"$attributes": {
|
||||||
|
"Bool": true,
|
||||||
|
"Number": 0.5,
|
||||||
|
"String": "Test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"Explicit": {
|
||||||
|
"$className": "Folder",
|
||||||
|
"$attributes": {
|
||||||
|
"Bool": {
|
||||||
|
"Bool": true
|
||||||
|
},
|
||||||
|
"Float32": {
|
||||||
|
"Float32": 0
|
||||||
|
},
|
||||||
|
"Float64": {
|
||||||
|
"Float64": 0
|
||||||
|
},
|
||||||
|
"UDim": {
|
||||||
|
"UDim": [0, 0]
|
||||||
|
},
|
||||||
|
"UDim2": {
|
||||||
|
"UDim2": [[0, 0], [0, 0]]
|
||||||
|
},
|
||||||
|
"BrickColor": {
|
||||||
|
"BrickColor": 1
|
||||||
|
},
|
||||||
|
"Color3": {
|
||||||
|
"Color3": [0, 0, 0]
|
||||||
|
},
|
||||||
|
"Vector2": {
|
||||||
|
"Vector2": [0, 0]
|
||||||
|
},
|
||||||
|
"Vector3": {
|
||||||
|
"Vector3": [0, 0, 0]
|
||||||
|
},
|
||||||
|
"NumberSequence": {
|
||||||
|
"NumberSequence": {
|
||||||
|
"keypoints": [
|
||||||
|
{
|
||||||
|
"time": 0,
|
||||||
|
"value": 0,
|
||||||
|
"envelope": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time": 1,
|
||||||
|
"value": 0,
|
||||||
|
"envelope": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ColorSequence": {
|
||||||
|
"ColorSequence": {
|
||||||
|
"keypoints": [
|
||||||
|
{
|
||||||
|
"time": 0,
|
||||||
|
"color": [1, 1, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time": 1,
|
||||||
|
"color": [1, 1, 1]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"NumberRange": {
|
||||||
|
"NumberRange": [0, 0]
|
||||||
|
},
|
||||||
|
"Rect": {
|
||||||
|
"Rect": [[0, 0], [0, 0]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"LegacyExplicit": {
|
||||||
|
"$className": "Folder",
|
||||||
|
"$properties": {
|
||||||
|
"Attributes": {
|
||||||
|
"Attributes": {
|
||||||
|
"Hello": {
|
||||||
|
"String": "World"
|
||||||
|
},
|
||||||
|
"Vector": {
|
||||||
|
"Vector3": [1, 2, 3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"LegacyImplicit": {
|
||||||
|
"$className": "Folder",
|
||||||
|
"$properties": {
|
||||||
|
"Attributes": {
|
||||||
|
"Hey": {
|
||||||
|
"String": "Grandma"
|
||||||
|
},
|
||||||
|
"Vector": {
|
||||||
|
"Vector3": [4, 5, 6]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "init_csv_with_children",
|
||||||
|
"tree": {
|
||||||
|
"$path": "src"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
Key
|
||||||
|
init.csv
|
||||||
|
@@ -0,0 +1,2 @@
|
|||||||
|
Key
|
||||||
|
other.csv
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -154,7 +154,9 @@ impl JobThreadContext {
|
|||||||
|
|
||||||
for id in affected_ids {
|
for id in affected_ids {
|
||||||
if let Some(patch) = compute_and_apply_changes(&mut tree, &self.vfs, id) {
|
if let Some(patch) = compute_and_apply_changes(&mut tree, &self.vfs, id) {
|
||||||
applied_patches.push(patch);
|
if !patch.is_empty() {
|
||||||
|
applied_patches.push(patch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,7 +255,9 @@ impl JobThreadContext {
|
|||||||
apply_patch_set(&mut tree, patch_set)
|
apply_patch_set(&mut tree, patch_set)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.message_queue.push_messages(&[applied_patch]);
|
if !applied_patch.is_empty() {
|
||||||
|
self.message_queue.push_messages(&[applied_patch]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +295,7 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: Ref) -> Option<
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let patch_set = compute_patch_set(snapshot.as_ref(), &tree, id);
|
let patch_set = compute_patch_set(snapshot, &tree, id);
|
||||||
apply_patch_set(tree, patch_set)
|
apply_patch_set(tree, patch_set)
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
@@ -334,7 +338,7 @@ fn compute_and_apply_changes(tree: &mut RojoTree, vfs: &Vfs, id: Ref) -> Option<
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let patch_set = compute_patch_set(snapshot.as_ref(), &tree, id);
|
let patch_set = compute_patch_set(snapshot, &tree, id);
|
||||||
apply_patch_set(tree, patch_set)
|
apply_patch_set(tree, patch_set)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
io::{BufWriter, Write},
|
io::{BufWriter, Write},
|
||||||
|
mem::forget,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -61,6 +62,10 @@ impl BuildCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid dropping ServeSession: it's potentially VERY expensive to drop
|
||||||
|
// and we're about to exit anyways.
|
||||||
|
forget(session);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,6 +102,7 @@ fn xml_encode_config() -> rbx_xml::EncodeOptions {
|
|||||||
rbx_xml::EncodeOptions::new().property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown)
|
rbx_xml::EncodeOptions::new().property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[profiling::function]
|
||||||
fn write_model(
|
fn write_model(
|
||||||
session: &ServeSession,
|
session: &ServeSession,
|
||||||
output: &Path,
|
output: &Path,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use clap::Parser;
|
|||||||
|
|
||||||
use crate::project::Project;
|
use crate::project::Project;
|
||||||
|
|
||||||
|
use super::resolve_path;
|
||||||
|
|
||||||
/// Reformat a Rojo project using the standard JSON formatting rules.
|
/// Reformat a Rojo project using the standard JSON formatting rules.
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub struct FmtProjectCommand {
|
pub struct FmtProjectCommand {
|
||||||
@@ -15,7 +17,8 @@ pub struct FmtProjectCommand {
|
|||||||
|
|
||||||
impl FmtProjectCommand {
|
impl FmtProjectCommand {
|
||||||
pub fn run(self) -> anyhow::Result<()> {
|
pub fn run(self) -> anyhow::Result<()> {
|
||||||
let project = Project::load_fuzzy(&self.project)?
|
let base_path = resolve_path(&self.project);
|
||||||
|
let project = Project::load_fuzzy(&base_path)?
|
||||||
.context("A project file is required to run 'rojo fmt-project'")?;
|
.context("A project file is required to run 'rojo fmt-project'")?;
|
||||||
|
|
||||||
let serialized = serde_json::to_string_pretty(&project)
|
let serialized = serde_json::to_string_pretty(&project)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user