mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 12:45:05 +00:00
Compare commits
153 Commits
v6.2.0
...
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 | ||
|
|
79b57b3359 | ||
|
|
c7aeffe586 | ||
|
|
79c02f2457 | ||
|
|
b9ed68fa9e | ||
|
|
6c6d6c9c8d | ||
|
|
e169d7be68 | ||
|
|
192fd7d4dd | ||
|
|
1f1193e857 | ||
|
|
0a412ade88 | ||
|
|
3cef2fe9aa | ||
|
|
18e53f06fe | ||
|
|
eaac539087 | ||
|
|
57005c4fd5 | ||
|
|
ea58999a2a | ||
|
|
a5a69fd9fc | ||
|
|
f1d0f1c1c9 | ||
|
|
83492d7495 | ||
|
|
10abc2254a | ||
|
|
5d5536a95e | ||
|
|
fe81e55925 | ||
|
|
654690d73e | ||
|
|
256aba4bc1 | ||
|
|
49f8845105 | ||
|
|
12370846b4 | ||
|
|
07637dfe96 | ||
|
|
f389a4a1db | ||
|
|
af077c796c | ||
|
|
1c319f2fa8 | ||
|
|
e8afa03f7b | ||
|
|
9b22545842 | ||
|
|
adc733d25c | ||
|
|
6896257647 | ||
|
|
1d9845a6cb | ||
|
|
8461339e9a | ||
|
|
9904d94e4c | ||
|
|
da25c80d0b | ||
|
|
5fa63733fd | ||
|
|
8b54bf0ba1 | ||
|
|
173dc12cb3 | ||
|
|
e136529ff0 | ||
|
|
75542dacb3 | ||
|
|
07abfbde43 | ||
|
|
96112fe118 | ||
|
|
9d0b313261 | ||
|
|
277ddfa9be | ||
|
|
5d88bdb256 | ||
|
|
8d29b43155 | ||
|
|
cc071a6415 | ||
|
|
8954def25c | ||
|
|
d484098781 | ||
|
|
9f06cbf3a0 | ||
|
|
af4a3ca0af | ||
|
|
43715143e4 | ||
|
|
16aa354d36 | ||
|
|
c739025453 | ||
|
|
f0526d17de | ||
|
|
6cc2e919c0 | ||
|
|
e1f9eaefa9 | ||
|
|
5d62bf9b60 | ||
|
|
4aa5814a0a | ||
|
|
5bca244062 | ||
|
|
7cf57714a4 | ||
|
|
92e6f862ad | ||
|
|
2377f41036 | ||
|
|
26a08f4d9f | ||
|
|
672d207961 | ||
|
|
a3d8e50f26 | ||
|
|
d3abca46a8 | ||
|
|
17fdd18c55 | ||
|
|
e482d038c6 | ||
|
|
d0482a004e | ||
|
|
561a3e3256 | ||
|
|
158dac5e1c | ||
|
|
1413f8c0b6 | ||
|
|
ffb2aa332a | ||
|
|
45e8208e9c | ||
|
|
7f230a8bf4 | ||
|
|
afe26b8c16 | ||
|
|
d153f62b8a | ||
|
|
5c80cd6e50 | ||
|
|
df1aced95d | ||
|
|
5a0a8f5077 | ||
|
|
1c281539e0 | ||
|
|
ef41d14f50 | ||
|
|
aa29397732 | ||
|
|
ca8865a3ce | ||
|
|
7599ef6626 | ||
|
|
43e71f9242 | ||
|
|
aac9b25efe | ||
|
|
532d170585 | ||
|
|
3dcb14013b | ||
|
|
a4c782cd35 | ||
|
|
0779baa0ac | ||
|
|
0849fab644 | ||
|
|
d85bca2e7e | ||
|
|
c7ab6c435c | ||
|
|
98db3b4f08 | ||
|
|
0e7ba839ed | ||
|
|
2c27691e57 | ||
|
|
934506bdfd | ||
|
|
f313fa4ae1 | ||
|
|
78755b6130 | ||
|
|
8bf59e5cbb | ||
|
|
8f71d13714 | ||
|
|
f4a790eb50 | ||
|
|
0d951c8ad1 | ||
|
|
59ef5f05ea |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
patreon: lpghatguy
|
||||
62
.github/workflows/ci.yml
vendored
62
.github/workflows/ci.yml
vendored
@@ -11,29 +11,67 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
name: Build and Test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
rust_version: [stable, "1.43.1"]
|
||||
rust_version: [stable, 1.58.1]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Rust toolchain
|
||||
run: rustup default ${{ matrix.rust_version }}
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust_version }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- name: Setup Foreman
|
||||
uses: Roblox/setup-foreman@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
cd plugin
|
||||
wally install
|
||||
cd ..
|
||||
|
||||
- name: Build
|
||||
run: cargo build --locked --verbose
|
||||
|
||||
- name: Run tests
|
||||
- name: Test
|
||||
run: cargo test --locked --verbose
|
||||
|
||||
- name: Rustfmt and Clippy
|
||||
lint:
|
||||
name: Rustfmt and Clippy
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Setup Foreman
|
||||
uses: Roblox/setup-foreman@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
cargo fmt -- --check
|
||||
cargo clippy
|
||||
if: matrix.rust_version == 'stable'
|
||||
cd plugin
|
||||
wally install
|
||||
cd ..
|
||||
|
||||
- name: Rustfmt
|
||||
run: cargo fmt -- --check
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy
|
||||
|
||||
208
.github/workflows/release.yml
vendored
208
.github/workflows/release.yml
vendored
@@ -2,65 +2,161 @@ name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["*"]
|
||||
tags: ["v*"]
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Build release binary
|
||||
run: cargo build --verbose --locked --release
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: rojo-win64
|
||||
path: target/release/rojo.exe
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install Rust
|
||||
run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
|
||||
- name: Build release binary
|
||||
run: |
|
||||
source $HOME/.cargo/env
|
||||
cargo build --verbose --locked --release
|
||||
env:
|
||||
OPENSSL_STATIC: 1
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: rojo-macos
|
||||
path: target/release/rojo
|
||||
|
||||
linux:
|
||||
create-release:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
submodules: true
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
|
||||
- name: Build
|
||||
run: cargo build --locked --verbose --release
|
||||
env:
|
||||
OPENSSL_STATIC: 1
|
||||
build-plugin:
|
||||
needs: ["create-release"]
|
||||
name: Build Roblox Studio Plugin
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: rojo-linux
|
||||
path: target/release/rojo
|
||||
- name: Setup Foreman
|
||||
uses: Roblox/setup-foreman@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
cd plugin
|
||||
wally install
|
||||
cd ..
|
||||
|
||||
- name: Build Plugin
|
||||
run: rojo build plugin --output Rojo.rbxm
|
||||
|
||||
- name: Upload Plugin to Release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: Rojo.rbxm
|
||||
asset_name: Rojo.rbxm
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Plugin to Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Rojo.rbxm
|
||||
path: Rojo.rbxm
|
||||
|
||||
build:
|
||||
needs: ["create-release"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# https://doc.rust-lang.org/rustc/platform-support.html
|
||||
include:
|
||||
- host: linux
|
||||
os: ubuntu-20.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
label: linux-x86_64
|
||||
|
||||
- host: windows
|
||||
os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
label: windows-x86_64
|
||||
|
||||
- host: macos
|
||||
os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
label: macos-x86_64
|
||||
|
||||
- host: macos
|
||||
os: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
label: macos-aarch64
|
||||
|
||||
name: Build (${{ matrix.target }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
BIN: rojo
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Get Version from Tag
|
||||
shell: bash
|
||||
# https://github.community/t/how-to-get-just-the-tag-name/16241/7#M1027
|
||||
run: |
|
||||
echo "PROJECT_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
echo "Version is: ${{ env.PROJECT_VERSION }}"
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
- name: Setup Foreman
|
||||
uses: Roblox/setup-foreman@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install packages
|
||||
run: |
|
||||
cd plugin
|
||||
wally install
|
||||
cd ..
|
||||
|
||||
- name: Build Release
|
||||
run: cargo build --release --locked --verbose
|
||||
env:
|
||||
# Build into a known directory so we can find our build artifact more
|
||||
# easily.
|
||||
CARGO_TARGET_DIR: output
|
||||
|
||||
# On platforms that use OpenSSL, ensure it is statically linked to
|
||||
# make binaries more portable.
|
||||
OPENSSL_STATIC: 1
|
||||
|
||||
- name: Create Release Archive
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir staging
|
||||
|
||||
if [ "${{ matrix.host }}" = "windows" ]; then
|
||||
cp "output/release/$BIN.exe" staging/
|
||||
cd staging
|
||||
7z a ../release.zip *
|
||||
else
|
||||
cp "output/release/$BIN" staging/
|
||||
cd staging
|
||||
zip ../release.zip *
|
||||
fi
|
||||
|
||||
- name: Upload Archive to Release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: release.zip
|
||||
asset_name: ${{ env.BIN }}-${{ env.PROJECT_VERSION }}-${{ matrix.label }}.zip
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Archive to Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.BIN }}-${{ env.PROJECT_VERSION }}-${{ matrix.label }}.zip
|
||||
path: release.zip
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -13,12 +13,12 @@
|
||||
# Test places for the Roblox Studio Plugin
|
||||
/plugin/*.rbxlx
|
||||
|
||||
# Packages for the Roblox Studio Plugin
|
||||
/plugin/*Packages
|
||||
|
||||
# Roblox Studio holds 'lock' files on places
|
||||
*.rbxl.lock
|
||||
*.rbxlx.lock
|
||||
|
||||
# Snapshot files from the 'insta' Rust crate
|
||||
**/*.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",
|
||||
}
|
||||
184
CHANGELOG.md
184
CHANGELOG.md
@@ -1,6 +1,190 @@
|
||||
# Rojo Changelog
|
||||
|
||||
## 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.
|
||||
* Significantly improved performance of building and serving. ([#548])
|
||||
* Increased minimum supported Rust version to 1.57.0. ([#564])
|
||||
|
||||
[#540]: https://github.com/rojo-rbx/rojo/pull/540
|
||||
[#548]: https://github.com/rojo-rbx/rojo/pull/548
|
||||
[#549]: https://github.com/rojo-rbx/rojo/pull/549
|
||||
[#552]: https://github.com/rojo-rbx/rojo/pull/552
|
||||
[#553]: https://github.com/rojo-rbx/rojo/pull/553
|
||||
[#556]: https://github.com/rojo-rbx/rojo/pull/556
|
||||
[#561]: https://github.com/rojo-rbx/rojo/pull/561
|
||||
[#563]: https://github.com/rojo-rbx/rojo/pull/563
|
||||
[#564]: https://github.com/rojo-rbx/rojo/pull/564
|
||||
[7.2.0]: https://github.com/rojo-rbx/rojo/releases/tag/v7.2.0
|
||||
|
||||
## [7.1.1] - May 26, 2022
|
||||
* Fixed sourcemap command not stripping paths correctly ([#544])
|
||||
* Fixed Studio plugin settings not saving correctly.
|
||||
|
||||
[#544]: https://github.com/rojo-rbx/rojo/pull/544
|
||||
[#545]: https://github.com/rojo-rbx/rojo/pull/545
|
||||
[7.1.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.1.1
|
||||
|
||||
## [7.1.0] - May 22, 2022
|
||||
* Added support for specifying an address to be used by default in project files. ([#507])
|
||||
* Added support for optional paths in project files. ([#472])
|
||||
* Added support for the new Open Cloud API when uploading. ([#504])
|
||||
* Added `sourcemap` command for generating sourcemaps to feed into other tools. ([#530])
|
||||
* Added PluginActions for connecting/disconnecting a session ([#537])
|
||||
* Added changing toolbar icon to indicate state ([#538])
|
||||
|
||||
[#472]: https://github.com/rojo-rbx/rojo/pull/472
|
||||
[#504]: https://github.com/rojo-rbx/rojo/pull/504
|
||||
[#507]: https://github.com/rojo-rbx/rojo/pull/507
|
||||
[#530]: https://github.com/rojo-rbx/rojo/pull/530
|
||||
[#537]: https://github.com/rojo-rbx/rojo/pull/537
|
||||
[#538]: https://github.com/rojo-rbx/rojo/pull/538
|
||||
[7.1.0]: https://github.com/rojo-rbx/rojo/releases/tag/v7.1.0
|
||||
|
||||
## [7.0.0] - December 10, 2021
|
||||
* Fixed Rojo's interactions with properties enabled by FFlags that are not yet enabled. ([#493])
|
||||
* Improved output in Roblox Studio plugin when bad property data is encountered.
|
||||
* Reintroduced support for CFrame shorthand syntax in Rojo project and `.meta.json` files, matching Rojo 6. ([#430])
|
||||
* Connection settings are now remembered when reconnecting in Roblox Studio. ([#500])
|
||||
* Updated reflection database to Roblox v503.
|
||||
|
||||
[#430]: https://github.com/rojo-rbx/rojo/issues/430
|
||||
[#493]: https://github.com/rojo-rbx/rojo/pull/493
|
||||
[#500]: https://github.com/rojo-rbx/rojo/pull/500
|
||||
[7.0.0]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0
|
||||
|
||||
## [7.0.0-rc.3] - October 19, 2021
|
||||
This is the last release candidate for Rojo 7. In an effort to get Rojo 7 out the door, we'll be freezing features from here on out, something we should've done a couple months ago.
|
||||
|
||||
Expect to see Rojo 7 stable soon!
|
||||
|
||||
* Added support for writing `Tags` in project files, model files, and meta files. ([#484])
|
||||
* Adjusted Studio plugin colors to match Roblox Studio palette. ([#482])
|
||||
* Improved experimental two-way sync feature by batching changes. ([#478])
|
||||
|
||||
[#482]: https://github.com/rojo-rbx/rojo/pull/482
|
||||
[#484]: https://github.com/rojo-rbx/rojo/pull/484
|
||||
[#478]: https://github.com/rojo-rbx/rojo/pull/478
|
||||
[7.0.0-rc.3]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-rc.3
|
||||
|
||||
## 7.0.0-rc.2 - October 19, 2021
|
||||
(Botched release due to Git mishap, oops!)
|
||||
|
||||
## [7.0.0-rc.1] - August 23, 2021
|
||||
In Rojo 6 and previous Rojo 7 alphas, an explicit Vector3 property would be written like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"className": "Part",
|
||||
"properties": {
|
||||
"Position": {
|
||||
"Type": "Vector3",
|
||||
"Value": [1, 2, 3]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For Rojo 7, this will need to be changed to:
|
||||
|
||||
```json
|
||||
{
|
||||
"className": "Part",
|
||||
"properties": {
|
||||
"Position": {
|
||||
"Vector3": [1, 2, 3]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The shorthand property format that most users use is not impacted. For reference, it looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"className": "Part",
|
||||
"properties": {
|
||||
"Position": [1, 2, 3]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* Major breaking change: changed property syntax for project files; shorthand syntax is unchanged.
|
||||
* Added the `fmt-project` subcommand for formatting Rojo project files.
|
||||
* Improved error output for many subcommands.
|
||||
* Updated to stable versions of rbx-dom libraries.
|
||||
* Updated async infrastructure, which should fix a handful of bugs. ([#459])
|
||||
* Fixed syncing refs in the Roblox Studio plugin ([#462], [#466])
|
||||
* Added support for long paths on Windows. ([#464])
|
||||
|
||||
[#459]: https://github.com/rojo-rbx/rojo/pull/459
|
||||
[#462]: https://github.com/rojo-rbx/rojo/pull/462
|
||||
[#464]: https://github.com/rojo-rbx/rojo/pull/464
|
||||
[#466]: https://github.com/rojo-rbx/rojo/pull/466
|
||||
[7.0.0-rc.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-rc.1
|
||||
|
||||
## [7.0.0-alpha.4][7.0.0-alpha.4] (May 5, 2021)
|
||||
* Added the `gameId` and `placeId` optional properties to project files.
|
||||
* When connecting from the Rojo Roblox Studio plugin, Rojo will set the game and place ID of the current place to these values, if set.
|
||||
* This is equivalent to running `game:SetUniverseId(...)` and `game:SetPlaceId(...)` from the command bar in Studio.
|
||||
* Added "EXPERIMENTAL!" label to two-way sync toggle in Rojo's Roblox Studio plugin.
|
||||
* Fixed `Name` and `Parent` properties being allowed in Rojo projects. ([#413][pr-413])
|
||||
* Fixed "Open Scripts Externally" feature crashing Studio. ([#369][issue-369])
|
||||
* Empty `.model.json` files will no longer cause errors. ([#420][pr-420])
|
||||
* When specifying `$path` on a service, Rojo now keeps the correct class name. ([#331][issue-331])
|
||||
* Improved error messages for misconfigured projects.
|
||||
|
||||
[issue-331]: https://github.com/rojo-rbx/rojo/issues/331
|
||||
[issue-369]: https://github.com/rojo-rbx/rojo/issues/369
|
||||
[pr-420]: https://github.com/rojo-rbx/rojo/pull/420
|
||||
[pr-413]: https://github.com/rojo-rbx/rojo/pull/413
|
||||
[7.0.0-alpha.4]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-alpha.4
|
||||
|
||||
## [7.0.0-alpha.3][7.0.0-alpha.3] (February 19, 2021)
|
||||
* Updated dependencies, fixing `OptionalCoordinateFrame`-related issues.
|
||||
* Added `--address` flag to `rojo serve` to allow for external connections. ([#403][pr-403])
|
||||
|
||||
[pr-403]: https://github.com/rojo-rbx/rojo/pull/403
|
||||
[7.0.0-alpha.3]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-alpha.3
|
||||
|
||||
## [7.0.0-alpha.2][7.0.0-alpha.2] (February 19, 2021)
|
||||
* Fixed incorrect protocol version between the client and server.
|
||||
|
||||
[7.0.0-alpha.2]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-alpha.2
|
||||
|
||||
## [7.0.0-alpha.1][7.0.0-alpha.1] (February 18, 2021)
|
||||
This release includes a brand new implementation of the Roblox DOM. It brings performance improvements, much better support for `rbxl` and `rbxm` files, and a better internal API.
|
||||
|
||||
* Added support for all remaining property types.
|
||||
* Added support for the entire Roblox binary model format.
|
||||
* Changed `rojo upload` to upload binary places and models instead of XML.
|
||||
* This should make using `rojo upload` much more feasible for large places.
|
||||
* **Breaking**: Changed format of some types of values in `project.json`, `model.json`, and `meta.json` files.
|
||||
* This should impact few projects. See [this file][allValues.json] for new examples of each property type.
|
||||
|
||||
Formatting of types will change more before the stable release of Rojo 7. We're hoping to use this opportunity to normalize some of the case inconsistency introduced in Rojo 0.5.
|
||||
|
||||
[7.0.0-alpha.1]: https://github.com/rojo-rbx/rojo/releases/tag/v7.0.0-alpha.1
|
||||
[allValues.json]: https://github.com/rojo-rbx/rojo/blob/f4a790eb50b74e482000bad1dcfe22533992fb20/plugin/rbx_dom_lua/src/allValues.json
|
||||
|
||||
## [6.0.2](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.2) (February 9, 2021)
|
||||
* Fixed `rojo upload` to handle CSRF challenges.
|
||||
|
||||
@@ -29,25 +29,29 @@ Sometimes there's something that Rojo doesn't do that it probably should.
|
||||
|
||||
Please file issues and we'll try to help figure out what the best way forward is.
|
||||
|
||||
## Local Development Gotchas
|
||||
|
||||
If your build fails with "Error: failed to open file `D:\code\rojo\plugin\modules\roact\src`" you need to update your Git submodules.
|
||||
Run the command and try building again: `git submodule update --init --recursive`.
|
||||
|
||||
## Pushing a Rojo Release
|
||||
The Rojo release process is pretty manual right now. If you need to do it, here's how:
|
||||
|
||||
1. Bump server version in [`Cargo.toml`](Cargo.toml)
|
||||
2. Bump plugin version in [`plugin/src/Config.lua`](plugin/src/Config.lua)
|
||||
3. Run `cargo test` to update `Cargo.lock` and double-check tests
|
||||
3. Run `cargo test` to update `Cargo.lock` and run tests
|
||||
4. Update [`CHANGELOG.md`](CHANGELOG.md)
|
||||
5. Commit!
|
||||
* `git add . && git commit -m "Release vX.Y.Z"`
|
||||
6. Tag the commit with the version from `Cargo.toml` prepended with a v, like `v0.4.13`
|
||||
6. Tag the commit
|
||||
* `git tag vX.Y.Z`
|
||||
7. Publish the CLI
|
||||
* `cargo publish`
|
||||
8. Build and upload the plugin
|
||||
* `rojo build plugin -o Rojo.rbxm`
|
||||
* Upload `Rojo.rbxm` to Roblox.com, keep it for later
|
||||
8. Publish the Plugin
|
||||
* `cargo run -- upload plugin --asset_id 6415005344`
|
||||
9. Push commits and tags
|
||||
* `git push && git push --tags`
|
||||
10. Copy GitHub release content from previous release
|
||||
* Update the leading text with a summary about the release
|
||||
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md)
|
||||
* Write a small summary of each major feature
|
||||
* Attach release artifacts from GitHub Actions for each platform
|
||||
* Write a small summary of each major feature
|
||||
2379
Cargo.lock
generated
2379
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
121
Cargo.toml
121
Cargo.toml
@@ -1,6 +1,7 @@
|
||||
[package]
|
||||
name = "rojo"
|
||||
version = "6.0.2"
|
||||
version = "7.2.1"
|
||||
rust-version = "1.58.1"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
description = "Enables professional-grade development tools for Roblox developers"
|
||||
license = "MPL-2.0"
|
||||
@@ -8,7 +9,8 @@ homepage = "https://rojo.space"
|
||||
documentation = "https://rojo.space/docs"
|
||||
repository = "https://github.com/rojo-rbx/rojo"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
exclude = [
|
||||
"/test-projects/**",
|
||||
@@ -26,80 +28,83 @@ default = []
|
||||
# Enable this feature to live-reload assets from the web UI.
|
||||
dev_live_assets = []
|
||||
|
||||
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client"]
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"rojo-insta-ext",
|
||||
"memofs",
|
||||
]
|
||||
members = ["crates/*"]
|
||||
|
||||
[lib]
|
||||
name = "librojo"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "rojo"
|
||||
path = "src/bin.rs"
|
||||
|
||||
[[bench]]
|
||||
name = "build"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
memofs = { version = "0.1.2", path = "memofs" }
|
||||
memofs = { version = "0.2.0", path = "crates/memofs" }
|
||||
|
||||
anyhow = "1.0.27"
|
||||
backtrace = "0.3"
|
||||
bincode = "1.2.1"
|
||||
crossbeam-channel = "0.4.0"
|
||||
csv = "1.1.1"
|
||||
env_logger = "0.7.1"
|
||||
fs-err = "2.2.0"
|
||||
futures = "0.1.29"
|
||||
globset = "0.4.4"
|
||||
humantime = "1.3.0"
|
||||
hyper = "0.12.35"
|
||||
jod-thread = "0.1.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.8"
|
||||
maplit = "1.0.1"
|
||||
notify = "4.0.14"
|
||||
opener = "0.4.1"
|
||||
rbx_binary = "0.5.0"
|
||||
rbx_dom_weak = "1.10.1"
|
||||
rbx_reflection = "3.3.408"
|
||||
rbx_xml = "0.11.3"
|
||||
regex = "1.3.1"
|
||||
reqwest = "0.9.20"
|
||||
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
||||
# rbx_binary = { path = "../rbx-dom/rbx_binary" }
|
||||
# rbx_dom_weak = { path = "../rbx-dom/rbx_dom_weak" }
|
||||
# rbx_reflection = { path = "../rbx-dom/rbx_reflection" }
|
||||
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
||||
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
||||
|
||||
rbx_binary = "0.6.5"
|
||||
rbx_dom_weak = "2.4.0"
|
||||
rbx_reflection = "4.2.0"
|
||||
rbx_reflection_database = "0.2.2"
|
||||
rbx_xml = "0.12.3"
|
||||
|
||||
anyhow = "1.0.44"
|
||||
backtrace = "0.3.61"
|
||||
bincode = "1.3.3"
|
||||
crossbeam-channel = "0.5.1"
|
||||
csv = "1.1.6"
|
||||
env_logger = "0.9.0"
|
||||
fs-err = "2.6.0"
|
||||
futures = "0.3.17"
|
||||
globset = "0.4.8"
|
||||
humantime = "2.1.0"
|
||||
hyper = { version = "0.14.13", features = ["server", "tcp", "http1"] }
|
||||
jod-thread = "0.1.2"
|
||||
log = "0.4.14"
|
||||
maplit = "1.0.2"
|
||||
notify = "4.0.17"
|
||||
opener = "0.5.0"
|
||||
reqwest = { version = "0.11.10", features = ["blocking", "json", "native-tls-vendored"] }
|
||||
ritz = "0.1.0"
|
||||
rlua = "0.17.0"
|
||||
roblox_install = "0.2.2"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
structopt = "0.3.5"
|
||||
termcolor = "1.0.5"
|
||||
thiserror = "1.0.11"
|
||||
tokio = "0.1.22"
|
||||
uuid = { version = "0.8.1", features = ["v4", "serde"] }
|
||||
roblox_install = "1.0.0"
|
||||
serde = { version = "1.0.130", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.68"
|
||||
termcolor = "1.1.2"
|
||||
thiserror = "1.0.30"
|
||||
tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] }
|
||||
uuid = { version = "1.0.0", features = ["v4", "serde"] }
|
||||
clap = { version = "3.1.18", features = ["derive"] }
|
||||
profiling = "1.0.6"
|
||||
tracy-client = { version = "0.13.2", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.6.2"
|
||||
winreg = "0.10.1"
|
||||
|
||||
[build-dependencies]
|
||||
memofs = { version = "0.1.3", path = "memofs" }
|
||||
memofs = { version = "0.2.0", path = "crates/memofs" }
|
||||
|
||||
anyhow = "1.0.27"
|
||||
bincode = "1.2.1"
|
||||
fs-err = "2.3.0"
|
||||
maplit = "1.0.1"
|
||||
embed-resource = "1.6.4"
|
||||
anyhow = "1.0.44"
|
||||
bincode = "1.3.3"
|
||||
fs-err = "2.6.0"
|
||||
maplit = "1.0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
rojo-insta-ext = { path = "rojo-insta-ext" }
|
||||
rojo-insta-ext = { path = "crates/rojo-insta-ext" }
|
||||
|
||||
criterion = "0.3"
|
||||
insta = { version = "1.3.0", features = ["redactions"] }
|
||||
lazy_static = "1.2"
|
||||
paste = "0.1"
|
||||
pretty_assertions = "0.6.1"
|
||||
serde_yaml = "0.8.9"
|
||||
tempfile = "3.0"
|
||||
walkdir = "2.1"
|
||||
criterion = "0.3.5"
|
||||
insta = { version = "1.8.0", features = ["redactions"] }
|
||||
paste = "1.0.5"
|
||||
pretty_assertions = "1.2.1"
|
||||
serde_yaml = "0.8.21"
|
||||
tempfile = "3.2.0"
|
||||
walkdir = "2.3.2"
|
||||
|
||||
18
README.md
18
README.md
@@ -1,21 +1,13 @@
|
||||
<div align="center">
|
||||
<a href="https://rojo.space">
|
||||
<img src="assets/logo-512.png" alt="Rojo" height="217" />
|
||||
</a>
|
||||
<a href="https://rojo.space"><img src="assets/logo-512.png" alt="Rojo" height="217" /></a>
|
||||
</div>
|
||||
|
||||
<div> </div>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/rojo-rbx/rojo/actions">
|
||||
<img src="https://github.com/rojo-rbx/rojo/workflows/CI/badge.svg" alt="Actions status" />
|
||||
</a>
|
||||
<a href="https://crates.io/crates/rojo">
|
||||
<img src="https://img.shields.io/crates/v/rojo.svg?label=latest%20release" alt="Latest server version" />
|
||||
</a>
|
||||
<a href="https://rojo.space/docs">
|
||||
<img src="https://img.shields.io/badge/docs-website-brightgreen.svg" alt="Rojo Documentation" />
|
||||
</a>
|
||||
<a href="https://github.com/rojo-rbx/rojo/actions"><img src="https://github.com/rojo-rbx/rojo/workflows/CI/badge.svg" alt="Actions status" /></a>
|
||||
<a href="https://crates.io/crates/rojo"><img src="https://img.shields.io/crates/v/rojo.svg?label=latest%20release" alt="Latest server version" /></a>
|
||||
<a href="https://rojo.space/docs"><img src="https://img.shields.io/badge/docs-website-brightgreen.svg" alt="Rojo Documentation" /></a>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
@@ -48,7 +40,7 @@ Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions fo
|
||||
|
||||
Pull requests are welcome!
|
||||
|
||||
Rojo supports Rust 1.43.1 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
|
||||
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",
|
||||
|
||||
"ReplicatedStorage": {
|
||||
"Common": {
|
||||
"Shared": {
|
||||
"$path": "src/shared"
|
||||
}
|
||||
},
|
||||
|
||||
BIN
assets/icon-link-32.png
Normal file
BIN
assets/icon-link-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/icon-warn-32.png
Normal file
BIN
assets/icon-warn-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -3,7 +3,7 @@ use std::path::Path;
|
||||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
use librojo::cli::{build, BuildCommand};
|
||||
use librojo::cli::BuildCommand;
|
||||
|
||||
pub fn benchmark_small_place(c: &mut Criterion) {
|
||||
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| {
|
||||
b.iter_batched(
|
||||
|| place_setup(path),
|
||||
|(_dir, options)| build(options).unwrap(),
|
||||
|(_dir, options)| options.run().unwrap(),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
watchexec -c -w plugin "sh -c './bin/install-dev-plugin.sh'"
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
DIR="$( mktemp -d )"
|
||||
PLUGIN_FILE="$DIR/Rojo.rbxm"
|
||||
TESTEZ_FILE="$DIR/TestEZ.rbxm"
|
||||
|
||||
rojo build plugin -o "$PLUGIN_FILE"
|
||||
rojo build plugin/testez.project.json -o "$TESTEZ_FILE"
|
||||
remodel bin/mark-plugin-as-dev.lua "$PLUGIN_FILE" "$TESTEZ_FILE" 2>/dev/null
|
||||
|
||||
cp "$PLUGIN_FILE" "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
rojo build plugin -o "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"
|
||||
@@ -1,12 +0,0 @@
|
||||
local pluginPath, testezPath = ...
|
||||
|
||||
local plugin = remodel.readModelFile(pluginPath)[1]
|
||||
local testez = remodel.readModelFile(testezPath)[1]
|
||||
|
||||
local marker = Instance.new("Folder")
|
||||
marker.Name = "ROJO_DEV_BUILD"
|
||||
marker.Parent = plugin
|
||||
|
||||
testez.Parent = plugin
|
||||
|
||||
remodel.writeModelFile(plugin, pluginPath)
|
||||
@@ -1,8 +0,0 @@
|
||||
local pluginPath, placePath = ...
|
||||
|
||||
local plugin = remodel.readModelFile(pluginPath)[1]
|
||||
local place = remodel.readPlaceFile(placePath)
|
||||
|
||||
plugin.Parent = place:GetService("ReplicatedStorage")
|
||||
|
||||
remodel.writePlaceFile(place, placePath)
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
./bin/run-cli-tests.sh
|
||||
./bin/run-plugin-tests.sh
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
cargo test --all --locked
|
||||
cargo fmt -- --check
|
||||
|
||||
touch src/lib.rs # Nudge Rust source to make Clippy actually check things
|
||||
cargo clippy
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
DIR="$( mktemp -d )"
|
||||
PLUGIN_FILE="$DIR/Rojo.rbxmx"
|
||||
PLACE_FILE="$DIR/RojoTestPlace.rbxlx"
|
||||
|
||||
rojo build plugin -o "$PLUGIN_FILE"
|
||||
rojo build plugin/place.project.json -o "$PLACE_FILE"
|
||||
|
||||
remodel bin/put-plugin-in-test-place.lua "$PLUGIN_FILE" "$PLACE_FILE"
|
||||
|
||||
run-in-roblox -s plugin/testBootstrap.server.lua "$PLACE_FILE"
|
||||
|
||||
luacheck plugin/src plugin/log plugin/http
|
||||
23
build.rs
23
build.rs
@@ -21,7 +21,7 @@ fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
||||
|
||||
// We can skip any TestEZ test files since they aren't necessary for
|
||||
// the plugin to run.
|
||||
if file_name.ends_with(".spec.lua") {
|
||||
if file_name.ends_with(".spec.lua") || file_name.ends_with(".spec.luau") {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -43,8 +43,6 @@ fn main() -> Result<(), anyhow::Error> {
|
||||
let root_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
|
||||
let plugin_root = PathBuf::from(root_dir).join("plugin");
|
||||
|
||||
let plugin_modules = plugin_root.join("modules");
|
||||
|
||||
let snapshot = VfsSnapshot::dir(hashmap! {
|
||||
"default.project.json" => snapshot_from_fs_path(&plugin_root.join("default.project.json"))?,
|
||||
"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"))?,
|
||||
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_root.join("rbx_dom_lua"))?,
|
||||
"src" => snapshot_from_fs_path(&plugin_root.join("src"))?,
|
||||
"modules" => VfsSnapshot::dir(hashmap! {
|
||||
"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"))?
|
||||
}),
|
||||
}),
|
||||
"Packages" => snapshot_from_fs_path(&plugin_root.join("Packages"))?,
|
||||
});
|
||||
|
||||
let out_path = Path::new(&out_dir).join("plugin.bincode");
|
||||
@@ -73,5 +58,9 @@ fn main() -> Result<(), anyhow::Error> {
|
||||
|
||||
bincode::serialize_into(out_file, &snapshot)?;
|
||||
|
||||
println!("cargo:rerun-if-changed=build/windows/rojo-manifest.rc");
|
||||
println!("cargo:rerun-if-changed=build/windows/rojo.manifest");
|
||||
embed_resource::compile("build/windows/rojo-manifest.rc");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
2
build/windows/rojo-manifest.rc
Normal file
2
build/windows/rojo-manifest.rc
Normal file
@@ -0,0 +1,2 @@
|
||||
#define RT_MANIFEST 24
|
||||
1 RT_MANIFEST "rojo.manifest"
|
||||
8
build/windows/rojo.manifest
Normal file
8
build/windows/rojo.manifest
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
|
||||
<ws2:longPathAware>true</ws2:longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
## Unreleased Changes
|
||||
|
||||
## 0.2.0 (2021-08-23)
|
||||
* Updated to `crossbeam-channel` 0.5.1.
|
||||
|
||||
## 0.1.3 (2020-11-19)
|
||||
* Added `set_watch_enabled` to `Vfs` and `VfsLock` to allow turning off file watching.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "memofs"
|
||||
description = "Virtual filesystem with configurable backends."
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
@@ -11,7 +11,7 @@ homepage = "https://github.com/rojo-rbx/rojo/tree/master/memofs"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
crossbeam-channel = "0.4.0"
|
||||
crossbeam-channel = "0.5.1"
|
||||
fs-err = "2.3.0"
|
||||
notify = "4.0.15"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
@@ -1,3 +1,5 @@
|
||||
[tools]
|
||||
rojo = { source = "rojo-rbx/rojo", version = "6.0.0-rc.3" }
|
||||
rojo = { source = "rojo-rbx/rojo", version = "7.2.1" }
|
||||
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }
|
||||
selene = { source = "Kampfkarren/selene", version = "0.20.0" }
|
||||
wally = { source = "UpliftGames/wally", version = "0.3.1"}
|
||||
|
||||
@@ -1,33 +1,25 @@
|
||||
{
|
||||
"name": "Rojo",
|
||||
"tree": {
|
||||
"$className": "Folder",
|
||||
"Plugin": {
|
||||
"$path": "src"
|
||||
},
|
||||
"Log": {
|
||||
"$path": "log"
|
||||
},
|
||||
"Http": {
|
||||
"$path": "http"
|
||||
},
|
||||
"Fmt": {
|
||||
"$path": "fmt"
|
||||
},
|
||||
"RbxDom": {
|
||||
"$path": "rbx_dom_lua"
|
||||
},
|
||||
"Roact": {
|
||||
"$path": "modules/roact/src"
|
||||
},
|
||||
"Promise": {
|
||||
"$path": "modules/promise/lib"
|
||||
},
|
||||
"t": {
|
||||
"$path": "modules/t/lib"
|
||||
},
|
||||
"Flipper": {
|
||||
"$path": "modules/flipper/src"
|
||||
}
|
||||
}
|
||||
}
|
||||
"name": "Rojo",
|
||||
"tree": {
|
||||
"$className": "Folder",
|
||||
"Plugin": {
|
||||
"$path": "src"
|
||||
},
|
||||
"Packages": {
|
||||
"$path": "Packages",
|
||||
|
||||
"Log": {
|
||||
"$path": "log"
|
||||
},
|
||||
"Http": {
|
||||
"$path": "http"
|
||||
},
|
||||
"Fmt": {
|
||||
"$path": "fmt"
|
||||
},
|
||||
"RbxDom": {
|
||||
"$path": "rbx_dom_lua"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 6e9157db3c
@@ -1,44 +0,0 @@
|
||||
stds.roblox = {
|
||||
read_globals = {
|
||||
game = {
|
||||
other_fields = true,
|
||||
},
|
||||
|
||||
-- Roblox globals
|
||||
"script",
|
||||
|
||||
-- Extra functions
|
||||
"tick", "warn",
|
||||
"wait", "typeof",
|
||||
|
||||
-- Types
|
||||
"CFrame",
|
||||
"Color3",
|
||||
"Enum",
|
||||
"Instance",
|
||||
"NumberRange",
|
||||
"Rect",
|
||||
"UDim", "UDim2",
|
||||
"Vector2", "Vector3",
|
||||
"Vector2int16", "Vector3int16",
|
||||
}
|
||||
}
|
||||
|
||||
stds.testez = {
|
||||
read_globals = {
|
||||
"describe",
|
||||
"it", "itFOCUS", "itSKIP",
|
||||
"FOCUS", "SKIP", "HACK_NO_XPCALL",
|
||||
"expect",
|
||||
}
|
||||
}
|
||||
|
||||
ignore = {
|
||||
"212", -- unused arguments
|
||||
}
|
||||
|
||||
std = "lua51+roblox"
|
||||
|
||||
files["**/*.spec.lua"] = {
|
||||
std = "+testez",
|
||||
}
|
||||
512
plugin/rbx_dom_lua/EncodedValue.lua
Normal file
512
plugin/rbx_dom_lua/EncodedValue.lua
Normal file
@@ -0,0 +1,512 @@
|
||||
local base64 = require(script.Parent.base64)
|
||||
|
||||
local function identity(...)
|
||||
return ...
|
||||
end
|
||||
|
||||
local function unpackDecoder(f)
|
||||
return function(value)
|
||||
return f(unpack(value))
|
||||
end
|
||||
end
|
||||
|
||||
local function serializeFloat(value)
|
||||
-- TODO: Figure out a better way to serialize infinity and NaN, neither of
|
||||
-- which fit into JSON.
|
||||
if value == math.huge or value == -math.huge then
|
||||
return 999999999 * math.sign(value)
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
local ALL_AXES = {"X", "Y", "Z"}
|
||||
local ALL_FACES = {"Right", "Top", "Back", "Left", "Bottom", "Front"}
|
||||
|
||||
local EncodedValue = {}
|
||||
|
||||
local types
|
||||
types = {
|
||||
Attributes = {
|
||||
fromPod = function(pod)
|
||||
local output = {}
|
||||
|
||||
for key, value in pairs(pod) do
|
||||
local ok, result = EncodedValue.decode(value)
|
||||
|
||||
if ok then
|
||||
output[key] = result
|
||||
else
|
||||
local warning = ("Could not decode attribute value of type %q: %s"):format(typeof(value), tostring(result))
|
||||
warn(warning)
|
||||
end
|
||||
end
|
||||
|
||||
return output
|
||||
end,
|
||||
toPod = function(roblox)
|
||||
local output = {}
|
||||
|
||||
for key, value in pairs(roblox) do
|
||||
local ok, result = EncodedValue.encodeNaive(value)
|
||||
|
||||
if ok then
|
||||
output[key] = result
|
||||
else
|
||||
local warning = ("Could not encode attribute value of type %q: %s"):format(typeof(value), tostring(result))
|
||||
warn(warning)
|
||||
end
|
||||
end
|
||||
|
||||
return output
|
||||
end,
|
||||
},
|
||||
|
||||
Axes = {
|
||||
fromPod = function(pod)
|
||||
local axes = {}
|
||||
|
||||
for index, axisName in ipairs(pod) do
|
||||
axes[index] = Enum.Axis[axisName]
|
||||
end
|
||||
|
||||
return Axes.new(unpack(axes))
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
local json = {}
|
||||
|
||||
for _, axis in ipairs(ALL_AXES) do
|
||||
if roblox[axis] then
|
||||
table.insert(json, axis)
|
||||
end
|
||||
end
|
||||
|
||||
return json
|
||||
end,
|
||||
},
|
||||
|
||||
BinaryString = {
|
||||
fromPod = base64.decode,
|
||||
toPod = base64.encode,
|
||||
},
|
||||
|
||||
Bool = {
|
||||
fromPod = identity,
|
||||
toPod = identity,
|
||||
},
|
||||
|
||||
BrickColor = {
|
||||
fromPod = function(pod)
|
||||
return BrickColor.new(pod)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
return roblox.Number
|
||||
end,
|
||||
},
|
||||
|
||||
CFrame = {
|
||||
fromPod = function(pod)
|
||||
local pos = pod.position
|
||||
local orient = pod.orientation
|
||||
|
||||
return CFrame.new(
|
||||
pos[1], pos[2], pos[3],
|
||||
orient[1][1], orient[1][2], orient[1][3],
|
||||
orient[2][1], orient[2][2], orient[2][3],
|
||||
orient[3][1], orient[3][2], orient[3][3]
|
||||
)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
local x, y, z,
|
||||
r00, r01, r02,
|
||||
r10, r11, r12,
|
||||
r20, r21, r22 = roblox:GetComponents()
|
||||
|
||||
return {
|
||||
position = {x, y, z},
|
||||
orientation = {
|
||||
{r00, r01, r02},
|
||||
{r10, r11, r12},
|
||||
{r20, r21, r22},
|
||||
},
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Color3 = {
|
||||
fromPod = unpackDecoder(Color3.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {roblox.r, roblox.g, roblox.b}
|
||||
end,
|
||||
},
|
||||
|
||||
Color3uint8 = {
|
||||
fromPod = unpackDecoder(Color3.fromRGB),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
math.round(roblox.R * 255),
|
||||
math.round(roblox.G * 255),
|
||||
math.round(roblox.B * 255),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
ColorSequence = {
|
||||
fromPod = function(pod)
|
||||
local keypoints = {}
|
||||
|
||||
for index, keypoint in ipairs(pod.keypoints) do
|
||||
keypoints[index] = ColorSequenceKeypoint.new(
|
||||
keypoint.time,
|
||||
types.Color3.fromPod(keypoint.color)
|
||||
)
|
||||
end
|
||||
|
||||
return ColorSequence.new(keypoints)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
local keypoints = {}
|
||||
|
||||
for index, keypoint in ipairs(roblox.Keypoints) do
|
||||
keypoints[index] = {
|
||||
time = keypoint.Time,
|
||||
color = types.Color3.toPod(keypoint.Value),
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
keypoints = keypoints,
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Content = {
|
||||
fromPod = identity,
|
||||
toPod = identity,
|
||||
},
|
||||
|
||||
Enum = {
|
||||
fromPod = identity,
|
||||
|
||||
toPod = function(roblox)
|
||||
-- FIXME: More robust handling of enums
|
||||
if typeof(roblox) == "number" then
|
||||
return roblox
|
||||
else
|
||||
return roblox.Value
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
||||
Faces = {
|
||||
fromPod = function(pod)
|
||||
local faces = {}
|
||||
|
||||
for index, faceName in ipairs(pod) do
|
||||
faces[index] = Enum.NormalId[faceName]
|
||||
end
|
||||
|
||||
return Faces.new(unpack(faces))
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
local pod = {}
|
||||
|
||||
for _, face in ipairs(ALL_FACES) do
|
||||
if roblox[face] then
|
||||
table.insert(pod, face)
|
||||
end
|
||||
end
|
||||
|
||||
return pod
|
||||
end,
|
||||
},
|
||||
|
||||
Float32 = {
|
||||
fromPod = identity,
|
||||
toPod = serializeFloat,
|
||||
},
|
||||
|
||||
Float64 = {
|
||||
fromPod = identity,
|
||||
toPod = serializeFloat,
|
||||
},
|
||||
|
||||
Int32 = {
|
||||
fromPod = identity,
|
||||
toPod = identity,
|
||||
},
|
||||
|
||||
Int64 = {
|
||||
fromPod = identity,
|
||||
toPod = identity,
|
||||
},
|
||||
|
||||
NumberRange = {
|
||||
fromPod = unpackDecoder(NumberRange.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {roblox.Min, roblox.Max}
|
||||
end,
|
||||
},
|
||||
|
||||
NumberSequence = {
|
||||
fromPod = function(pod)
|
||||
local keypoints = {}
|
||||
|
||||
for index, keypoint in ipairs(pod.keypoints) do
|
||||
keypoints[index] = NumberSequenceKeypoint.new(
|
||||
keypoint.time,
|
||||
keypoint.value,
|
||||
keypoint.envelope
|
||||
)
|
||||
end
|
||||
|
||||
return NumberSequence.new(keypoints)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
local keypoints = {}
|
||||
|
||||
for index, keypoint in ipairs(roblox.Keypoints) do
|
||||
keypoints[index] = {
|
||||
time = keypoint.Time,
|
||||
value = keypoint.Value,
|
||||
envelope = keypoint.Envelope,
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
keypoints = keypoints,
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
PhysicalProperties = {
|
||||
fromPod = function(pod)
|
||||
if pod == "Default" then
|
||||
return nil
|
||||
else
|
||||
return PhysicalProperties.new(
|
||||
pod.density,
|
||||
pod.friction,
|
||||
pod.elasticity,
|
||||
pod.frictionWeight,
|
||||
pod.elasticityWeight
|
||||
)
|
||||
end
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
if roblox == nil then
|
||||
return "Default"
|
||||
else
|
||||
return {
|
||||
density = roblox.Density,
|
||||
friction = roblox.Friction,
|
||||
elasticity = roblox.Elasticity,
|
||||
frictionWeight = roblox.FrictionWeight,
|
||||
elasticityWeight = roblox.ElasticityWeight,
|
||||
}
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
||||
Ray = {
|
||||
fromPod = function(pod)
|
||||
return Ray.new(
|
||||
types.Vector3.fromPod(pod.origin),
|
||||
types.Vector3.fromPod(pod.direction)
|
||||
)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
origin = types.Vector3.toPod(roblox.Origin),
|
||||
direction = types.Vector3.toPod(roblox.Direction),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Rect = {
|
||||
fromPod = function(pod)
|
||||
return Rect.new(
|
||||
types.Vector2.fromPod(pod[1]),
|
||||
types.Vector2.fromPod(pod[2])
|
||||
)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
types.Vector2.toPod(roblox.Min),
|
||||
types.Vector2.toPod(roblox.Max),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Ref = {
|
||||
fromPod = function(_pod)
|
||||
error("Ref cannot be decoded on its own")
|
||||
end,
|
||||
|
||||
toPod = function(_roblox)
|
||||
error("Ref can not be encoded on its own")
|
||||
end,
|
||||
},
|
||||
|
||||
Region3 = {
|
||||
fromPod = function(pod)
|
||||
error("Region3 is not implemented")
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
error("Region3 is not implemented")
|
||||
end,
|
||||
},
|
||||
|
||||
Region3int16 = {
|
||||
fromPod = function(pod)
|
||||
return Region3int16.new(
|
||||
types.Vector3int16.fromPod(pod[1]),
|
||||
types.Vector3int16.fromPod(pod[2])
|
||||
)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
types.Vector3int16.toPod(roblox.Min),
|
||||
types.Vector3int16.toPod(roblox.Max),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
SharedString = {
|
||||
fromPod = function(pod)
|
||||
error("SharedString is not supported")
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
error("SharedString is not supported")
|
||||
end,
|
||||
},
|
||||
|
||||
String = {
|
||||
fromPod = identity,
|
||||
toPod = identity,
|
||||
},
|
||||
|
||||
UDim = {
|
||||
fromPod = unpackDecoder(UDim.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {roblox.Scale, roblox.Offset}
|
||||
end,
|
||||
},
|
||||
|
||||
UDim2 = {
|
||||
fromPod = function(pod)
|
||||
return UDim2.new(
|
||||
types.UDim.fromPod(pod[1]),
|
||||
types.UDim.fromPod(pod[2])
|
||||
)
|
||||
end,
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
types.UDim.toPod(roblox.X),
|
||||
types.UDim.toPod(roblox.Y),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Tags = {
|
||||
fromPod = identity,
|
||||
toPod = identity,
|
||||
},
|
||||
|
||||
Vector2 = {
|
||||
fromPod = unpackDecoder(Vector2.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
serializeFloat(roblox.X),
|
||||
serializeFloat(roblox.Y),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Vector2int16 = {
|
||||
fromPod = unpackDecoder(Vector2int16.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {roblox.X, roblox.Y}
|
||||
end,
|
||||
},
|
||||
|
||||
Vector3 = {
|
||||
fromPod = unpackDecoder(Vector3.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {
|
||||
serializeFloat(roblox.X),
|
||||
serializeFloat(roblox.Y),
|
||||
serializeFloat(roblox.Z),
|
||||
}
|
||||
end,
|
||||
},
|
||||
|
||||
Vector3int16 = {
|
||||
fromPod = unpackDecoder(Vector3int16.new),
|
||||
|
||||
toPod = function(roblox)
|
||||
return {roblox.X, roblox.Y, roblox.Z}
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
function EncodedValue.decode(encodedValue)
|
||||
local ty, value = next(encodedValue)
|
||||
|
||||
local typeImpl = types[ty]
|
||||
if typeImpl == nil then
|
||||
return false, "Couldn't decode value " .. tostring(ty)
|
||||
end
|
||||
|
||||
return true, typeImpl.fromPod(value)
|
||||
end
|
||||
|
||||
function EncodedValue.encode(rbxValue, propertyType)
|
||||
assert(propertyType ~= nil, "Property type descriptor is required")
|
||||
|
||||
local typeImpl = types[propertyType]
|
||||
if typeImpl == nil then
|
||||
return false, ("Missing encoder for property type %q"):format(propertyType)
|
||||
end
|
||||
|
||||
return true, {
|
||||
[propertyType] = typeImpl.toPod(rbxValue),
|
||||
}
|
||||
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
|
||||
72
plugin/rbx_dom_lua/EncodedValue.spec.lua
Normal file
72
plugin/rbx_dom_lua/EncodedValue.spec.lua
Normal file
@@ -0,0 +1,72 @@
|
||||
return function()
|
||||
local HttpService = game:GetService("HttpService")
|
||||
|
||||
local EncodedValue = require(script.Parent.EncodedValue)
|
||||
local allValues = require(script.Parent.allValues)
|
||||
|
||||
local function deepEq(a, b)
|
||||
if typeof(a) ~= typeof(b) then
|
||||
return false
|
||||
end
|
||||
|
||||
local ty = typeof(a)
|
||||
|
||||
if ty == "table" then
|
||||
local visited = {}
|
||||
|
||||
for key, valueA in pairs(a) do
|
||||
visited[key] = true
|
||||
|
||||
if not deepEq(valueA, b[key]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for key, valueB in pairs(b) do
|
||||
if visited[key] then
|
||||
continue
|
||||
end
|
||||
|
||||
if not deepEq(valueB, a[key]) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
else
|
||||
return a == b
|
||||
end
|
||||
end
|
||||
|
||||
local extraAssertions = {
|
||||
CFrame = function(value)
|
||||
expect(value).to.equal(CFrame.new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12))
|
||||
end,
|
||||
}
|
||||
|
||||
for testName, testEntry in pairs(allValues) do
|
||||
it("round trip " .. testName, function()
|
||||
local ok, decoded = EncodedValue.decode(testEntry.value)
|
||||
assert(ok, decoded)
|
||||
|
||||
if extraAssertions[testName] ~= nil then
|
||||
extraAssertions[testName](decoded)
|
||||
end
|
||||
|
||||
local ok, encoded = EncodedValue.encode(decoded, testEntry.ty)
|
||||
assert(ok, encoded)
|
||||
|
||||
if not deepEq(encoded, testEntry.value) then
|
||||
local expected = HttpService:JSONEncode(testEntry.value)
|
||||
local actual = HttpService:JSONEncode(encoded)
|
||||
|
||||
local message = string.format(
|
||||
"Round-trip results did not match.\nExpected:\n%s\nActual:\n%s",
|
||||
expected, actual
|
||||
)
|
||||
|
||||
error(message)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
@@ -20,8 +20,22 @@ local function set(container, key, value)
|
||||
end
|
||||
|
||||
function PropertyDescriptor.fromRaw(data, className, propertyName)
|
||||
local key, value = next(data.DataType)
|
||||
|
||||
return setmetatable({
|
||||
scriptability = data.scriptability,
|
||||
-- The meanings of the key and value in DataType differ when the type of
|
||||
-- the property is Enum. When the property is of type Enum, the key is
|
||||
-- the name of the type:
|
||||
--
|
||||
-- { Enum = "<name of enum>" }
|
||||
--
|
||||
-- When the property is not of type Enum, the value is the name of the
|
||||
-- type:
|
||||
--
|
||||
-- { Value = "<data type>" }
|
||||
dataType = key == "Enum" and key or value,
|
||||
|
||||
scriptability = data.Scriptability,
|
||||
className = className,
|
||||
name = propertyName,
|
||||
}, PropertyDescriptor)
|
||||
@@ -77,4 +91,4 @@ function PropertyDescriptor:write(instance, value)
|
||||
end
|
||||
end
|
||||
|
||||
return PropertyDescriptor
|
||||
return PropertyDescriptor
|
||||
@@ -1,2 +0,0 @@
|
||||
# rbx_dom_lua
|
||||
Roblox Lua implementation of rbx-dom mechanisms, intended to work with rbx_dom_weak and friends.
|
||||
395
plugin/rbx_dom_lua/allValues.json
Normal file
395
plugin/rbx_dom_lua/allValues.json
Normal file
@@ -0,0 +1,395 @@
|
||||
{
|
||||
"Attributes": {
|
||||
"value": {
|
||||
"Attributes": {
|
||||
"TestBool": {
|
||||
"Bool": true
|
||||
},
|
||||
"TestBrickColor": {
|
||||
"BrickColor": 24
|
||||
},
|
||||
"TestColor3": {
|
||||
"Color3": [
|
||||
1.0,
|
||||
0.5,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"TestNumber": {
|
||||
"Float64": 1337.0
|
||||
},
|
||||
"TestRect": {
|
||||
"Rect": [
|
||||
[
|
||||
1.0,
|
||||
2.0
|
||||
],
|
||||
[
|
||||
3.0,
|
||||
4.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"TestString": {
|
||||
"String": "Test"
|
||||
},
|
||||
"TestUDim": {
|
||||
"UDim": [
|
||||
1.0,
|
||||
2
|
||||
]
|
||||
},
|
||||
"TestUDim2": {
|
||||
"UDim2": [
|
||||
[
|
||||
1.0,
|
||||
2
|
||||
],
|
||||
[
|
||||
3.0,
|
||||
4
|
||||
]
|
||||
]
|
||||
},
|
||||
"TestVector2": {
|
||||
"Vector2": [
|
||||
1.0,
|
||||
2.0
|
||||
]
|
||||
},
|
||||
"TestVector3": {
|
||||
"Vector3": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"ty": "Attributes"
|
||||
},
|
||||
"Axes": {
|
||||
"value": {
|
||||
"Axes": [
|
||||
"X",
|
||||
"Y",
|
||||
"Z"
|
||||
]
|
||||
},
|
||||
"ty": "Axes"
|
||||
},
|
||||
"BinaryString": {
|
||||
"value": {
|
||||
"BinaryString": "SGVsbG8h"
|
||||
},
|
||||
"ty": "BinaryString"
|
||||
},
|
||||
"Bool": {
|
||||
"value": {
|
||||
"Bool": true
|
||||
},
|
||||
"ty": "Bool"
|
||||
},
|
||||
"BrickColor": {
|
||||
"value": {
|
||||
"BrickColor": 1004
|
||||
},
|
||||
"ty": "BrickColor"
|
||||
},
|
||||
"CFrame": {
|
||||
"value": {
|
||||
"CFrame": {
|
||||
"position": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"orientation": [
|
||||
[
|
||||
4.0,
|
||||
5.0,
|
||||
6.0
|
||||
],
|
||||
[
|
||||
7.0,
|
||||
8.0,
|
||||
9.0
|
||||
],
|
||||
[
|
||||
10.0,
|
||||
11.0,
|
||||
12.0
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"ty": "CFrame"
|
||||
},
|
||||
"Color3": {
|
||||
"value": {
|
||||
"Color3": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
]
|
||||
},
|
||||
"ty": "Color3"
|
||||
},
|
||||
"Color3uint8": {
|
||||
"value": {
|
||||
"Color3uint8": [
|
||||
0,
|
||||
128,
|
||||
255
|
||||
]
|
||||
},
|
||||
"ty": "Color3uint8"
|
||||
},
|
||||
"ColorSequence": {
|
||||
"value": {
|
||||
"ColorSequence": {
|
||||
"keypoints": [
|
||||
{
|
||||
"time": 0.0,
|
||||
"color": [
|
||||
1.0,
|
||||
1.0,
|
||||
0.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"time": 1.0,
|
||||
"color": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ty": "ColorSequence"
|
||||
},
|
||||
"Content": {
|
||||
"value": {
|
||||
"Content": "rbxassetid://12345"
|
||||
},
|
||||
"ty": "Content"
|
||||
},
|
||||
"Enum": {
|
||||
"value": {
|
||||
"Enum": 1234
|
||||
},
|
||||
"ty": "Enum"
|
||||
},
|
||||
"Faces": {
|
||||
"value": {
|
||||
"Faces": [
|
||||
"Right",
|
||||
"Top",
|
||||
"Back",
|
||||
"Left",
|
||||
"Bottom",
|
||||
"Front"
|
||||
]
|
||||
},
|
||||
"ty": "Faces"
|
||||
},
|
||||
"Float32": {
|
||||
"value": {
|
||||
"Float32": 15.0
|
||||
},
|
||||
"ty": "Float32"
|
||||
},
|
||||
"Float64": {
|
||||
"value": {
|
||||
"Float64": 15123.0
|
||||
},
|
||||
"ty": "Float64"
|
||||
},
|
||||
"Int32": {
|
||||
"value": {
|
||||
"Int32": 6014
|
||||
},
|
||||
"ty": "Int32"
|
||||
},
|
||||
"Int64": {
|
||||
"value": {
|
||||
"Int64": 23491023
|
||||
},
|
||||
"ty": "Int64"
|
||||
},
|
||||
"NumberRange": {
|
||||
"value": {
|
||||
"NumberRange": [
|
||||
-36.0,
|
||||
94.0
|
||||
]
|
||||
},
|
||||
"ty": "NumberRange"
|
||||
},
|
||||
"NumberSequence": {
|
||||
"value": {
|
||||
"NumberSequence": {
|
||||
"keypoints": [
|
||||
{
|
||||
"time": 0.0,
|
||||
"value": 5.0,
|
||||
"envelope": 2.0
|
||||
},
|
||||
{
|
||||
"time": 1.0,
|
||||
"value": 22.0,
|
||||
"envelope": 0.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ty": "NumberSequence"
|
||||
},
|
||||
"PhysicalProperties-Custom": {
|
||||
"value": {
|
||||
"PhysicalProperties": {
|
||||
"density": 0.5,
|
||||
"friction": 1.0,
|
||||
"elasticity": 0.0,
|
||||
"frictionWeight": 50.0,
|
||||
"elasticityWeight": 25.0
|
||||
}
|
||||
},
|
||||
"ty": "PhysicalProperties"
|
||||
},
|
||||
"PhysicalProperties-Default": {
|
||||
"value": {
|
||||
"PhysicalProperties": "Default"
|
||||
},
|
||||
"ty": "PhysicalProperties"
|
||||
},
|
||||
"Ray": {
|
||||
"value": {
|
||||
"Ray": {
|
||||
"origin": [
|
||||
1.0,
|
||||
2.0,
|
||||
3.0
|
||||
],
|
||||
"direction": [
|
||||
4.0,
|
||||
5.0,
|
||||
6.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"ty": "Ray"
|
||||
},
|
||||
"Rect": {
|
||||
"value": {
|
||||
"Rect": [
|
||||
[
|
||||
0.0,
|
||||
5.0
|
||||
],
|
||||
[
|
||||
10.0,
|
||||
15.0
|
||||
]
|
||||
]
|
||||
},
|
||||
"ty": "Rect"
|
||||
},
|
||||
"Region3int16": {
|
||||
"value": {
|
||||
"Region3int16": [
|
||||
[
|
||||
-10,
|
||||
-5,
|
||||
0
|
||||
],
|
||||
[
|
||||
5,
|
||||
10,
|
||||
15
|
||||
]
|
||||
]
|
||||
},
|
||||
"ty": "Region3int16"
|
||||
},
|
||||
"String": {
|
||||
"value": {
|
||||
"String": "Hello, world!"
|
||||
},
|
||||
"ty": "String"
|
||||
},
|
||||
"Tags": {
|
||||
"value": {
|
||||
"Tags": [
|
||||
"foo",
|
||||
"con'fusion?!",
|
||||
"bar"
|
||||
]
|
||||
},
|
||||
"ty": "Tags"
|
||||
},
|
||||
"UDim": {
|
||||
"value": {
|
||||
"UDim": [
|
||||
1.0,
|
||||
32
|
||||
]
|
||||
},
|
||||
"ty": "UDim"
|
||||
},
|
||||
"UDim2": {
|
||||
"value": {
|
||||
"UDim2": [
|
||||
[
|
||||
-1.0,
|
||||
100
|
||||
],
|
||||
[
|
||||
1.0,
|
||||
-100
|
||||
]
|
||||
]
|
||||
},
|
||||
"ty": "UDim2"
|
||||
},
|
||||
"Vector2": {
|
||||
"value": {
|
||||
"Vector2": [
|
||||
-50.0,
|
||||
50.0
|
||||
]
|
||||
},
|
||||
"ty": "Vector2"
|
||||
},
|
||||
"Vector2int16": {
|
||||
"value": {
|
||||
"Vector2int16": [
|
||||
-300,
|
||||
300
|
||||
]
|
||||
},
|
||||
"ty": "Vector2int16"
|
||||
},
|
||||
"Vector3": {
|
||||
"value": {
|
||||
"Vector3": [
|
||||
-300.0,
|
||||
0.0,
|
||||
1500.0
|
||||
]
|
||||
},
|
||||
"ty": "Vector3"
|
||||
},
|
||||
"Vector3int16": {
|
||||
"value": {
|
||||
"Vector3int16": [
|
||||
60,
|
||||
37,
|
||||
-450
|
||||
]
|
||||
},
|
||||
"ty": "Vector3int16"
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,31 @@ local CollectionService = game:GetService("CollectionService")
|
||||
-- The reflection database refers to these as having scriptability = "Custom"
|
||||
return {
|
||||
Instance = {
|
||||
Tags = {
|
||||
read = function(instance, key)
|
||||
local tagList = CollectionService:GetTags(instance)
|
||||
|
||||
return true, table.concat(tagList, "\0")
|
||||
Attributes = {
|
||||
read = function(instance)
|
||||
return true, instance:GetAttributes()
|
||||
end,
|
||||
write = function(instance, key, value)
|
||||
write = function(instance, _, value)
|
||||
local existing = instance:GetAttributes()
|
||||
|
||||
for key, attr in pairs(value) do
|
||||
instance:SetAttribute(key, attr)
|
||||
end
|
||||
|
||||
for key in pairs(existing) do
|
||||
if value[key] == nil then
|
||||
instance:SetAttribute(key, nil)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
},
|
||||
Tags = {
|
||||
read = function(instance)
|
||||
return true, CollectionService:GetTags(instance)
|
||||
end,
|
||||
write = function(instance, _, value)
|
||||
local existingTags = CollectionService:GetTags(instance)
|
||||
|
||||
local unseenTags = {}
|
||||
@@ -19,8 +37,7 @@ return {
|
||||
unseenTags[tag] = true
|
||||
end
|
||||
|
||||
local tagList = string.split(value, "\0")
|
||||
for _, tag in ipairs(tagList) do
|
||||
for _, tag in ipairs(value) do
|
||||
unseenTags[tag] = nil
|
||||
CollectionService:AddTag(instance, tag)
|
||||
end
|
||||
@@ -44,4 +61,4 @@ return {
|
||||
end,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
51185
plugin/rbx_dom_lua/database.json
Normal file
51185
plugin/rbx_dom_lua/database.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
local ReflectionDatabase = require(script.ReflectionDatabase)
|
||||
local database = require(script.database)
|
||||
local Error = require(script.Error)
|
||||
local PropertyDescriptor = require(script.PropertyDescriptor)
|
||||
|
||||
@@ -6,29 +6,31 @@ local function findCanonicalPropertyDescriptor(className, propertyName)
|
||||
local currentClassName = className
|
||||
|
||||
repeat
|
||||
local currentClass = ReflectionDatabase.classes[currentClassName]
|
||||
local currentClass = database.Classes[currentClassName]
|
||||
|
||||
if currentClass == nil then
|
||||
return currentClass
|
||||
end
|
||||
|
||||
local propertyData = currentClass.properties[propertyName]
|
||||
local propertyData = currentClass.Properties[propertyName]
|
||||
if propertyData ~= nil then
|
||||
if propertyData.isCanonical then
|
||||
local canonicalData = propertyData.Kind.Canonical
|
||||
if canonicalData ~= nil then
|
||||
return PropertyDescriptor.fromRaw(propertyData, currentClassName, propertyName)
|
||||
end
|
||||
|
||||
if propertyData.canonicalName ~= nil then
|
||||
local aliasData = propertyData.Kind.Alias
|
||||
if aliasData ~= nil then
|
||||
return PropertyDescriptor.fromRaw(
|
||||
currentClass.properties[propertyData.canonicalName],
|
||||
currentClass.Properties[aliasData.AliasFor],
|
||||
currentClassName,
|
||||
propertyData.canonicalName)
|
||||
aliasData.AliasFor)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
currentClassName = currentClass.superclass
|
||||
currentClassName = currentClass.Superclass
|
||||
until currentClassName == nil
|
||||
|
||||
return nil
|
||||
@@ -64,4 +66,4 @@ return {
|
||||
findCanonicalPropertyDescriptor = findCanonicalPropertyDescriptor,
|
||||
Error = Error,
|
||||
EncodedValue = require(script.EncodedValue),
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
local base64 = require(script.Parent.base64)
|
||||
|
||||
local function identity(...)
|
||||
return ...
|
||||
end
|
||||
|
||||
local function unpackDecoder(f)
|
||||
return function(value)
|
||||
return f(unpack(value))
|
||||
end
|
||||
end
|
||||
|
||||
local function serializeFloat(value)
|
||||
-- TODO: Figure out a better way to serialize infinity and NaN, neither of
|
||||
-- which fit into JSON.
|
||||
if value == math.huge or value == -math.huge then
|
||||
return 999999999 * math.sign(value)
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
local encoders
|
||||
encoders = {
|
||||
Bool = identity,
|
||||
Content = identity,
|
||||
Float32 = serializeFloat,
|
||||
Float64 = serializeFloat,
|
||||
Int32 = identity,
|
||||
Int64 = identity,
|
||||
String = identity,
|
||||
|
||||
BinaryString = base64.encode,
|
||||
SharedString = base64.encode,
|
||||
|
||||
BrickColor = function(value)
|
||||
return value.Number
|
||||
end,
|
||||
|
||||
CFrame = function(value)
|
||||
return {value:GetComponents()}
|
||||
end,
|
||||
Color3 = function(value)
|
||||
return {value.r, value.g, value.b}
|
||||
end,
|
||||
NumberRange = function(value)
|
||||
return {value.Min, value.Max}
|
||||
end,
|
||||
NumberSequence = function(value)
|
||||
local keypoints = {}
|
||||
|
||||
for index, keypoint in ipairs(value.Keypoints) do
|
||||
keypoints[index] = {
|
||||
Time = keypoint.Time,
|
||||
Value = keypoint.Value,
|
||||
Envelope = keypoint.Envelope,
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
Keypoints = keypoints,
|
||||
}
|
||||
end,
|
||||
ColorSequence = function(value)
|
||||
local keypoints = {}
|
||||
|
||||
for index, keypoint in ipairs(value.Keypoints) do
|
||||
keypoints[index] = {
|
||||
Time = keypoint.Time,
|
||||
Color = encoders.Color3(keypoint.Value),
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
Keypoints = keypoints,
|
||||
}
|
||||
end,
|
||||
Rect = function(value)
|
||||
return {
|
||||
Min = {value.Min.X, value.Min.Y},
|
||||
Max = {value.Max.X, value.Max.Y},
|
||||
}
|
||||
end,
|
||||
UDim = function(value)
|
||||
return {value.Scale, value.Offset}
|
||||
end,
|
||||
UDim2 = function(value)
|
||||
return {value.X.Scale, value.X.Offset, value.Y.Scale, value.Y.Offset}
|
||||
end,
|
||||
Vector2 = function(value)
|
||||
return {
|
||||
serializeFloat(value.X),
|
||||
serializeFloat(value.Y),
|
||||
}
|
||||
end,
|
||||
Vector2int16 = function(value)
|
||||
return {value.X, value.Y}
|
||||
end,
|
||||
Vector3 = function(value)
|
||||
return {
|
||||
serializeFloat(value.X),
|
||||
serializeFloat(value.Y),
|
||||
serializeFloat(value.Z),
|
||||
}
|
||||
end,
|
||||
Vector3int16 = function(value)
|
||||
return {value.X, value.Y, value.Z}
|
||||
end,
|
||||
|
||||
PhysicalProperties = function(value)
|
||||
if value == nil then
|
||||
return nil
|
||||
else
|
||||
return {
|
||||
Density = value.Density,
|
||||
Friction = value.Friction,
|
||||
Elasticity = value.Elasticity,
|
||||
FrictionWeight = value.FrictionWeight,
|
||||
ElasticityWeight = value.ElasticityWeight,
|
||||
}
|
||||
end
|
||||
end,
|
||||
|
||||
Ref = function(value)
|
||||
return nil
|
||||
end,
|
||||
}
|
||||
|
||||
local decoders = {
|
||||
Bool = identity,
|
||||
Content = identity,
|
||||
Enum = identity,
|
||||
Float32 = identity,
|
||||
Float64 = identity,
|
||||
Int32 = identity,
|
||||
Int64 = identity,
|
||||
String = identity,
|
||||
|
||||
BinaryString = base64.decode,
|
||||
SharedString = base64.decode,
|
||||
|
||||
BrickColor = BrickColor.new,
|
||||
|
||||
CFrame = unpackDecoder(CFrame.new),
|
||||
Color3 = unpackDecoder(Color3.new),
|
||||
Color3uint8 = unpackDecoder(Color3.fromRGB),
|
||||
NumberRange = unpackDecoder(NumberRange.new),
|
||||
UDim = unpackDecoder(UDim.new),
|
||||
UDim2 = unpackDecoder(UDim2.new),
|
||||
Vector2 = unpackDecoder(Vector2.new),
|
||||
Vector2int16 = unpackDecoder(Vector2int16.new),
|
||||
Vector3 = unpackDecoder(Vector3.new),
|
||||
Vector3int16 = unpackDecoder(Vector3int16.new),
|
||||
|
||||
Rect = function(value)
|
||||
return Rect.new(value.Min[1], value.Min[2], value.Max[1], value.Max[2])
|
||||
end,
|
||||
|
||||
NumberSequence = function(value)
|
||||
local keypoints = {}
|
||||
|
||||
for index, keypoint in ipairs(value.Keypoints) do
|
||||
keypoints[index] = NumberSequenceKeypoint.new(
|
||||
keypoint.Time,
|
||||
keypoint.Value,
|
||||
keypoint.Envelope
|
||||
)
|
||||
end
|
||||
|
||||
return NumberSequence.new(keypoints)
|
||||
end,
|
||||
|
||||
ColorSequence = function(value)
|
||||
local keypoints = {}
|
||||
|
||||
for index, keypoint in ipairs(value.Keypoints) do
|
||||
keypoints[index] = ColorSequenceKeypoint.new(
|
||||
keypoint.Time,
|
||||
Color3.new(unpack(keypoint.Color))
|
||||
)
|
||||
end
|
||||
|
||||
return ColorSequence.new(keypoints)
|
||||
end,
|
||||
|
||||
PhysicalProperties = function(properties)
|
||||
if properties == nil then
|
||||
return nil
|
||||
else
|
||||
return PhysicalProperties.new(
|
||||
properties.Density,
|
||||
properties.Friction,
|
||||
properties.Elasticity,
|
||||
properties.FrictionWeight,
|
||||
properties.ElasticityWeight
|
||||
)
|
||||
end
|
||||
end,
|
||||
|
||||
Ref = function()
|
||||
return nil
|
||||
end,
|
||||
}
|
||||
|
||||
local EncodedValue = {}
|
||||
|
||||
function EncodedValue.decode(encodedValue)
|
||||
local decoder = decoders[encodedValue.Type]
|
||||
if decoder ~= nil then
|
||||
return true, decoder(encodedValue.Value)
|
||||
end
|
||||
|
||||
return false, "Couldn't decode value " .. tostring(encodedValue.Type)
|
||||
end
|
||||
|
||||
function EncodedValue.encode(rbxValue, propertyType)
|
||||
assert(propertyType ~= nil, "Property type descriptor is required")
|
||||
|
||||
if propertyType.type == "Data" then
|
||||
local encoder = encoders[propertyType.name]
|
||||
|
||||
if encoder == nil then
|
||||
return false, ("Missing encoder for property type %q"):format(propertyType.name)
|
||||
end
|
||||
|
||||
if encoder ~= nil then
|
||||
return true, {
|
||||
Type = propertyType.name,
|
||||
Value = encoder(rbxValue),
|
||||
}
|
||||
end
|
||||
elseif propertyType.type == "Enum" then
|
||||
return true, {
|
||||
Type = "Enum",
|
||||
Value = rbxValue.Value,
|
||||
}
|
||||
end
|
||||
|
||||
return false, ("Unknown property descriptor type %q"):format(tostring(propertyType.type))
|
||||
end
|
||||
|
||||
return EncodedValue
|
||||
@@ -1,127 +0,0 @@
|
||||
return function()
|
||||
local RbxDom = require(script.Parent)
|
||||
local EncodedValue = require(script.Parent.EncodedValue)
|
||||
|
||||
it("should decode Rect values", function()
|
||||
local input = {
|
||||
Type = "Rect",
|
||||
Value = {
|
||||
Min = {1, 2},
|
||||
Max = {3, 4},
|
||||
},
|
||||
}
|
||||
|
||||
local output = Rect.new(1, 2, 3, 4)
|
||||
|
||||
local ok, decoded = EncodedValue.decode(input)
|
||||
|
||||
assert(ok, decoded)
|
||||
expect(decoded).to.equal(output)
|
||||
end)
|
||||
|
||||
it("should decode ColorSequence values", function()
|
||||
local input = {
|
||||
Type = "ColorSequence",
|
||||
Value = {
|
||||
Keypoints = {
|
||||
{
|
||||
Time = 0,
|
||||
Color = { 0.12, 0.34, 0.56 },
|
||||
},
|
||||
|
||||
{
|
||||
Time = 1,
|
||||
Color = { 0.13, 0.33, 0.37 },
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
local output = ColorSequence.new({
|
||||
ColorSequenceKeypoint.new(0, Color3.new(0.12, 0.34, 0.56)),
|
||||
ColorSequenceKeypoint.new(1, Color3.new(0.13, 0.33, 0.37)),
|
||||
})
|
||||
|
||||
local ok, decoded = EncodedValue.decode(input)
|
||||
assert(ok, decoded)
|
||||
expect(decoded).to.equal(output)
|
||||
end)
|
||||
|
||||
it("should decode NumberSequence values", function()
|
||||
local input = {
|
||||
Type = "NumberSequence",
|
||||
Value = {
|
||||
Keypoints = {
|
||||
{
|
||||
Time = 0,
|
||||
Value = 0.5,
|
||||
Envelope = 0,
|
||||
},
|
||||
|
||||
{
|
||||
Time = 1,
|
||||
Value = 0.5,
|
||||
Envelope = 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
local output = NumberSequence.new({
|
||||
NumberSequenceKeypoint.new(0, 0.5, 0),
|
||||
NumberSequenceKeypoint.new(1, 0.5, 0),
|
||||
})
|
||||
|
||||
local ok, decoded = EncodedValue.decode(input)
|
||||
assert(ok, decoded)
|
||||
expect(decoded).to.equal(output)
|
||||
end)
|
||||
|
||||
it("should decode PhysicalProperties values", function()
|
||||
local input = {
|
||||
Type = "PhysicalProperties",
|
||||
Value = {
|
||||
Density = 0.1,
|
||||
Friction = 0.2,
|
||||
Elasticity = 0.3,
|
||||
FrictionWeight = 0.4,
|
||||
ElasticityWeight = 0.5,
|
||||
},
|
||||
}
|
||||
|
||||
local output = PhysicalProperties.new(
|
||||
0.1,
|
||||
0.2,
|
||||
0.3,
|
||||
0.4,
|
||||
0.5
|
||||
)
|
||||
|
||||
local ok, decoded = EncodedValue.decode(input)
|
||||
assert(ok, decoded)
|
||||
expect(decoded).to.equal(output)
|
||||
end)
|
||||
|
||||
-- This part of rbx_dom_lua needs some work still.
|
||||
itSKIP("should encode Rect values", function()
|
||||
local input = Rect.new(10, 20, 30, 40)
|
||||
|
||||
local output = {
|
||||
Type = "Rect",
|
||||
Value = {
|
||||
Min = {10, 20},
|
||||
Max = {30, 40},
|
||||
},
|
||||
}
|
||||
|
||||
local descriptor = RbxDom.findCanonicalPropertyDescriptor("ImageLabel", "SliceCenter")
|
||||
local ok, encoded = EncodedValue.encode(input, descriptor)
|
||||
|
||||
assert(ok, encoded)
|
||||
expect(encoded.Type).to.equal(output.Type)
|
||||
expect(encoded.Value.Min[1]).to.equal(output.Value.Min[1])
|
||||
expect(encoded.Value.Min[2]).to.equal(output.Value.Min[2])
|
||||
expect(encoded.Value.Max[1]).to.equal(output.Value.Max[1])
|
||||
expect(encoded.Value.Max[2]).to.equal(output.Value.Max[2])
|
||||
end)
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
return {
|
||||
classes = require(script.classes)
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"name": "rbx_dom_lua test place",
|
||||
"tree": {
|
||||
"$className": "DataModel",
|
||||
"ReplicatedStorage": {
|
||||
"$className": "ReplicatedStorage",
|
||||
|
||||
"RbxDom": {
|
||||
"$path": "src"
|
||||
},
|
||||
"TestEZ": {
|
||||
"$path": "modules/testez/lib"
|
||||
}
|
||||
},
|
||||
"ServerScriptService": {
|
||||
"$className": "ServerScriptService",
|
||||
|
||||
"Run Tests": {
|
||||
"$path": "test.server.lua"
|
||||
}
|
||||
},
|
||||
"Players": {
|
||||
"$className": "Players",
|
||||
"$properties": {
|
||||
"CharacterAutoLoads": false
|
||||
}
|
||||
},
|
||||
"HttpService": {
|
||||
"$className": "HttpService",
|
||||
"$properties": {
|
||||
"HttpEnabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local LIB_ROOT = ReplicatedStorage.RbxDom
|
||||
|
||||
local TestEZ = require(ReplicatedStorage.TestEZ)
|
||||
|
||||
TestEZ.TestBootstrap:run({LIB_ROOT})
|
||||
@@ -1,6 +1,6 @@
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local TestEZ = require(ReplicatedStorage.TestEZ)
|
||||
local TestEZ = require(ReplicatedStorage.Packages.TestEZ)
|
||||
|
||||
local Rojo = ReplicatedStorage.Rojo
|
||||
|
||||
@@ -16,4 +16,4 @@ require(Rojo.Plugin.runTests)(TestEZ)
|
||||
|
||||
if setDevSettings then
|
||||
DevSettings:resetValues()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
local Http = require(script.Parent.Parent.Http)
|
||||
local Log = require(script.Parent.Parent.Log)
|
||||
local Promise = require(script.Parent.Parent.Promise)
|
||||
local Packages = script.Parent.Parent.Packages
|
||||
local Http = require(Packages.Http)
|
||||
local Log = require(Packages.Log)
|
||||
local Promise = require(Packages.Promise)
|
||||
|
||||
local Config = require(script.Parent.Config)
|
||||
local Types = require(script.Parent.Types)
|
||||
@@ -85,7 +86,7 @@ local ApiContext = {}
|
||||
ApiContext.__index = ApiContext
|
||||
|
||||
function ApiContext.new(baseUrl)
|
||||
assert(type(baseUrl) == "string")
|
||||
assert(type(baseUrl) == "string", "baseUrl must be a string")
|
||||
|
||||
local self = {
|
||||
__baseUrl = baseUrl,
|
||||
@@ -248,4 +249,4 @@ function ApiContext:open(id)
|
||||
end)
|
||||
end
|
||||
|
||||
return ApiContext
|
||||
return ApiContext
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
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 Assets = require(Plugin.Assets)
|
||||
@@ -38,4 +39,4 @@ local function BorderedContainer(props)
|
||||
end)
|
||||
end
|
||||
|
||||
return BorderedContainer
|
||||
return BorderedContainer
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
local Roact = require(Packages.Roact)
|
||||
local Flipper = require(Packages.Flipper)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
@@ -93,4 +94,4 @@ function Checkbox:render()
|
||||
end)
|
||||
end
|
||||
|
||||
return Checkbox
|
||||
return Checkbox
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
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 Assets = require(Plugin.Assets)
|
||||
@@ -52,4 +53,4 @@ local function Header(props)
|
||||
end)
|
||||
end
|
||||
|
||||
return Header
|
||||
return Header
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
local Roact = require(Packages.Roact)
|
||||
local Flipper = require(Packages.Flipper)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
@@ -76,4 +77,4 @@ function IconButton:render()
|
||||
})
|
||||
end
|
||||
|
||||
return IconButton
|
||||
return IconButton
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
@@ -39,4 +40,4 @@ local function ScrollingFrame(props)
|
||||
end)
|
||||
end
|
||||
|
||||
return ScrollingFrame
|
||||
return ScrollingFrame
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
@@ -26,4 +27,4 @@ local function SlicedImage(props)
|
||||
}, props[Roact.Children])
|
||||
end
|
||||
|
||||
return SlicedImage
|
||||
return SlicedImage
|
||||
|
||||
@@ -2,8 +2,9 @@ local RunService = game:GetService("RunService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
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 Assets = require(Plugin.Assets)
|
||||
@@ -63,4 +64,4 @@ function Spinner:willUnmount()
|
||||
self.stepper:Disconnect()
|
||||
end
|
||||
|
||||
return Spinner
|
||||
return Spinner
|
||||
|
||||
41
plugin/src/App/Components/Studio/StudioPluginAction.lua
Normal file
41
plugin/src/App/Components/Studio/StudioPluginAction.lua
Normal file
@@ -0,0 +1,41 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
local StudioPluginContext = require(script.Parent.StudioPluginContext)
|
||||
|
||||
local e = Roact.createElement
|
||||
|
||||
local StudioPluginAction = Roact.Component:extend("StudioPluginAction")
|
||||
|
||||
function StudioPluginAction:init()
|
||||
self.pluginAction = self.props.plugin:CreatePluginAction(
|
||||
self.props.name, self.props.title, self.props.description, self.props.icon, self.props.bindable
|
||||
)
|
||||
|
||||
self.pluginAction.Triggered:Connect(self.props.onTriggered)
|
||||
end
|
||||
|
||||
function StudioPluginAction:render()
|
||||
return nil
|
||||
end
|
||||
|
||||
function StudioPluginAction:willUnmount()
|
||||
self.pluginAction:Destroy()
|
||||
end
|
||||
|
||||
local function StudioPluginActionWrapper(props)
|
||||
return e(StudioPluginContext.Consumer, {
|
||||
render = function(plugin)
|
||||
return e(StudioPluginAction, Dictionary.merge(props, {
|
||||
plugin = plugin,
|
||||
}))
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return StudioPluginActionWrapper
|
||||
@@ -1,7 +1,8 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local StudioPluginContext = Roact.createContext(nil)
|
||||
|
||||
return StudioPluginContext
|
||||
return StudioPluginContext
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
@@ -81,4 +82,4 @@ local function StudioPluginGuiWrapper(props)
|
||||
})
|
||||
end
|
||||
|
||||
return StudioPluginGuiWrapper
|
||||
return StudioPluginGuiWrapper
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
@@ -44,6 +45,10 @@ function StudioToggleButton:didUpdate(lastProps)
|
||||
self.button.Enabled = self.props.enabled
|
||||
end
|
||||
|
||||
if self.props.icon ~= lastProps.icon then
|
||||
self.button.Icon = self.props.icon
|
||||
end
|
||||
|
||||
if self.props.active ~= lastProps.active then
|
||||
self.button:SetActive(self.props.active)
|
||||
end
|
||||
@@ -63,4 +68,4 @@ local function StudioToggleButtonWrapper(props)
|
||||
})
|
||||
end
|
||||
|
||||
return StudioToggleButtonWrapper
|
||||
return StudioToggleButtonWrapper
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
@@ -42,4 +43,4 @@ local function StudioToolbarWrapper(props)
|
||||
})
|
||||
end
|
||||
|
||||
return StudioToolbarWrapper
|
||||
return StudioToolbarWrapper
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local StudioToolbarContext = Roact.createContext(nil)
|
||||
|
||||
return StudioToolbarContext
|
||||
return StudioToolbarContext
|
||||
|
||||
@@ -2,9 +2,10 @@ local TextService = game:GetService("TextService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
local Roact = require(Packages.Roact)
|
||||
local Flipper = require(Packages.Flipper)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
@@ -134,4 +135,4 @@ function TextButton:render()
|
||||
end)
|
||||
end
|
||||
|
||||
return TextButton
|
||||
return TextButton
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
local Roact = require(Packages.Roact)
|
||||
local Flipper = require(Packages.Flipper)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||
@@ -142,4 +143,4 @@ function TouchRipple:render()
|
||||
})
|
||||
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 Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Flipper = require(Rojo.Flipper)
|
||||
local Roact = require(Packages.Roact)
|
||||
local Flipper = require(Packages.Flipper)
|
||||
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
|
||||
@@ -67,4 +68,4 @@ function Page:didUpdate(lastProps)
|
||||
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 Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
local Assets = require(Plugin.Assets)
|
||||
@@ -12,6 +13,26 @@ local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||
|
||||
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)
|
||||
return Theme.with(function(theme)
|
||||
return e(BorderedContainer, {
|
||||
@@ -82,33 +103,59 @@ end
|
||||
local ConnectedPage = Roact.Component:extend("ConnectedPage")
|
||||
|
||||
function ConnectedPage:render()
|
||||
return Roact.createFragment({
|
||||
Header = e(Header, {
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
}),
|
||||
return Theme.with(function(theme)
|
||||
return Roact.createFragment({
|
||||
Header = e(Header, {
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 1,
|
||||
}),
|
||||
|
||||
ConnectionDetails = e(ConnectionDetails, {
|
||||
projectName = self.state.projectName,
|
||||
address = self.state.address,
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
ConnectionDetails = e(ConnectionDetails, {
|
||||
projectName = self.state.projectName,
|
||||
address = self.state.address,
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
|
||||
onDisconnect = self.props.onDisconnect,
|
||||
}),
|
||||
onDisconnect = self.props.onDisconnect,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||
FillDirection = Enum.FillDirection.Vertical,
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
Padding = UDim.new(0, 10),
|
||||
}),
|
||||
Info = e("TextLabel", {
|
||||
Text = self.props.patchInfo:map(function(info)
|
||||
return string.format(
|
||||
"<i>Synced %d change%s %s</i>",
|
||||
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", {
|
||||
PaddingLeft = UDim.new(0, 20),
|
||||
PaddingRight = UDim.new(0, 20),
|
||||
}),
|
||||
})
|
||||
Size = UDim2.new(1, 0, 0, 28),
|
||||
|
||||
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
|
||||
|
||||
function ConnectedPage.getDerivedStateFromProps(props)
|
||||
@@ -122,4 +169,4 @@ function ConnectedPage.getDerivedStateFromProps(props)
|
||||
}
|
||||
end
|
||||
|
||||
return ConnectedPage
|
||||
return ConnectedPage
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
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)
|
||||
|
||||
@@ -17,4 +18,4 @@ function ConnectingPage:render()
|
||||
})
|
||||
end
|
||||
|
||||
return ConnectingPage
|
||||
return ConnectingPage
|
||||
|
||||
@@ -2,8 +2,9 @@ local TextService = game:GetService("TextService")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local Theme = require(Plugin.App.Theme)
|
||||
|
||||
@@ -56,8 +57,16 @@ function Error:render()
|
||||
end,
|
||||
}, {
|
||||
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,
|
||||
TextEditable = false,
|
||||
Font = Enum.Font.Code,
|
||||
TextSize = 16,
|
||||
TextColor3 = theme.ErrorColor,
|
||||
@@ -65,10 +74,9 @@ function Error:render()
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
TextTransparency = self.props.transparency,
|
||||
TextWrapped = true,
|
||||
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
|
||||
ClearTextOnFocus = false,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
})
|
||||
end),
|
||||
|
||||
@@ -150,4 +158,4 @@ function ErrorPage.getDerivedStateFromProps(props)
|
||||
}
|
||||
end
|
||||
|
||||
return ErrorPage
|
||||
return ErrorPage
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Roact = require(Packages.Roact)
|
||||
|
||||
local Config = require(Plugin.Config)
|
||||
|
||||
@@ -24,7 +25,7 @@ local function AddressEntry(props)
|
||||
layoutOrder = props.layoutOrder,
|
||||
}, {
|
||||
Host = e("TextBox", {
|
||||
Text = "",
|
||||
Text = props.host or "",
|
||||
Font = Enum.Font.Code,
|
||||
TextSize = 18,
|
||||
TextColor3 = theme.AddressEntry.TextColor,
|
||||
@@ -32,6 +33,7 @@ local function AddressEntry(props)
|
||||
TextTransparency = props.transparency,
|
||||
PlaceholderText = Config.defaultHost,
|
||||
PlaceholderColor3 = theme.AddressEntry.PlaceholderColor,
|
||||
ClearTextOnFocus = false,
|
||||
|
||||
Size = UDim2.new(1, -(HOST_OFFSET + DIVIDER_WIDTH + PORT_WIDTH), 1, 0),
|
||||
Position = UDim2.new(0, HOST_OFFSET, 0, 0),
|
||||
@@ -39,17 +41,22 @@ local function AddressEntry(props)
|
||||
ClipsDescendants = true,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Ref] = props.hostRef,
|
||||
[Roact.Change.Text] = function(object)
|
||||
if props.onHostChange ~= nil then
|
||||
props.onHostChange(object.Text)
|
||||
end
|
||||
end
|
||||
}),
|
||||
|
||||
Port = e("TextBox", {
|
||||
Text = "",
|
||||
Text = props.port or "",
|
||||
Font = Enum.Font.Code,
|
||||
TextSize = 18,
|
||||
TextColor3 = theme.AddressEntry.TextColor,
|
||||
TextTransparency = props.transparency,
|
||||
PlaceholderText = Config.defaultPort,
|
||||
PlaceholderColor3 = theme.AddressEntry.PlaceholderColor,
|
||||
ClearTextOnFocus = false,
|
||||
|
||||
Size = UDim2.new(0, PORT_WIDTH, 1, 0),
|
||||
Position = UDim2.new(1, 0, 0, 0),
|
||||
@@ -58,12 +65,14 @@ local function AddressEntry(props)
|
||||
ClipsDescendants = true,
|
||||
BackgroundTransparency = 1,
|
||||
|
||||
[Roact.Ref] = props.portRef,
|
||||
|
||||
[Roact.Change.Text] = function(object)
|
||||
local text = object.Text
|
||||
text = text:gsub("%D", "")
|
||||
object.Text = text
|
||||
|
||||
if props.onPortChange ~= nil then
|
||||
props.onPortChange(text)
|
||||
end
|
||||
end,
|
||||
}, {
|
||||
Divider = e("Frame", {
|
||||
@@ -80,11 +89,6 @@ end
|
||||
|
||||
local NotConnectedPage = Roact.Component:extend("NotConnectedPage")
|
||||
|
||||
function NotConnectedPage:init()
|
||||
self.hostRef = Roact.createRef()
|
||||
self.portRef = Roact.createRef()
|
||||
end
|
||||
|
||||
function NotConnectedPage:render()
|
||||
return Roact.createFragment({
|
||||
Header = e(Header, {
|
||||
@@ -93,8 +97,10 @@ function NotConnectedPage:render()
|
||||
}),
|
||||
|
||||
AddressEntry = e(AddressEntry, {
|
||||
hostRef = self.hostRef,
|
||||
portRef = self.portRef,
|
||||
host = self.props.host,
|
||||
port = self.props.port,
|
||||
onHostChange = self.props.onHostChange,
|
||||
onPortChange = self.props.onPortChange,
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
}),
|
||||
@@ -117,15 +123,7 @@ function NotConnectedPage:render()
|
||||
style = "Solid",
|
||||
transparency = self.props.transparency,
|
||||
layoutOrder = 2,
|
||||
onClick = function()
|
||||
local hostText = self.hostRef.current.Text
|
||||
local portText = self.portRef.current.Text
|
||||
|
||||
self.props.onConnect(
|
||||
#hostText > 0 and hostText or Config.defaultHost,
|
||||
#portText > 0 and portText or Config.defaultPort
|
||||
)
|
||||
end,
|
||||
onClick = self.props.onConnect,
|
||||
}),
|
||||
|
||||
Layout = e("UIListLayout", {
|
||||
@@ -151,4 +149,4 @@ function NotConnectedPage:render()
|
||||
})
|
||||
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 = "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
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Log = require(Rojo.Log)
|
||||
local Roact = require(Packages.Roact)
|
||||
local Log = require(Packages.Log)
|
||||
|
||||
local strict = require(script.Parent.Parent.strict)
|
||||
|
||||
@@ -34,7 +35,7 @@ end
|
||||
local BRAND_COLOR = hexColor(0xE13835)
|
||||
|
||||
local lightTheme = strict("LightTheme", {
|
||||
BackgroundColor = hexColor(0xF0F0F0),
|
||||
BackgroundColor = hexColor(0xFFFFFF),
|
||||
Button = {
|
||||
Solid = {
|
||||
ActionFillColor = hexColor(0xFFFFFF),
|
||||
@@ -67,7 +68,7 @@ local lightTheme = strict("LightTheme", {
|
||||
BackgroundColor = BRAND_COLOR,
|
||||
},
|
||||
Inactive = {
|
||||
IconColor = hexColor(0xCACACA),
|
||||
IconColor = hexColor(0xEEEEEE),
|
||||
BorderColor = hexColor(0xAFAFAF),
|
||||
},
|
||||
},
|
||||
@@ -77,11 +78,11 @@ local lightTheme = strict("LightTheme", {
|
||||
},
|
||||
BorderedContainer = {
|
||||
BorderColor = hexColor(0xCBCBCB),
|
||||
BackgroundColor = hexColor(0xE0E0E0),
|
||||
BackgroundColor = hexColor(0xEEEEEE),
|
||||
},
|
||||
Spinner = {
|
||||
ForegroundColor = BRAND_COLOR,
|
||||
BackgroundColor = hexColor(0xE0E0E0),
|
||||
BackgroundColor = hexColor(0xEEEEEE),
|
||||
},
|
||||
ConnectionDetails = {
|
||||
ProjectNameColor = hexColor(0x00000),
|
||||
@@ -103,12 +104,16 @@ local lightTheme = strict("LightTheme", {
|
||||
LogoColor = BRAND_COLOR,
|
||||
VersionColor = hexColor(0x727272),
|
||||
},
|
||||
Notification = {
|
||||
InfoColor = hexColor(0x00000),
|
||||
CloseColor = BRAND_COLOR,
|
||||
},
|
||||
ErrorColor = hexColor(0x000000),
|
||||
ScrollBarColor = hexColor(0x000000),
|
||||
})
|
||||
|
||||
local darkTheme = strict("DarkTheme", {
|
||||
BackgroundColor = hexColor(0x272727),
|
||||
BackgroundColor = hexColor(0x2E2E2E),
|
||||
Button = {
|
||||
Solid = {
|
||||
ActionFillColor = hexColor(0xFFFFFF),
|
||||
@@ -147,15 +152,15 @@ local darkTheme = strict("DarkTheme", {
|
||||
},
|
||||
AddressEntry = {
|
||||
TextColor = hexColor(0xFFFFFF),
|
||||
PlaceholderColor = hexColor(0x717171)
|
||||
PlaceholderColor = hexColor(0x8B8B8B)
|
||||
},
|
||||
BorderedContainer = {
|
||||
BorderColor = hexColor(0x535353),
|
||||
BackgroundColor = hexColor(0x323232),
|
||||
BackgroundColor = hexColor(0x2B2B2B),
|
||||
},
|
||||
Spinner = {
|
||||
ForegroundColor = BRAND_COLOR,
|
||||
BackgroundColor = hexColor(0x323232),
|
||||
BackgroundColor = hexColor(0x2B2B2B),
|
||||
},
|
||||
ConnectionDetails = {
|
||||
ProjectNameColor = hexColor(0xFFFFFF),
|
||||
@@ -177,6 +182,10 @@ local darkTheme = strict("DarkTheme", {
|
||||
LogoColor = BRAND_COLOR,
|
||||
VersionColor = hexColor(0xD3D3D3)
|
||||
},
|
||||
Notification = {
|
||||
InfoColor = hexColor(0xFFFFFF),
|
||||
CloseColor = hexColor(0xFFFFFF),
|
||||
},
|
||||
ErrorColor = hexColor(0xFFFFFF),
|
||||
ScrollBarColor = hexColor(0xFFFFFF),
|
||||
})
|
||||
@@ -236,4 +245,4 @@ return {
|
||||
StudioProvider = StudioProvider,
|
||||
Consumer = Context.Consumer,
|
||||
with = with,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Log = require(Rojo.Log)
|
||||
local Roact = require(Packages.Roact)
|
||||
local Log = require(Packages.Log)
|
||||
|
||||
local LERP_DATA_TYPES = {
|
||||
Color3 = true,
|
||||
@@ -55,4 +56,4 @@ return {
|
||||
mapLerp = mapLerp,
|
||||
deriveProperty = deriveProperty,
|
||||
blendAlpha = blendAlpha,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
local Players = game:GetService("Players")
|
||||
local ServerStorage = game:GetService("ServerStorage")
|
||||
|
||||
local Rojo = script:FindFirstAncestor("Rojo")
|
||||
local Plugin = Rojo.Plugin
|
||||
local Packages = Rojo.Packages
|
||||
|
||||
local Roact = require(Rojo.Roact)
|
||||
local Log = require(Rojo.Log)
|
||||
local Roact = require(Packages.Roact)
|
||||
local Log = require(Packages.Log)
|
||||
|
||||
local Assets = require(Plugin.Assets)
|
||||
local Version = require(Plugin.Version)
|
||||
local Config = require(Plugin.Config)
|
||||
local Settings = require(Plugin.Settings)
|
||||
local strict = require(Plugin.strict)
|
||||
local Dictionary = require(Plugin.Dictionary)
|
||||
local ServeSession = require(Plugin.ServeSession)
|
||||
local ApiContext = require(Plugin.ApiContext)
|
||||
local preloadAssets = require(Plugin.preloadAssets)
|
||||
local soundPlayer = require(Plugin.soundPlayer)
|
||||
local Theme = require(script.Theme)
|
||||
local PluginSettings = require(script.PluginSettings)
|
||||
|
||||
local Page = require(script.Page)
|
||||
local Notifications = require(script.Notifications)
|
||||
local StudioPluginAction = require(script.Components.Studio.StudioPluginAction)
|
||||
local StudioToolbar = require(script.Components.Studio.StudioToolbar)
|
||||
local StudioToggleButton = require(script.Components.Studio.StudioToggleButton)
|
||||
local StudioPluginGui = require(script.Components.Studio.StudioPluginGui)
|
||||
@@ -37,13 +44,123 @@ local App = Roact.Component:extend("App")
|
||||
function App:init()
|
||||
preloadAssets()
|
||||
|
||||
self.host, self.setHost = Roact.createBinding("")
|
||||
self.port, self.setPort = Roact.createBinding("")
|
||||
self.patchInfo, self.setPatchInfo = Roact.createBinding({
|
||||
changes = 0,
|
||||
timestamp = os.time(),
|
||||
})
|
||||
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
guiEnabled = false,
|
||||
notifications = {},
|
||||
toolbarIcon = Assets.Images.PluginButton,
|
||||
})
|
||||
end
|
||||
|
||||
function App:startSession(host, port, sessionOptions)
|
||||
function App:addNotification(text: string, timeout: number?)
|
||||
if not Settings:get("showNotifications") then
|
||||
return
|
||||
end
|
||||
|
||||
local notifications = table.clone(self.state.notifications)
|
||||
table.insert(notifications, {
|
||||
text = text,
|
||||
timestamp = DateTime.now().UnixTimestampMillis,
|
||||
timeout = timeout or 3,
|
||||
})
|
||||
|
||||
self:setState({
|
||||
notifications = notifications,
|
||||
})
|
||||
end
|
||||
|
||||
function App:closeNotification(index: number)
|
||||
local notifications = table.clone(self.state.notifications)
|
||||
table.remove(notifications, index)
|
||||
|
||||
self:setState({
|
||||
notifications = notifications,
|
||||
})
|
||||
end
|
||||
|
||||
function App:getHostAndPort()
|
||||
local host = self.host:getValue()
|
||||
local port = self.port:getValue()
|
||||
|
||||
local host = if #host > 0 then host else Config.defaultHost
|
||||
local port = if #port > 0 then port else Config.defaultPort
|
||||
|
||||
return host, port
|
||||
end
|
||||
|
||||
function App: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()
|
||||
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 sessionOptions = {
|
||||
openScriptsExternally = Settings:get("openScriptsExternally"),
|
||||
twoWaySync = Settings:get("twoWaySync"),
|
||||
}
|
||||
|
||||
local baseUrl = ("http://%s:%s"):format(host, port)
|
||||
local apiContext = ApiContext.new(baseUrl)
|
||||
|
||||
@@ -53,20 +170,53 @@ function App:startSession(host, port, sessionOptions)
|
||||
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)
|
||||
if status == ServeSession.Status.Connecting then
|
||||
self:setState({
|
||||
appStatus = AppStatus.Connecting,
|
||||
toolbarIcon = Assets.Images.PluginButton,
|
||||
})
|
||||
self:addNotification("Connecting to session...")
|
||||
elseif status == ServeSession.Status.Connected then
|
||||
local address = ("%s:%s"):format(host, port)
|
||||
self:setState({
|
||||
appStatus = AppStatus.Connected,
|
||||
projectName = details,
|
||||
address = address,
|
||||
toolbarIcon = Assets.Images.PluginButtonConnected,
|
||||
})
|
||||
self:addNotification(string.format("Connected to session '%s' at %s.", details, address), 5)
|
||||
elseif status == ServeSession.Status.Disconnected then
|
||||
self.serveSession = nil
|
||||
self:releaseSyncLock()
|
||||
|
||||
-- Details being present indicates that this
|
||||
-- disconnection was from an error.
|
||||
@@ -76,11 +226,15 @@ function App:startSession(host, port, sessionOptions)
|
||||
self:setState({
|
||||
appStatus = AppStatus.Error,
|
||||
errorMessage = tostring(details),
|
||||
toolbarIcon = Assets.Images.PluginButtonWarning,
|
||||
})
|
||||
self:addNotification(tostring(details), 10)
|
||||
else
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
toolbarIcon = Assets.Images.PluginButton,
|
||||
})
|
||||
self:addNotification("Disconnected from session.")
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -88,6 +242,32 @@ function App:startSession(host, port, sessionOptions)
|
||||
serveSession:start()
|
||||
|
||||
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
|
||||
|
||||
function App:endSession()
|
||||
if self.serveSession == nil then
|
||||
return
|
||||
end
|
||||
|
||||
Log.trace("Disconnecting session")
|
||||
|
||||
self.serveSession:stop()
|
||||
self.serveSession = nil
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
})
|
||||
|
||||
Log.trace("Session terminated by user")
|
||||
end
|
||||
|
||||
function App:render()
|
||||
@@ -108,119 +288,178 @@ function App:render()
|
||||
value = self.props.plugin,
|
||||
}, {
|
||||
e(Theme.StudioProvider, nil, {
|
||||
e(PluginSettings.StudioProvider, {
|
||||
plugin = self.props.plugin,
|
||||
gui = e(StudioPluginGui, {
|
||||
id = pluginName,
|
||||
title = pluginName,
|
||||
active = self.state.guiEnabled,
|
||||
|
||||
initDockState = Enum.InitialDockState.Right,
|
||||
initEnabled = false,
|
||||
overridePreviousState = false,
|
||||
floatingSize = Vector2.new(300, 200),
|
||||
minimumSize = Vector2.new(300, 200),
|
||||
|
||||
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
|
||||
onInitialState = function(initialState)
|
||||
self:setState({
|
||||
guiEnabled = initialState,
|
||||
})
|
||||
end,
|
||||
|
||||
onClose = function()
|
||||
self:setState({
|
||||
guiEnabled = false,
|
||||
})
|
||||
end,
|
||||
}, {
|
||||
gui = e(StudioPluginGui, {
|
||||
id = pluginName,
|
||||
title = pluginName,
|
||||
active = self.state.guiEnabled,
|
||||
NotConnectedPage = createPageElement(AppStatus.NotConnected, {
|
||||
host = self.host,
|
||||
onHostChange = self.setHost,
|
||||
port = self.port,
|
||||
onPortChange = self.setPort,
|
||||
|
||||
initDockState = Enum.InitialDockState.Right,
|
||||
initEnabled = false,
|
||||
overridePreviousState = false,
|
||||
floatingSize = Vector2.new(300, 200),
|
||||
minimumSize = Vector2.new(300, 200),
|
||||
onConnect = function()
|
||||
self:startSession()
|
||||
end,
|
||||
|
||||
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||
|
||||
onInitialState = function(initialState)
|
||||
onNavigateSettings = function()
|
||||
self:setState({
|
||||
guiEnabled = initialState,
|
||||
appStatus = AppStatus.Settings,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
|
||||
Connecting = createPageElement(AppStatus.Connecting),
|
||||
|
||||
Connected = createPageElement(AppStatus.Connected, {
|
||||
projectName = self.state.projectName,
|
||||
address = self.state.address,
|
||||
patchInfo = self.patchInfo,
|
||||
|
||||
onDisconnect = function()
|
||||
self:endSession()
|
||||
end,
|
||||
}),
|
||||
|
||||
Settings = createPageElement(AppStatus.Settings, {
|
||||
onBack = function()
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
|
||||
Error = createPageElement(AppStatus.Error, {
|
||||
errorMessage = self.state.errorMessage,
|
||||
|
||||
onClose = function()
|
||||
self:setState({
|
||||
guiEnabled = false,
|
||||
appStatus = AppStatus.NotConnected,
|
||||
toolbarIcon = Assets.Images.PluginButton,
|
||||
})
|
||||
end,
|
||||
}, {
|
||||
NotConnectedPage = PluginSettings.with(function(settings)
|
||||
return createPageElement(AppStatus.NotConnected, {
|
||||
onConnect = function(host, port)
|
||||
self:startSession(host, port, {
|
||||
openScriptsExternally = settings:get("openScriptsExternally"),
|
||||
twoWaySync = settings:get("twoWaySync"),
|
||||
})
|
||||
end,
|
||||
|
||||
onNavigateSettings = function()
|
||||
self:setState({
|
||||
appStatus = AppStatus.Settings,
|
||||
})
|
||||
end,
|
||||
})
|
||||
end),
|
||||
|
||||
Connecting = createPageElement(AppStatus.Connecting),
|
||||
|
||||
Connected = createPageElement(AppStatus.Connected, {
|
||||
projectName = self.state.projectName,
|
||||
address = self.state.address,
|
||||
|
||||
onDisconnect = function()
|
||||
Log.trace("Disconnecting session")
|
||||
|
||||
self.serveSession:stop()
|
||||
self.serveSession = nil
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
})
|
||||
|
||||
Log.trace("Session terminated by user")
|
||||
end,
|
||||
}),
|
||||
|
||||
Settings = createPageElement(AppStatus.Settings, {
|
||||
onBack = function()
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
|
||||
Error = createPageElement(AppStatus.Error, {
|
||||
errorMessage = self.state.errorMessage,
|
||||
|
||||
onClose = function()
|
||||
self:setState({
|
||||
appStatus = AppStatus.NotConnected,
|
||||
})
|
||||
end,
|
||||
}),
|
||||
|
||||
Background = Theme.with(function(theme)
|
||||
return e("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundColor3 = theme.BackgroundColor,
|
||||
ZIndex = 0,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
end),
|
||||
}),
|
||||
|
||||
toolbar = e(StudioToolbar, {
|
||||
name = pluginName,
|
||||
}, {
|
||||
button = e(StudioToggleButton, {
|
||||
name = "Rojo",
|
||||
tooltip = "Show or hide the Rojo panel",
|
||||
icon = Assets.Images.PluginButton,
|
||||
active = self.state.guiEnabled,
|
||||
enabled = true,
|
||||
onClick = function()
|
||||
self:setState(function(state)
|
||||
return {
|
||||
guiEnabled = not state.guiEnabled,
|
||||
}
|
||||
end)
|
||||
end,
|
||||
Background = Theme.with(function(theme)
|
||||
return e("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundColor3 = theme.BackgroundColor,
|
||||
ZIndex = 0,
|
||||
BorderSizePixel = 0,
|
||||
})
|
||||
end),
|
||||
}),
|
||||
|
||||
RojoNotifications = e("ScreenGui", {}, {
|
||||
layout = e("UIListLayout", {
|
||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||
VerticalAlignment = Enum.VerticalAlignment.Bottom,
|
||||
Padding = UDim.new(0, 5),
|
||||
}),
|
||||
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, {
|
||||
name = "RojoConnection",
|
||||
title = "Rojo: Connect/Disconnect",
|
||||
description = "Toggles the server for a Rojo sync session",
|
||||
icon = Assets.Images.PluginButton,
|
||||
bindable = true,
|
||||
onTriggered = function()
|
||||
if self.serveSession == nil or self.serveSession:getStatus() == ServeSession.Status.NotStarted then
|
||||
self:startSession()
|
||||
elseif self.serveSession ~= nil and self.serveSession:getStatus() == ServeSession.Status.Connected then
|
||||
self:endSession()
|
||||
end
|
||||
end,
|
||||
}),
|
||||
|
||||
connectAction = e(StudioPluginAction, {
|
||||
name = "RojoConnect",
|
||||
title = "Rojo: Connect",
|
||||
description = "Connects the server for a Rojo sync session",
|
||||
icon = Assets.Images.PluginButton,
|
||||
bindable = true,
|
||||
onTriggered = function()
|
||||
if self.serveSession == nil or self.serveSession:getStatus() == ServeSession.Status.NotStarted then
|
||||
self:startSession()
|
||||
end
|
||||
end,
|
||||
}),
|
||||
|
||||
disconnectAction = e(StudioPluginAction, {
|
||||
name = "RojoDisconnect",
|
||||
title = "Rojo: Disconnect",
|
||||
description = "Disconnects the server for a Rojo sync session",
|
||||
icon = Assets.Images.PluginButton,
|
||||
bindable = true,
|
||||
onTriggered = function()
|
||||
if self.serveSession ~= nil and self.serveSession:getStatus() == ServeSession.Status.Connected then
|
||||
self:endSession()
|
||||
end
|
||||
end,
|
||||
}),
|
||||
|
||||
toolbar = e(StudioToolbar, {
|
||||
name = pluginName,
|
||||
}, {
|
||||
button = e(StudioToggleButton, {
|
||||
name = "Rojo",
|
||||
tooltip = "Show or hide the Rojo panel",
|
||||
icon = self.state.toolbarIcon,
|
||||
active = self.state.guiEnabled,
|
||||
enabled = true,
|
||||
onClick = function()
|
||||
self:setState(function(state)
|
||||
return {
|
||||
guiEnabled = not state.guiEnabled,
|
||||
}
|
||||
end)
|
||||
end,
|
||||
})
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return App
|
||||
return function(props)
|
||||
local mergedProps = Dictionary.merge(props, {
|
||||
soundPlayer = soundPlayer.new(Settings),
|
||||
})
|
||||
|
||||
return e(App, mergedProps)
|
||||
end
|
||||
|
||||
@@ -18,6 +18,8 @@ local Assets = {
|
||||
Images = {
|
||||
Logo = "rbxassetid://5990772764",
|
||||
PluginButton = "rbxassetid://3405341609",
|
||||
PluginButtonConnected = "rbxassetid://9529783993",
|
||||
PluginButtonWarning = "rbxassetid://9529784530",
|
||||
Icons = {
|
||||
Close = "rbxassetid://6012985953",
|
||||
Back = "rbxassetid://6017213752",
|
||||
@@ -43,6 +45,9 @@ local Assets = {
|
||||
[500] = "rbxassetid://2609138523"
|
||||
},
|
||||
},
|
||||
Sounds = {
|
||||
Notification = "rbxassetid://203785492",
|
||||
},
|
||||
StartSession = "",
|
||||
SessionActive = "",
|
||||
Configure = "",
|
||||
@@ -60,4 +65,4 @@ end
|
||||
|
||||
guardForTypos("Assets", Assets)
|
||||
|
||||
return Assets
|
||||
return Assets
|
||||
|
||||
41
plugin/src/ChangeBatcher/createPatchSet.lua
Normal file
41
plugin/src/ChangeBatcher/createPatchSet.lua
Normal file
@@ -0,0 +1,41 @@
|
||||
--[[
|
||||
Take an InstanceMap and a dictionary mapping instances to sets of property
|
||||
names. Populate a patch with the encoded values of all the given properties
|
||||
on all the given instances (or, if any changes set Parent to nil, removals
|
||||
of instances) and return the patch.
|
||||
]]
|
||||
|
||||
local Packages = script.Parent.Parent.Parent.Packages
|
||||
local Log = require(Packages.Log)
|
||||
|
||||
local PatchSet = require(script.Parent.Parent.PatchSet)
|
||||
|
||||
local encodePatchUpdate = require(script.Parent.encodePatchUpdate)
|
||||
|
||||
return function(instanceMap, propertyChanges)
|
||||
local patch = PatchSet.newEmpty()
|
||||
|
||||
for instance, properties in pairs(propertyChanges) do
|
||||
local instanceId = instanceMap.fromInstances[instance]
|
||||
|
||||
if instanceId == nil then
|
||||
Log.warn("Ignoring change for instance {:?} as it is unknown to Rojo", instance)
|
||||
continue
|
||||
end
|
||||
|
||||
if properties.Parent then
|
||||
if instance.Parent == nil then
|
||||
table.insert(patch.removed, instanceId)
|
||||
else
|
||||
Log.warn("Cannot sync non-nil Parent property changes yet")
|
||||
end
|
||||
else
|
||||
local update = encodePatchUpdate(instance, instanceId, properties)
|
||||
table.insert(patch.updated, update)
|
||||
end
|
||||
|
||||
propertyChanges[instance] = nil
|
||||
end
|
||||
|
||||
return patch
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user