mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-21 05:06:29 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eddc469f95 | ||
|
|
21a4667fe4 | ||
|
|
b25f2fcd5d | ||
|
|
0f7c9493d2 | ||
|
|
f1c4102d7f | ||
|
|
8b5bfd5f44 | ||
|
|
0599b50235 | ||
|
|
21f7ef6186 | ||
|
|
de6470bb45 |
@@ -23,7 +23,4 @@ insert_final_newline = true
|
|||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[*.lua]
|
[*.lua]
|
||||||
indent_style = tab
|
|
||||||
|
|
||||||
[*.luau]
|
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# stylua formatting
|
|
||||||
0f8e1625d572a5fe0f7b5c08653ff92cc837d346
|
|
||||||
23
.github/workflows/changelog.yml
vendored
23
.github/workflows/changelog.yml
vendored
@@ -1,23 +0,0 @@
|
|||||||
name: Changelog Check
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [assigned, opened, synchronize, reopened, labeled, unlabeled]
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Check Actions
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Changelog check
|
|
||||||
uses: Zomzog/changelog-checker@v1.3.0
|
|
||||||
with:
|
|
||||||
fileName: CHANGELOG.md
|
|
||||||
noChangelogLabel: skip changelog
|
|
||||||
checkNotification: Simple
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
91
.github/workflows/ci.yml
vendored
91
.github/workflows/ci.yml
vendored
@@ -11,96 +11,29 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build and Test
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
rust_version: [stable, "1.43.1"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v1
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Setup Rust toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
run: rustup default ${{ matrix.rust_version }}
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
profile: minimal
|
|
||||||
|
|
||||||
- name: Rust cache
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
|
|
||||||
- name: Setup Aftman
|
|
||||||
uses: ok-nick/setup-aftman@v0.3.0
|
|
||||||
with:
|
|
||||||
version: 'v0.2.7'
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --locked --verbose
|
run: cargo build --locked --verbose
|
||||||
|
|
||||||
- name: Test
|
- name: Run tests
|
||||||
run: cargo test --locked --verbose
|
run: cargo test --locked --verbose
|
||||||
|
|
||||||
msrv:
|
- name: Rustfmt and Clippy
|
||||||
name: Check MSRV
|
run: |
|
||||||
runs-on: ubuntu-latest
|
cargo fmt -- --check
|
||||||
|
cargo clippy
|
||||||
steps:
|
if: matrix.rust_version == 'stable'
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Install Rust
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: 1.70.0
|
|
||||||
override: true
|
|
||||||
profile: minimal
|
|
||||||
|
|
||||||
- name: Rust cache
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
|
|
||||||
- name: Setup Aftman
|
|
||||||
uses: ok-nick/setup-aftman@v0.3.0
|
|
||||||
with:
|
|
||||||
version: 'v0.2.7'
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: cargo build --locked --verbose
|
|
||||||
|
|
||||||
lint:
|
|
||||||
name: Rustfmt, Clippy, & Stylua
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Install Rust
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
components: rustfmt, clippy
|
|
||||||
|
|
||||||
- name: Rust cache
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
|
|
||||||
- name: Setup Aftman
|
|
||||||
uses: ok-nick/setup-aftman@v0.3.0
|
|
||||||
with:
|
|
||||||
version: 'v0.2.7'
|
|
||||||
|
|
||||||
- name: Stylua
|
|
||||||
run: stylua --check plugin/src
|
|
||||||
|
|
||||||
- name: Rustfmt
|
|
||||||
run: cargo fmt -- --check
|
|
||||||
|
|
||||||
- name: Clippy
|
|
||||||
run: cargo clippy
|
|
||||||
|
|
||||||
204
.github/workflows/release.yml
vendored
204
.github/workflows/release.yml
vendored
@@ -2,157 +2,65 @@ name: Release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: ["v*"]
|
tags: ["*"]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-release:
|
windows:
|
||||||
name: Create Release
|
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:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Create Release
|
- uses: actions/checkout@v1
|
||||||
id: create_release
|
with:
|
||||||
uses: actions/create-release@v1
|
submodules: true
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
release_name: ${{ github.ref }}
|
|
||||||
draft: true
|
|
||||||
prerelease: false
|
|
||||||
|
|
||||||
build-plugin:
|
- name: Build
|
||||||
needs: ["create-release"]
|
run: cargo build --locked --verbose --release
|
||||||
name: Build Roblox Studio Plugin
|
env:
|
||||||
runs-on: ubuntu-latest
|
OPENSSL_STATIC: 1
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Setup Aftman
|
- name: Upload artifacts
|
||||||
uses: ok-nick/setup-aftman@v0.1.0
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
name: rojo-linux
|
||||||
trust-check: false
|
path: target/release/rojo
|
||||||
version: 'v0.2.6'
|
|
||||||
|
|
||||||
- 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
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Get Version from Tag
|
|
||||||
shell: bash
|
|
||||||
# https://github.community/t/how-to-get-just-the-tag-name/16241/7#M1027
|
|
||||||
run: |
|
|
||||||
echo "PROJECT_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
|
||||||
echo "Version is: ${{ env.PROJECT_VERSION }}"
|
|
||||||
|
|
||||||
- name: Install Rust
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
target: ${{ matrix.target }}
|
|
||||||
override: true
|
|
||||||
profile: minimal
|
|
||||||
|
|
||||||
- name: Setup Aftman
|
|
||||||
uses: ok-nick/setup-aftman@v0.1.0
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
trust-check: false
|
|
||||||
version: 'v0.2.6'
|
|
||||||
|
|
||||||
- name: Build Release
|
|
||||||
run: cargo build --release --locked --verbose --target ${{ matrix.target }}
|
|
||||||
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/${{ matrix.target }}/release/$BIN.exe" staging/
|
|
||||||
cd staging
|
|
||||||
7z a ../release.zip *
|
|
||||||
else
|
|
||||||
cp "output/${{ matrix.target }}/release/$BIN" staging/
|
|
||||||
cd staging
|
|
||||||
zip ../release.zip *
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Upload Archive to Release
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
|
||||||
asset_path: release.zip
|
|
||||||
asset_name: ${{ env.BIN }}-${{ env.PROJECT_VERSION }}-${{ matrix.label }}.zip
|
|
||||||
asset_content_type: application/octet-stream
|
|
||||||
|
|
||||||
- name: Upload Archive to Artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: ${{ env.BIN }}-${{ env.PROJECT_VERSION }}-${{ matrix.label }}.zip
|
|
||||||
path: release.zip
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -19,3 +19,6 @@
|
|||||||
|
|
||||||
# Snapshot files from the 'insta' Rust crate
|
# Snapshot files from the 'insta' Rust crate
|
||||||
**/*.snap.new
|
**/*.snap.new
|
||||||
|
|
||||||
|
# Selene generates a roblox.toml file that should not be checked in.
|
||||||
|
/roblox.toml
|
||||||
31
.gitmodules
vendored
31
.gitmodules
vendored
@@ -1,18 +1,15 @@
|
|||||||
[submodule "plugin/Packages/Roact"]
|
[submodule "plugin/modules/roact"]
|
||||||
path = plugin/Packages/Roact
|
path = plugin/modules/roact
|
||||||
url = https://github.com/roblox/roact.git
|
url = https://github.com/Roblox/roact.git
|
||||||
[submodule "plugin/Packages/Flipper"]
|
[submodule "plugin/modules/testez"]
|
||||||
path = plugin/Packages/Flipper
|
path = plugin/modules/testez
|
||||||
url = https://github.com/reselim/flipper.git
|
url = https://github.com/Roblox/testez.git
|
||||||
[submodule "plugin/Packages/Promise"]
|
[submodule "plugin/modules/promise"]
|
||||||
path = plugin/Packages/Promise
|
path = plugin/modules/promise
|
||||||
url = https://github.com/evaera/roblox-lua-promise.git
|
url = https://github.com/LPGhatguy/roblox-lua-promise.git
|
||||||
[submodule "plugin/Packages/t"]
|
[submodule "plugin/modules/t"]
|
||||||
path = plugin/Packages/t
|
path = plugin/modules/t
|
||||||
url = https://github.com/osyrisrblx/t.git
|
url = https://github.com/osyrisrblx/t.git
|
||||||
[submodule "plugin/Packages/TestEZ"]
|
[submodule "plugin/modules/flipper"]
|
||||||
path = plugin/Packages/TestEZ
|
path = plugin/modules/flipper
|
||||||
url = https://github.com/roblox/testez.git
|
url = https://github.com/Reselim/Flipper
|
||||||
[submodule "plugin/Packages/Highlighter"]
|
|
||||||
path = plugin/Packages/Highlighter
|
|
||||||
url = https://github.com/boatbomber/highlighter.git
|
|
||||||
|
|||||||
58
.luacheckrc
Normal file
58
.luacheckrc
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
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",
|
||||||
|
}
|
||||||
402
CHANGELOG.md
402
CHANGELOG.md
@@ -2,405 +2,21 @@
|
|||||||
|
|
||||||
## Unreleased Changes
|
## Unreleased Changes
|
||||||
|
|
||||||
## [7.4.0] - January 16, 2024
|
## [6.2.0][6.2.0] (June 10, 2021)
|
||||||
* Improved the visualization for array properties like Tags ([#829])
|
|
||||||
* Significantly improved performance of `rojo serve`, `rojo build --watch`, and `rojo sourcemap --watch` on macOS. ([#830])
|
|
||||||
* Changed *.lua files that init command generates to *.luau ([#831])
|
|
||||||
* Does not remind users to sync if the sync lock is claimed already ([#833])
|
|
||||||
|
|
||||||
[#829]: https://github.com/rojo-rbx/rojo/pull/829
|
|
||||||
[#830]: https://github.com/rojo-rbx/rojo/pull/830
|
|
||||||
[#831]: https://github.com/rojo-rbx/rojo/pull/831
|
|
||||||
[#833]: https://github.com/rojo-rbx/rojo/pull/833
|
|
||||||
|
|
||||||
## [7.4.0-rc3] - October 25, 2023
|
|
||||||
* Changed `sourcemap --watch` to only generate the sourcemap when it's necessary ([#800])
|
|
||||||
* Switched script source property getter and setter to `ScriptEditorService` methods ([#801])
|
|
||||||
|
|
||||||
This ensures that the script editor reflects any changes Rojo makes to a script while it is open in the script editor.
|
|
||||||
|
|
||||||
* Fixed issues when handling `SecurityCapabilities` values ([#803], [#807])
|
|
||||||
* Fixed Rojo plugin erroring out when attempting to sync attributes with invalid names ([#809])
|
|
||||||
|
|
||||||
[#800]: https://github.com/rojo-rbx/rojo/pull/800
|
|
||||||
[#801]: https://github.com/rojo-rbx/rojo/pull/801
|
|
||||||
[#803]: https://github.com/rojo-rbx/rojo/pull/803
|
|
||||||
[#807]: https://github.com/rojo-rbx/rojo/pull/807
|
|
||||||
[#809]: https://github.com/rojo-rbx/rojo/pull/809
|
|
||||||
|
|
||||||
## [7.4.0-rc2] - October 3, 2023
|
|
||||||
* Fixed bug with parsing version for plugin validation ([#797])
|
|
||||||
|
|
||||||
[#797]: https://github.com/rojo-rbx/rojo/pull/797
|
|
||||||
|
|
||||||
## [7.4.0-rc1] - October 3, 2023
|
|
||||||
### Additions
|
|
||||||
#### Project format
|
|
||||||
* Added support for `.toml` files to `$path` ([#633])
|
|
||||||
* Added support for `Font` and `CFrame` attributes ([rbx-dom#299], [rbx-dom#296])
|
|
||||||
* Added the `emitLegacyScripts` field to the project format ([#765]). The behavior is outlined below:
|
|
||||||
|
|
||||||
| `emitLegacyScripts` Value | Action Taken by Rojo |
|
|
||||||
|---------------------------|------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| false | Rojo emits Scripts with the appropriate `RunContext` for `*.client.lua` and `*.server.lua` files in the project. |
|
|
||||||
| true (default) | Rojo emits LocalScripts and Scripts with legacy `RunContext` (same behavior as previously). |
|
|
||||||
|
|
||||||
|
|
||||||
It can be used like this:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"emitLegacyScripts": false,
|
|
||||||
"name": "MyCoolRunContextProject",
|
|
||||||
"tree": {
|
|
||||||
"$path": "src"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* Added `Terrain` classname inference, similar to services ([#771])
|
|
||||||
|
|
||||||
`Terrain` may now be defined in projects without using `$className`:
|
|
||||||
```json
|
|
||||||
"Workspace": {
|
|
||||||
"Terrain": {
|
|
||||||
"$path": "path/to/terrain.rbxm"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* Added support for `Terrain.MaterialColors` ([#770])
|
|
||||||
|
|
||||||
`Terrain.MaterialColors` is now represented in projects in a human readable format:
|
|
||||||
```json
|
|
||||||
"Workspace": {
|
|
||||||
"Terrain": {
|
|
||||||
"$path": "path/to/terrain.rbxm"
|
|
||||||
"$properties": {
|
|
||||||
"MaterialColors": {
|
|
||||||
"Grass": [10, 20, 30],
|
|
||||||
"Asphalt": [40, 50, 60],
|
|
||||||
"LeafyGrass": [255, 155, 55]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
* Added better support for `Font` properties ([#731])
|
|
||||||
|
|
||||||
`FontFace` properties may now be defined using implicit property syntax:
|
|
||||||
```json
|
|
||||||
"TextBox": {
|
|
||||||
"$className": "TextBox",
|
|
||||||
"$properties": {
|
|
||||||
"FontFace": {
|
|
||||||
"family": "rbxasset://fonts/families/RobotoMono.json",
|
|
||||||
"weight": "Thin",
|
|
||||||
"style": "Normal"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Patch visualizer and notifications
|
|
||||||
* Added a setting to control patch confirmation behavior ([#774])
|
|
||||||
|
|
||||||
This is a new setting for controlling when the Rojo plugin prompts for confirmation before syncing. It has four options:
|
|
||||||
* Initial (default): prompts only once for a project in a given Studio session
|
|
||||||
* Always: always prompts for confirmation
|
|
||||||
* Large Changes: only prompts when there are more than X changed instances. The number of instances is configurable - an additional setting for the number of instances becomes available when this option is chosen
|
|
||||||
* Unlisted PlaceId: only prompts if the place ID is not present in servePlaceIds
|
|
||||||
|
|
||||||
* Added the ability to select Instances in patch visualizer ([#709])
|
|
||||||
|
|
||||||
Double-clicking an instance in the patch visualizer sets Roblox Studio's selection to the instance.
|
|
||||||
|
|
||||||
* Added a sync reminder notification. ([#689])
|
|
||||||
|
|
||||||
Rojo detects if you have previously synced to a place, and displays a notification reminding you to sync again:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
* Added rich Source diffs in patch visualizer ([#748])
|
|
||||||
|
|
||||||
A "View Diff" button for script sources is now present in the patch visualizer. Clicking it displays a side-by-side diff of the script changes:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
* Patch visualizer now indicates what changes failed to apply. ([#717])
|
|
||||||
|
|
||||||
A clickable warning label is displayed when the Rojo plugin is unable to apply changes. Clicking the label displays precise information about which changes failed:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
#### Miscellaneous
|
|
||||||
* Added `plugin` flag to the `build` command that outputs to the local plugins folder ([#735])
|
|
||||||
|
|
||||||
This is a flag that builds a Rojo project into Roblox Studio's plugins directory. This allows you to build a Rojo project and load it into Studio as a plugin without having to type the full path to the plugins directory. It can be used like this: `rojo build <PATH-TO-PROJECT> --plugin <FILE-NAME>`
|
|
||||||
|
|
||||||
* Added new plugin template to the `init` command ([#738])
|
|
||||||
|
|
||||||
This is a new template geared towards plugins. It is similar to the model template, but creates a `Script` instead of a `ModuleScript` in the `src` directory. It can be used like this: `rojo init --kind plugin`
|
|
||||||
|
|
||||||
* Added protection against syncing non-place projects as a place. ([#691])
|
|
||||||
* Add buttons for navigation on the Connected page ([#722])
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
* Significantly improved performance of `rojo sourcemap` ([#668])
|
|
||||||
* Fixed the diff visualizer of connected sessions. ([#674])
|
|
||||||
* Fixed disconnected session activity. ([#675])
|
|
||||||
* Skip confirming patches that contain only a datamodel name change. ([#688])
|
|
||||||
* Fix Rojo breaking when users undo/redo in Studio ([#708])
|
|
||||||
* Improve tooltip behavior ([#723])
|
|
||||||
* Better settings controls ([#725])
|
|
||||||
* Rework patch visualizer with many fixes and improvements ([#713], [#726], [#755])
|
|
||||||
|
|
||||||
[#668]: https://github.com/rojo-rbx/rojo/pull/668
|
|
||||||
[#674]: https://github.com/rojo-rbx/rojo/pull/674
|
|
||||||
[#675]: https://github.com/rojo-rbx/rojo/pull/675
|
|
||||||
[#688]: https://github.com/rojo-rbx/rojo/pull/688
|
|
||||||
[#689]: https://github.com/rojo-rbx/rojo/pull/689
|
|
||||||
[#691]: https://github.com/rojo-rbx/rojo/pull/691
|
|
||||||
[#709]: https://github.com/rojo-rbx/rojo/pull/709
|
|
||||||
[#708]: https://github.com/rojo-rbx/rojo/pull/708
|
|
||||||
[#713]: https://github.com/rojo-rbx/rojo/pull/713
|
|
||||||
[#717]: https://github.com/rojo-rbx/rojo/pull/717
|
|
||||||
[#722]: https://github.com/rojo-rbx/rojo/pull/722
|
|
||||||
[#723]: https://github.com/rojo-rbx/rojo/pull/723
|
|
||||||
[#725]: https://github.com/rojo-rbx/rojo/pull/725
|
|
||||||
[#726]: https://github.com/rojo-rbx/rojo/pull/726
|
|
||||||
[#633]: https://github.com/rojo-rbx/rojo/pull/633
|
|
||||||
[#735]: https://github.com/rojo-rbx/rojo/pull/735
|
|
||||||
[#731]: https://github.com/rojo-rbx/rojo/pull/731
|
|
||||||
[#738]: https://github.com/rojo-rbx/rojo/pull/738
|
|
||||||
[#748]: https://github.com/rojo-rbx/rojo/pull/748
|
|
||||||
[#755]: https://github.com/rojo-rbx/rojo/pull/755
|
|
||||||
[#765]: https://github.com/rojo-rbx/rojo/pull/765
|
|
||||||
[#770]: https://github.com/rojo-rbx/rojo/pull/770
|
|
||||||
[#771]: https://github.com/rojo-rbx/rojo/pull/771
|
|
||||||
[#774]: https://github.com/rojo-rbx/rojo/pull/774
|
|
||||||
[rbx-dom#299]: https://github.com/rojo-rbx/rbx-dom/pull/299
|
|
||||||
[rbx-dom#296]: https://github.com/rojo-rbx/rbx-dom/pull/296
|
|
||||||
|
|
||||||
## [7.3.0] - April 22, 2023
|
|
||||||
* Added `$attributes` to project format. ([#574])
|
|
||||||
* Added `--watch` flag to `rojo sourcemap`. ([#602])
|
|
||||||
* Added support for `init.csv` files. ([#594])
|
|
||||||
* Added real-time sync status to the Studio plugin. ([#569])
|
|
||||||
* Added support for copying error messages to the clipboard. ([#614])
|
|
||||||
* Added sync locking for Team Create. ([#590])
|
|
||||||
* Added support for specifying HTTP or HTTPS protocol in plugin. ([#642])
|
|
||||||
* Added tooltips to buttons in the Studio plugin. ([#637])
|
|
||||||
* Added visual diffs when connecting from the Studio plugin. ([#603])
|
|
||||||
* Host and port are now saved in the Studio plugin. ([#613])
|
|
||||||
* Improved padding on notifications in Studio plugin. ([#589])
|
|
||||||
* Renamed `Common` to `Shared` in the default Rojo project. ([#611])
|
|
||||||
* Reduced the minimum size of the Studio plugin widget. ([#606])
|
|
||||||
* Fixed current directory in `rojo fmt-project`. ([#581])
|
|
||||||
* Fixed errors after a session has already ended. ([#587])
|
|
||||||
* Fixed an uncommon security permission error ([#619])
|
|
||||||
|
|
||||||
[#569]: https://github.com/rojo-rbx/rojo/pull/569
|
|
||||||
[#574]: https://github.com/rojo-rbx/rojo/pull/574
|
|
||||||
[#581]: https://github.com/rojo-rbx/rojo/pull/581
|
|
||||||
[#587]: https://github.com/rojo-rbx/rojo/pull/587
|
|
||||||
[#589]: https://github.com/rojo-rbx/rojo/pull/589
|
|
||||||
[#590]: https://github.com/rojo-rbx/rojo/pull/590
|
|
||||||
[#594]: https://github.com/rojo-rbx/rojo/pull/594
|
|
||||||
[#602]: https://github.com/rojo-rbx/rojo/pull/602
|
|
||||||
[#603]: https://github.com/rojo-rbx/rojo/pull/603
|
|
||||||
[#606]: https://github.com/rojo-rbx/rojo/pull/606
|
|
||||||
[#611]: https://github.com/rojo-rbx/rojo/pull/611
|
|
||||||
[#613]: https://github.com/rojo-rbx/rojo/pull/613
|
|
||||||
[#614]: https://github.com/rojo-rbx/rojo/pull/614
|
|
||||||
[#619]: https://github.com/rojo-rbx/rojo/pull/619
|
|
||||||
[#637]: https://github.com/rojo-rbx/rojo/pull/637
|
|
||||||
[#642]: https://github.com/rojo-rbx/rojo/pull/642
|
|
||||||
[7.3.0]: https://github.com/rojo-rbx/rojo/releases/tag/v7.3.0
|
|
||||||
|
|
||||||
## [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.
|
* 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])
|
||||||
* Fixed "Open Scripts Externally" feature crashing Studio. ([#369][issue-369])
|
* Updated dependencies, fixing `HumanoidDescription` ID issues.
|
||||||
* 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
|
[issue-369]: https://github.com/rojo-rbx/rojo/issues/369
|
||||||
[pr-420]: https://github.com/rojo-rbx/rojo/pull/420
|
[6.2.0]: https://github.com/rojo-rbx/rojo/releases/tag/v6.2.0
|
||||||
[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
|
## [6.1.0][6.1.0] (April 12, 2021)
|
||||||
|
* Updated dependencies, fixing OptionalCoordinateFrame-related issues.
|
||||||
|
|
||||||
## [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])
|
* Added `--address` flag to `rojo serve` to allow for external connections. ([#403][pr-403])
|
||||||
|
|
||||||
[pr-403]: https://github.com/rojo-rbx/rojo/pull/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
|
[6.1.0]: https://github.com/rojo-rbx/rojo/releases/tag/v6.1.0
|
||||||
|
|
||||||
## [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)
|
## [6.0.2](https://github.com/rojo-rbx/rojo/releases/tag/v6.0.2) (February 9, 2021)
|
||||||
* Fixed `rojo upload` to handle CSRF challenges.
|
* Fixed `rojo upload` to handle CSRF challenges.
|
||||||
@@ -749,4 +365,4 @@ This is a general maintenance release for the Rojo 0.5.x release series.
|
|||||||
* More robust syncing with a new reconciler
|
* More robust syncing with a new reconciler
|
||||||
|
|
||||||
## [0.1.0](https://github.com/rojo-rbx/rojo/releases/tag/v0.1.0) (November 29, 2017)
|
## [0.1.0](https://github.com/rojo-rbx/rojo/releases/tag/v0.1.0) (November 29, 2017)
|
||||||
* Initial release, functionally very similar to [rbxfs](https://github.com/LPGhatguy/rbxfs)
|
* Initial release, functionally very similar to [rbxfs](https://github.com/LPGhatguy/rbxfs)
|
||||||
@@ -29,29 +29,25 @@ 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.
|
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
|
## Pushing a Rojo Release
|
||||||
The Rojo release process is pretty manual right now. If you need to do it, here's how:
|
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)
|
1. Bump server version in [`Cargo.toml`](Cargo.toml)
|
||||||
2. Bump plugin version in [`plugin/src/Config.lua`](plugin/src/Config.lua)
|
2. Bump plugin version in [`plugin/src/Config.lua`](plugin/src/Config.lua)
|
||||||
3. Run `cargo test` to update `Cargo.lock` and run tests
|
3. Run `cargo test` to update `Cargo.lock` and double-check tests
|
||||||
4. Update [`CHANGELOG.md`](CHANGELOG.md)
|
4. Update [`CHANGELOG.md`](CHANGELOG.md)
|
||||||
5. Commit!
|
5. Commit!
|
||||||
* `git add . && git commit -m "Release vX.Y.Z"`
|
* `git add . && git commit -m "Release vX.Y.Z"`
|
||||||
6. Tag the commit
|
6. Tag the commit with the version from `Cargo.toml` prepended with a v, like `v0.4.13`
|
||||||
* `git tag vX.Y.Z`
|
|
||||||
7. Publish the CLI
|
7. Publish the CLI
|
||||||
* `cargo publish`
|
* `cargo publish`
|
||||||
8. Publish the Plugin
|
8. Build and upload the plugin
|
||||||
* `cargo run -- upload plugin --asset_id 6415005344`
|
* `rojo build plugin -o Rojo.rbxm`
|
||||||
|
* Upload `Rojo.rbxm` to Roblox.com, keep it for later
|
||||||
9. Push commits and tags
|
9. Push commits and tags
|
||||||
* `git push && git push --tags`
|
* `git push && git push --tags`
|
||||||
10. Copy GitHub release content from previous release
|
10. Copy GitHub release content from previous release
|
||||||
* Update the leading text with a summary about the release
|
* Update the leading text with a summary about the release
|
||||||
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md)
|
* Paste the changelog notes (as-is!) from [`CHANGELOG.md`](CHANGELOG.md)
|
||||||
* Write a small summary of each major feature
|
* Write a small summary of each major feature
|
||||||
|
* Attach release artifacts from GitHub Actions for each platform
|
||||||
2471
Cargo.lock
generated
2471
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
132
Cargo.toml
132
Cargo.toml
@@ -1,7 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rojo"
|
name = "rojo"
|
||||||
version = "7.4.0"
|
version = "6.2.0"
|
||||||
rust-version = "1.70.0"
|
|
||||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||||
description = "Enables professional-grade development tools for Roblox developers"
|
description = "Enables professional-grade development tools for Roblox developers"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
@@ -9,10 +8,11 @@ homepage = "https://rojo.space"
|
|||||||
documentation = "https://rojo.space/docs"
|
documentation = "https://rojo.space/docs"
|
||||||
repository = "https://github.com/rojo-rbx/rojo"
|
repository = "https://github.com/rojo-rbx/rojo"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
edition = "2021"
|
edition = "2018"
|
||||||
build = "build.rs"
|
|
||||||
|
|
||||||
exclude = ["/test-projects/**"]
|
exclude = [
|
||||||
|
"/test-projects/**",
|
||||||
|
]
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
@@ -26,90 +26,80 @@ default = []
|
|||||||
# Enable this feature to live-reload assets from the web UI.
|
# Enable this feature to live-reload assets from the web UI.
|
||||||
dev_live_assets = []
|
dev_live_assets = []
|
||||||
|
|
||||||
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client"]
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*"]
|
members = [
|
||||||
|
"rojo-insta-ext",
|
||||||
|
"memofs",
|
||||||
|
]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "librojo"
|
name = "librojo"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "rojo"
|
||||||
|
path = "src/bin.rs"
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "build"
|
name = "build"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
memofs = { version = "0.2.0", path = "crates/memofs" }
|
memofs = { version = "0.1.2", path = "memofs" }
|
||||||
|
|
||||||
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
anyhow = "1.0.27"
|
||||||
# rbx_binary = { path = "../rbx-dom/rbx_binary" }
|
backtrace = "0.3"
|
||||||
# rbx_dom_weak = { path = "../rbx-dom/rbx_dom_weak" }
|
bincode = "1.2.1"
|
||||||
# rbx_reflection = { path = "../rbx-dom/rbx_reflection" }
|
crossbeam-channel = "0.4.0"
|
||||||
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
csv = "1.1.1"
|
||||||
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
env_logger = "0.7.1"
|
||||||
|
fs-err = "2.2.0"
|
||||||
rbx_binary = "0.7.4"
|
futures = "0.1.29"
|
||||||
rbx_dom_weak = "2.7.0"
|
globset = "0.4.4"
|
||||||
rbx_reflection = "4.5.0"
|
humantime = "1.3.0"
|
||||||
rbx_reflection_database = "0.2.10"
|
hyper = "0.12.35"
|
||||||
rbx_xml = "0.13.3"
|
jod-thread = "0.1.0"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
anyhow = "1.0.44"
|
log = "0.4.8"
|
||||||
backtrace = "0.3.61"
|
maplit = "1.0.1"
|
||||||
bincode = "1.3.3"
|
notify = "4.0.14"
|
||||||
crossbeam-channel = "0.5.1"
|
opener = "0.4.1"
|
||||||
csv = "1.1.6"
|
rbx_binary = "0.5.0"
|
||||||
env_logger = "0.9.0"
|
rbx_dom_weak = "1.10.1"
|
||||||
fs-err = "2.6.0"
|
rbx_reflection = "3.3.408"
|
||||||
futures = "0.3.17"
|
rbx_xml = "0.11.3"
|
||||||
globset = "0.4.8"
|
regex = "1.3.1"
|
||||||
humantime = "2.1.0"
|
reqwest = "0.9.20"
|
||||||
hyper = { version = "0.14.13", features = ["server", "tcp", "http1"] }
|
|
||||||
jod-thread = "0.1.2"
|
|
||||||
log = "0.4.14"
|
|
||||||
maplit = "1.0.2"
|
|
||||||
num_cpus = "1.15.0"
|
|
||||||
opener = "0.5.0"
|
|
||||||
rayon = "1.7.0"
|
|
||||||
reqwest = { version = "0.11.10", features = [
|
|
||||||
"blocking",
|
|
||||||
"json",
|
|
||||||
"native-tls-vendored",
|
|
||||||
] }
|
|
||||||
ritz = "0.1.0"
|
ritz = "0.1.0"
|
||||||
roblox_install = "1.0.0"
|
rlua = "0.17.0"
|
||||||
serde = { version = "1.0.130", features = ["derive", "rc"] }
|
roblox_install = "0.2.2"
|
||||||
serde_json = "1.0.68"
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
toml = "0.5.9"
|
serde_json = "1.0"
|
||||||
termcolor = "1.1.2"
|
structopt = "0.3.5"
|
||||||
thiserror = "1.0.30"
|
termcolor = "1.0.5"
|
||||||
tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] }
|
thiserror = "1.0.11"
|
||||||
uuid = { version = "1.0.0", features = ["v4", "serde"] }
|
tokio = "0.1.22"
|
||||||
clap = { version = "3.1.18", features = ["derive"] }
|
uuid = { version = "0.8.1", features = ["v4", "serde"] }
|
||||||
profiling = "1.0.6"
|
|
||||||
tracy-client = { version = "0.13.2", optional = true }
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.10.1"
|
winreg = "0.6.2"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
memofs = { version = "0.2.0", path = "crates/memofs" }
|
memofs = { version = "0.1.3", path = "memofs" }
|
||||||
|
|
||||||
embed-resource = "1.6.4"
|
anyhow = "1.0.27"
|
||||||
anyhow = "1.0.44"
|
bincode = "1.2.1"
|
||||||
bincode = "1.3.3"
|
fs-err = "2.3.0"
|
||||||
fs-err = "2.6.0"
|
maplit = "1.0.1"
|
||||||
maplit = "1.0.2"
|
|
||||||
semver = "1.0.19"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rojo-insta-ext = { path = "crates/rojo-insta-ext" }
|
rojo-insta-ext = { path = "rojo-insta-ext" }
|
||||||
|
|
||||||
criterion = "0.3.5"
|
criterion = "0.3"
|
||||||
insta = { version = "1.8.0", features = ["redactions", "yaml"] }
|
insta = { version = "1.3.0", features = ["redactions"] }
|
||||||
paste = "1.0.5"
|
lazy_static = "1.2"
|
||||||
pretty_assertions = "1.2.1"
|
paste = "0.1"
|
||||||
serde_yaml = "0.8.21"
|
pretty_assertions = "0.6.1"
|
||||||
tempfile = "3.2.0"
|
serde_yaml = "0.8.9"
|
||||||
walkdir = "2.3.2"
|
tempfile = "3.0"
|
||||||
|
walkdir = "2.1"
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -1,13 +1,21 @@
|
|||||||
<div align="center">
|
<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>
|
<div> </div>
|
||||||
|
|
||||||
<div align="center">
|
<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://github.com/rojo-rbx/rojo/actions">
|
||||||
<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>
|
<img src="https://github.com/rojo-rbx/rojo/workflows/CI/badge.svg" alt="Actions status" />
|
||||||
<a href="https://rojo.space/docs"><img src="https://img.shields.io/badge/docs-website-brightgreen.svg" alt="Rojo Documentation" /></a>
|
</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>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
@@ -40,7 +48,7 @@ Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions fo
|
|||||||
|
|
||||||
Pull requests are welcome!
|
Pull requests are welcome!
|
||||||
|
|
||||||
Rojo supports Rust 1.70.0 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
Rojo supports Rust 1.43.1 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
[tools]
|
|
||||||
rojo = "rojo-rbx/rojo@7.3.0"
|
|
||||||
selene = "Kampfkarren/selene@0.25.0"
|
|
||||||
stylua = "JohnnyMorganz/stylua@0.18.2"
|
|
||||||
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"
|
|
||||||
Binary file not shown.
@@ -2,7 +2,7 @@
|
|||||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
To build this library, use:
|
To build this library or plugin, use:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rojo build -o "{project_name}.rbxmx"
|
rojo build -o "{project_name}.rbxmx"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"$className": "DataModel",
|
"$className": "DataModel",
|
||||||
|
|
||||||
"ReplicatedStorage": {
|
"ReplicatedStorage": {
|
||||||
"Shared": {
|
"Common": {
|
||||||
"$path": "src/shared"
|
"$path": "src/shared"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
# {project_name}
|
|
||||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) {rojo_version}.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
To build this plugin to your local plugins folder, use:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rojo build -p "{project_name}.rbxm"
|
|
||||||
```
|
|
||||||
|
|
||||||
You can include the `watch` flag to re-build it on save:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rojo build -p "{project_name}.rbxm" --watch
|
|
||||||
```
|
|
||||||
|
|
||||||
For more help, check out [the Rojo documentation](https://rojo.space/docs).
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "{project_name}",
|
|
||||||
"tree": {
|
|
||||||
"$path": "src"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Plugin model files
|
|
||||||
/{project_name}.rbxmx
|
|
||||||
/{project_name}.rbxm
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -3,7 +3,7 @@ use std::path::Path;
|
|||||||
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||||
use tempfile::{tempdir, TempDir};
|
use tempfile::{tempdir, TempDir};
|
||||||
|
|
||||||
use librojo::cli::BuildCommand;
|
use librojo::cli::{build, BuildCommand};
|
||||||
|
|
||||||
pub fn benchmark_small_place(c: &mut Criterion) {
|
pub fn benchmark_small_place(c: &mut Criterion) {
|
||||||
bench_build_place(c, "Small Place", "test-projects/benchmark_small_place")
|
bench_build_place(c, "Small Place", "test-projects/benchmark_small_place")
|
||||||
@@ -20,7 +20,7 @@ fn bench_build_place(c: &mut Criterion, name: &str, path: &str) {
|
|||||||
group.bench_function("build", |b| {
|
group.bench_function("build", |b| {
|
||||||
b.iter_batched(
|
b.iter_batched(
|
||||||
|| place_setup(path),
|
|| place_setup(path),
|
||||||
|(_dir, options)| options.run().unwrap(),
|
|(_dir, options)| build(options).unwrap(),
|
||||||
BatchSize::SmallInput,
|
BatchSize::SmallInput,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -31,12 +31,11 @@ fn bench_build_place(c: &mut Criterion, name: &str, path: &str) {
|
|||||||
fn place_setup<P: AsRef<Path>>(input_path: P) -> (TempDir, BuildCommand) {
|
fn place_setup<P: AsRef<Path>>(input_path: P) -> (TempDir, BuildCommand) {
|
||||||
let dir = tempdir().unwrap();
|
let dir = tempdir().unwrap();
|
||||||
let input = input_path.as_ref().to_path_buf();
|
let input = input_path.as_ref().to_path_buf();
|
||||||
let output = Some(dir.path().join("output.rbxlx"));
|
let output = dir.path().join("output.rbxlx");
|
||||||
|
|
||||||
let options = BuildCommand {
|
let options = BuildCommand {
|
||||||
project: input,
|
project: input,
|
||||||
watch: false,
|
watch: false,
|
||||||
plugin: None,
|
|
||||||
output,
|
output,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
5
bin/dev-plugin.sh
Executable file
5
bin/dev-plugin.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
watchexec -c -w plugin "sh -c './bin/install-dev-plugin.sh'"
|
||||||
13
bin/install-dev-plugin.sh
Executable file
13
bin/install-dev-plugin.sh
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/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"
|
||||||
5
bin/install-release-plugin.sh
Executable file
5
bin/install-release-plugin.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
rojo build plugin -o "$LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm"
|
||||||
12
bin/mark-plugin-as-dev.lua
Normal file
12
bin/mark-plugin-as-dev.lua
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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)
|
||||||
8
bin/put-plugin-in-test-place.lua
Normal file
8
bin/put-plugin-in-test-place.lua
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
local pluginPath, placePath = ...
|
||||||
|
|
||||||
|
local plugin = remodel.readModelFile(pluginPath)[1]
|
||||||
|
local place = remodel.readPlaceFile(placePath)
|
||||||
|
|
||||||
|
plugin.Parent = place:GetService("ReplicatedStorage")
|
||||||
|
|
||||||
|
remodel.writePlaceFile(place, placePath)
|
||||||
6
bin/run-all-tests.sh
Executable file
6
bin/run-all-tests.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
./bin/run-cli-tests.sh
|
||||||
|
./bin/run-plugin-tests.sh
|
||||||
9
bin/run-cli-tests.sh
Executable file
9
bin/run-cli-tests.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/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
|
||||||
16
bin/run-plugin-tests.sh
Executable file
16
bin/run-plugin-tests.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/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
|
||||||
34
build.rs
34
build.rs
@@ -7,7 +7,6 @@ use fs_err as fs;
|
|||||||
use fs_err::File;
|
use fs_err::File;
|
||||||
use maplit::hashmap;
|
use maplit::hashmap;
|
||||||
use memofs::VfsSnapshot;
|
use memofs::VfsSnapshot;
|
||||||
use semver::Version;
|
|
||||||
|
|
||||||
fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
||||||
println!("cargo:rerun-if-changed={}", path.display());
|
println!("cargo:rerun-if-changed={}", path.display());
|
||||||
@@ -22,7 +21,7 @@ fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
|||||||
|
|
||||||
// We can skip any TestEZ test files since they aren't necessary for
|
// We can skip any TestEZ test files since they aren't necessary for
|
||||||
// the plugin to run.
|
// the plugin to run.
|
||||||
if file_name.ends_with(".spec.lua") || file_name.ends_with(".spec.luau") {
|
if file_name.ends_with(".spec.lua") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,14 +43,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
let root_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
|
let root_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
|
||||||
let plugin_root = PathBuf::from(root_dir).join("plugin");
|
let plugin_root = PathBuf::from(root_dir).join("plugin");
|
||||||
|
|
||||||
let our_version = Version::parse(env::var_os("CARGO_PKG_VERSION").unwrap().to_str().unwrap())?;
|
let plugin_modules = plugin_root.join("modules");
|
||||||
let plugin_version =
|
|
||||||
Version::parse(fs::read_to_string(plugin_root.join("Version.txt"))?.trim())?;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
our_version, plugin_version,
|
|
||||||
"plugin version does not match Cargo version"
|
|
||||||
);
|
|
||||||
|
|
||||||
let snapshot = VfsSnapshot::dir(hashmap! {
|
let snapshot = VfsSnapshot::dir(hashmap! {
|
||||||
"default.project.json" => snapshot_from_fs_path(&plugin_root.join("default.project.json"))?,
|
"default.project.json" => snapshot_from_fs_path(&plugin_root.join("default.project.json"))?,
|
||||||
@@ -60,18 +52,26 @@ fn main() -> Result<(), anyhow::Error> {
|
|||||||
"log" => snapshot_from_fs_path(&plugin_root.join("log"))?,
|
"log" => snapshot_from_fs_path(&plugin_root.join("log"))?,
|
||||||
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_root.join("rbx_dom_lua"))?,
|
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_root.join("rbx_dom_lua"))?,
|
||||||
"src" => snapshot_from_fs_path(&plugin_root.join("src"))?,
|
"src" => snapshot_from_fs_path(&plugin_root.join("src"))?,
|
||||||
"Packages" => snapshot_from_fs_path(&plugin_root.join("Packages"))?,
|
"modules" => VfsSnapshot::dir(hashmap! {
|
||||||
"Version.txt" => snapshot_from_fs_path(&plugin_root.join("Version.txt"))?,
|
"roact" => VfsSnapshot::dir(hashmap! {
|
||||||
|
"src" => snapshot_from_fs_path(&plugin_modules.join("roact").join("src"))?
|
||||||
|
}),
|
||||||
|
"promise" => VfsSnapshot::dir(hashmap! {
|
||||||
|
"lib" => snapshot_from_fs_path(&plugin_modules.join("promise").join("lib"))?
|
||||||
|
}),
|
||||||
|
"t" => VfsSnapshot::dir(hashmap! {
|
||||||
|
"lib" => snapshot_from_fs_path(&plugin_modules.join("t").join("lib"))?
|
||||||
|
}),
|
||||||
|
"flipper" => VfsSnapshot::dir(hashmap! {
|
||||||
|
"src" => snapshot_from_fs_path(&plugin_modules.join("flipper").join("src"))?
|
||||||
|
}),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
let out_path = Path::new(&out_dir).join("plugin.bincode");
|
let out_path = Path::new(&out_dir).join("plugin.bincode");
|
||||||
let out_file = File::create(out_path)?;
|
let out_file = File::create(&out_path)?;
|
||||||
|
|
||||||
bincode::serialize_into(out_file, &snapshot)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
#define RT_MANIFEST 24
|
|
||||||
1 RT_MANIFEST "rojo.manifest"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?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>
|
|
||||||
3
foreman.toml
Normal file
3
foreman.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[tools]
|
||||||
|
rojo = { source = "rojo-rbx/rojo", version = "6.0.0-rc.3" }
|
||||||
|
run-in-roblox = { source = "rojo-rbx/run-in-roblox", version = "0.3.0" }
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
# memofs Changelog
|
# memofs Changelog
|
||||||
|
|
||||||
## Unreleased Changes
|
## Unreleased Changes
|
||||||
* Changed `StdBackend` file watching component to use minimal recursive watches. [#830]
|
|
||||||
|
|
||||||
[#830]: https://github.com/rojo-rbx/rojo/pull/830
|
|
||||||
|
|
||||||
## 0.2.0 (2021-08-23)
|
|
||||||
* Updated to `crossbeam-channel` 0.5.1.
|
|
||||||
|
|
||||||
## 0.1.3 (2020-11-19)
|
## 0.1.3 (2020-11-19)
|
||||||
* Added `set_watch_enabled` to `Vfs` and `VfsLock` to allow turning off file watching.
|
* Added `set_watch_enabled` to `Vfs` and `VfsLock` to allow turning off file watching.
|
||||||
@@ -18,4 +12,4 @@
|
|||||||
* Improved error messages using the [fs-err](https://crates.io/crates/fs-err) crate.
|
* Improved error messages using the [fs-err](https://crates.io/crates/fs-err) crate.
|
||||||
|
|
||||||
## 0.1.0 (2020-03-10)
|
## 0.1.0 (2020-03-10)
|
||||||
* Initial release
|
* Initial release
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "memofs"
|
name = "memofs"
|
||||||
description = "Virtual filesystem with configurable backends."
|
description = "Virtual filesystem with configurable backends."
|
||||||
version = "0.2.0"
|
version = "0.1.3"
|
||||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
readme = "README.md"
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossbeam-channel = "0.5.1"
|
crossbeam-channel = "0.4.0"
|
||||||
fs-err = "2.3.0"
|
fs-err = "2.3.0"
|
||||||
notify = "4.0.15"
|
notify = "4.0.15"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
@@ -50,12 +50,6 @@ impl InMemoryFs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for InMemoryFs {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct InMemoryFsInner {
|
struct InMemoryFsInner {
|
||||||
entries: HashMap<PathBuf, Entry>,
|
entries: HashMap<PathBuf, Entry>,
|
||||||
@@ -194,8 +194,11 @@ impl VfsInner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn commit_event(&mut self, event: &VfsEvent) -> io::Result<()> {
|
fn commit_event(&mut self, event: &VfsEvent) -> io::Result<()> {
|
||||||
if let VfsEvent::Remove(path) = event {
|
match event {
|
||||||
let _ = self.backend.unwatch(path);
|
VfsEvent::Remove(path) => {
|
||||||
|
let _ = self.backend.unwatch(&path);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -74,9 +74,3 @@ impl VfsBackend for NoopBackend {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NoopBackend {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{collections::HashSet, io};
|
|
||||||
|
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use notify::{watcher, DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{watcher, DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
@@ -13,7 +13,6 @@ use crate::{DirEntry, Metadata, ReadDir, VfsBackend, VfsEvent};
|
|||||||
pub struct StdBackend {
|
pub struct StdBackend {
|
||||||
watcher: RecommendedWatcher,
|
watcher: RecommendedWatcher,
|
||||||
watcher_receiver: Receiver<VfsEvent>,
|
watcher_receiver: Receiver<VfsEvent>,
|
||||||
watches: HashSet<PathBuf>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StdBackend {
|
impl StdBackend {
|
||||||
@@ -49,7 +48,6 @@ impl StdBackend {
|
|||||||
Self {
|
Self {
|
||||||
watcher,
|
watcher,
|
||||||
watcher_receiver: rx,
|
watcher_receiver: rx,
|
||||||
watches: HashSet::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,30 +97,14 @@ impl VfsBackend for StdBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn watch(&mut self, path: &Path) -> io::Result<()> {
|
fn watch(&mut self, path: &Path) -> io::Result<()> {
|
||||||
if self.watches.contains(path)
|
self.watcher
|
||||||
|| path
|
.watch(path, RecursiveMode::NonRecursive)
|
||||||
.ancestors()
|
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner))
|
||||||
.any(|ancestor| self.watches.contains(ancestor))
|
|
||||||
{
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
self.watches.insert(path.to_path_buf());
|
|
||||||
self.watcher
|
|
||||||
.watch(path, RecursiveMode::Recursive)
|
|
||||||
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unwatch(&mut self, path: &Path) -> io::Result<()> {
|
fn unwatch(&mut self, path: &Path) -> io::Result<()> {
|
||||||
self.watches.remove(path);
|
|
||||||
self.watcher
|
self.watcher
|
||||||
.unwatch(path)
|
.unwatch(path)
|
||||||
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner))
|
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StdBackend {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Submodule plugin/Packages/Flipper deleted from 2d91a5ed55
Submodule plugin/Packages/Highlighter deleted from e0d061449e
Submodule plugin/Packages/Promise deleted from 2c6f433903
Submodule plugin/Packages/Roact deleted from 956891b70f
Submodule plugin/Packages/TestEZ deleted from edc7246d01
Submodule plugin/Packages/t deleted from 1f9754254b
@@ -1 +0,0 @@
|
|||||||
7.4.0
|
|
||||||
@@ -5,23 +5,29 @@
|
|||||||
"Plugin": {
|
"Plugin": {
|
||||||
"$path": "src"
|
"$path": "src"
|
||||||
},
|
},
|
||||||
"Packages": {
|
"Log": {
|
||||||
"$path": "Packages",
|
"$path": "log"
|
||||||
"Log": {
|
|
||||||
"$path": "log"
|
|
||||||
},
|
|
||||||
"Http": {
|
|
||||||
"$path": "http"
|
|
||||||
},
|
|
||||||
"Fmt": {
|
|
||||||
"$path": "fmt"
|
|
||||||
},
|
|
||||||
"RbxDom": {
|
|
||||||
"$path": "rbx_dom_lua"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"Version": {
|
"Http": {
|
||||||
"$path": "Version.txt"
|
"$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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1
plugin/modules/flipper
Submodule
1
plugin/modules/flipper
Submodule
Submodule plugin/modules/flipper added at 4cf7a03cb6
1
plugin/modules/promise
Submodule
1
plugin/modules/promise
Submodule
Submodule plugin/modules/promise added at 7fb09d103f
1
plugin/modules/roact
Submodule
1
plugin/modules/roact
Submodule
Submodule plugin/modules/roact added at f7d2f1ce1d
1
plugin/modules/t
Submodule
1
plugin/modules/t
Submodule
Submodule plugin/modules/t added at f643b50682
1
plugin/modules/testez
Submodule
1
plugin/modules/testez
Submodule
Submodule plugin/modules/testez added at 6e9157db3c
44
plugin/rbx_dom_lua/.luacheckrc
Normal file
44
plugin/rbx_dom_lua/.luacheckrc
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
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",
|
||||||
|
}
|
||||||
@@ -1,535 +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 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
|
|
||||||
|
|
||||||
--stylua: ignore
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
|
|
||||||
Font = {
|
|
||||||
fromPod = function(pod)
|
|
||||||
return Font.new(
|
|
||||||
pod.family,
|
|
||||||
if pod.weight ~= nil then Enum.FontWeight[pod.weight] else nil,
|
|
||||||
if pod.style ~= nil then Enum.FontStyle[pod.style] else nil
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
toPod = function(roblox)
|
|
||||||
return {
|
|
||||||
family = roblox.Family,
|
|
||||||
weight = roblox.Weight.Name,
|
|
||||||
style = roblox.Style.Name,
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
|
|
||||||
Int32 = {
|
|
||||||
fromPod = identity,
|
|
||||||
toPod = identity,
|
|
||||||
},
|
|
||||||
|
|
||||||
Int64 = {
|
|
||||||
fromPod = identity,
|
|
||||||
toPod = identity,
|
|
||||||
},
|
|
||||||
|
|
||||||
MaterialColors = {
|
|
||||||
fromPod = function(pod: { [string]: { number } })
|
|
||||||
local real = {}
|
|
||||||
for name, color in pod do
|
|
||||||
real[Enum.Material[name]] = Color3.fromRGB(color[1], color[2], color[3])
|
|
||||||
end
|
|
||||||
return real
|
|
||||||
end,
|
|
||||||
toPod = function(roblox: { [Enum.Material]: Color3 })
|
|
||||||
local pod = {}
|
|
||||||
for material, color in roblox do
|
|
||||||
pod[material.Name] = {
|
|
||||||
math.round(math.clamp(color.R, 0, 1) * 255),
|
|
||||||
math.round(math.clamp(color.G, 0, 1) * 255),
|
|
||||||
math.round(math.clamp(color.B, 0, 1) * 255),
|
|
||||||
}
|
|
||||||
end
|
|
||||||
return pod
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
|
|
||||||
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(_)
|
|
||||||
error("Ref cannot be decoded on its own")
|
|
||||||
end,
|
|
||||||
|
|
||||||
toPod = function(_)
|
|
||||||
error("Ref can not be encoded on its own")
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
|
|
||||||
Region3 = {
|
|
||||||
fromPod = function(_)
|
|
||||||
error("Region3 is not implemented")
|
|
||||||
end,
|
|
||||||
|
|
||||||
toPod = function(_)
|
|
||||||
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
|
|
||||||
2
plugin/rbx_dom_lua/README.md
Normal file
2
plugin/rbx_dom_lua/README.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# rbx_dom_lua
|
||||||
|
Roblox Lua implementation of rbx-dom mechanisms, intended to work with rbx_dom_weak and friends.
|
||||||
@@ -1,518 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"Font": {
|
|
||||||
"value": {
|
|
||||||
"Font": {
|
|
||||||
"family": "rbxasset://fonts/families/SourceSansPro.json",
|
|
||||||
"weight": "Regular",
|
|
||||||
"style": "Normal",
|
|
||||||
"cachedFaceId": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ty": "Font"
|
|
||||||
},
|
|
||||||
"Int32": {
|
|
||||||
"value": {
|
|
||||||
"Int32": 6014
|
|
||||||
},
|
|
||||||
"ty": "Int32"
|
|
||||||
},
|
|
||||||
"Int64": {
|
|
||||||
"value": {
|
|
||||||
"Int64": 23491023
|
|
||||||
},
|
|
||||||
"ty": "Int64"
|
|
||||||
},
|
|
||||||
"MaterialColors": {
|
|
||||||
"value": {
|
|
||||||
"MaterialColors": {
|
|
||||||
"Grass": [
|
|
||||||
106,
|
|
||||||
127,
|
|
||||||
63
|
|
||||||
],
|
|
||||||
"Slate": [
|
|
||||||
63,
|
|
||||||
127,
|
|
||||||
107
|
|
||||||
],
|
|
||||||
"Concrete": [
|
|
||||||
127,
|
|
||||||
102,
|
|
||||||
63
|
|
||||||
],
|
|
||||||
"Brick": [
|
|
||||||
138,
|
|
||||||
86,
|
|
||||||
62
|
|
||||||
],
|
|
||||||
"Sand": [
|
|
||||||
143,
|
|
||||||
126,
|
|
||||||
95
|
|
||||||
],
|
|
||||||
"WoodPlanks": [
|
|
||||||
139,
|
|
||||||
109,
|
|
||||||
79
|
|
||||||
],
|
|
||||||
"Rock": [
|
|
||||||
102,
|
|
||||||
108,
|
|
||||||
111
|
|
||||||
],
|
|
||||||
"Glacier": [
|
|
||||||
101,
|
|
||||||
176,
|
|
||||||
234
|
|
||||||
],
|
|
||||||
"Snow": [
|
|
||||||
195,
|
|
||||||
199,
|
|
||||||
218
|
|
||||||
],
|
|
||||||
"Sandstone": [
|
|
||||||
137,
|
|
||||||
90,
|
|
||||||
71
|
|
||||||
],
|
|
||||||
"Mud": [
|
|
||||||
58,
|
|
||||||
46,
|
|
||||||
36
|
|
||||||
],
|
|
||||||
"Basalt": [
|
|
||||||
30,
|
|
||||||
30,
|
|
||||||
37
|
|
||||||
],
|
|
||||||
"Ground": [
|
|
||||||
102,
|
|
||||||
92,
|
|
||||||
59
|
|
||||||
],
|
|
||||||
"CrackedLava": [
|
|
||||||
232,
|
|
||||||
156,
|
|
||||||
74
|
|
||||||
],
|
|
||||||
"Asphalt": [
|
|
||||||
115,
|
|
||||||
123,
|
|
||||||
107
|
|
||||||
],
|
|
||||||
"Cobblestone": [
|
|
||||||
132,
|
|
||||||
123,
|
|
||||||
90
|
|
||||||
],
|
|
||||||
"Ice": [
|
|
||||||
129,
|
|
||||||
194,
|
|
||||||
224
|
|
||||||
],
|
|
||||||
"LeafyGrass": [
|
|
||||||
115,
|
|
||||||
132,
|
|
||||||
74
|
|
||||||
],
|
|
||||||
"Salt": [
|
|
||||||
198,
|
|
||||||
189,
|
|
||||||
181
|
|
||||||
],
|
|
||||||
"Limestone": [
|
|
||||||
206,
|
|
||||||
173,
|
|
||||||
148
|
|
||||||
],
|
|
||||||
"Pavement": [
|
|
||||||
148,
|
|
||||||
148,
|
|
||||||
140
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ty": "MaterialColors"
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
local CollectionService = game:GetService("CollectionService")
|
|
||||||
local ScriptEditorService = game:GetService("ScriptEditorService")
|
|
||||||
|
|
||||||
--- A list of `Enum.Material` values that are used for Terrain.MaterialColors
|
|
||||||
local TERRAIN_MATERIAL_COLORS = {
|
|
||||||
Enum.Material.Grass,
|
|
||||||
Enum.Material.Slate,
|
|
||||||
Enum.Material.Concrete,
|
|
||||||
Enum.Material.Brick,
|
|
||||||
Enum.Material.Sand,
|
|
||||||
Enum.Material.WoodPlanks,
|
|
||||||
Enum.Material.Rock,
|
|
||||||
Enum.Material.Glacier,
|
|
||||||
Enum.Material.Snow,
|
|
||||||
Enum.Material.Sandstone,
|
|
||||||
Enum.Material.Mud,
|
|
||||||
Enum.Material.Basalt,
|
|
||||||
Enum.Material.Ground,
|
|
||||||
Enum.Material.CrackedLava,
|
|
||||||
Enum.Material.Asphalt,
|
|
||||||
Enum.Material.Cobblestone,
|
|
||||||
Enum.Material.Ice,
|
|
||||||
Enum.Material.LeafyGrass,
|
|
||||||
Enum.Material.Salt,
|
|
||||||
Enum.Material.Limestone,
|
|
||||||
Enum.Material.Pavement,
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Defines how to read and write properties that aren't directly scriptable.
|
|
||||||
--
|
|
||||||
-- The reflection database refers to these as having scriptability = "Custom"
|
|
||||||
return {
|
|
||||||
Instance = {
|
|
||||||
Attributes = {
|
|
||||||
read = function(instance)
|
|
||||||
return true, instance:GetAttributes()
|
|
||||||
end,
|
|
||||||
write = function(instance, _, value)
|
|
||||||
local existing = instance:GetAttributes()
|
|
||||||
local didAllWritesSucceed = true
|
|
||||||
|
|
||||||
for attributeName, attributeValue in pairs(value) do
|
|
||||||
local isNameValid =
|
|
||||||
-- For our SetAttribute to succeed, the attribute name must be
|
|
||||||
-- less than or equal to 100 characters...
|
|
||||||
#attributeName <= 100
|
|
||||||
-- ...must only contain alphanumeric characters, periods, hyphens,
|
|
||||||
-- underscores, or forward slashes...
|
|
||||||
and attributeName:match("[^%w%.%-_/]") == nil
|
|
||||||
-- ... and must not use the RBX prefix, which is reserved by Roblox.
|
|
||||||
and attributeName:sub(1, 3) ~= "RBX"
|
|
||||||
|
|
||||||
if isNameValid then
|
|
||||||
instance:SetAttribute(attributeName, attributeValue)
|
|
||||||
else
|
|
||||||
didAllWritesSucceed = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for key in pairs(existing) do
|
|
||||||
if value[key] == nil then
|
|
||||||
instance:SetAttribute(key, nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return didAllWritesSucceed
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
Tags = {
|
|
||||||
read = function(instance)
|
|
||||||
return true, CollectionService:GetTags(instance)
|
|
||||||
end,
|
|
||||||
write = function(instance, _, value)
|
|
||||||
local existingTags = CollectionService:GetTags(instance)
|
|
||||||
|
|
||||||
local unseenTags = {}
|
|
||||||
for _, tag in ipairs(existingTags) do
|
|
||||||
unseenTags[tag] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, tag in ipairs(value) do
|
|
||||||
unseenTags[tag] = nil
|
|
||||||
CollectionService:AddTag(instance, tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
for tag in pairs(unseenTags) do
|
|
||||||
CollectionService:RemoveTag(instance, tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
LocalizationTable = {
|
|
||||||
Contents = {
|
|
||||||
read = function(instance, _)
|
|
||||||
return true, instance:GetContents()
|
|
||||||
end,
|
|
||||||
write = function(instance, _, value)
|
|
||||||
instance:SetContents(value)
|
|
||||||
return true
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Model = {
|
|
||||||
Scale = {
|
|
||||||
read = function(instance, _, _)
|
|
||||||
return true, instance:GetScale()
|
|
||||||
end,
|
|
||||||
write = function(instance, _, value)
|
|
||||||
return true, instance:ScaleTo(value)
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Terrain = {
|
|
||||||
MaterialColors = {
|
|
||||||
read = function(instance: Terrain)
|
|
||||||
-- There's no way to get a list of every color, so we have to
|
|
||||||
-- make one.
|
|
||||||
local colors = {}
|
|
||||||
for _, material in TERRAIN_MATERIAL_COLORS do
|
|
||||||
colors[material] = instance:GetMaterialColor(material)
|
|
||||||
end
|
|
||||||
|
|
||||||
return true, colors
|
|
||||||
end,
|
|
||||||
write = function(instance: Terrain, _, value: { [Enum.Material]: Color3 })
|
|
||||||
for material, color in value do
|
|
||||||
instance:SetMaterialColor(material, color)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Script = {
|
|
||||||
Source = {
|
|
||||||
read = function(instance: Script)
|
|
||||||
return true, ScriptEditorService:GetEditorSource(instance)
|
|
||||||
end,
|
|
||||||
write = function(instance: Script, _, value: string)
|
|
||||||
task.spawn(function()
|
|
||||||
ScriptEditorService:UpdateSourceAsync(instance, function()
|
|
||||||
return value
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
return true
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ModuleScript = {
|
|
||||||
Source = {
|
|
||||||
read = function(instance: ModuleScript)
|
|
||||||
return true, ScriptEditorService:GetEditorSource(instance)
|
|
||||||
end,
|
|
||||||
write = function(instance: ModuleScript, _, value: string)
|
|
||||||
task.spawn(function()
|
|
||||||
ScriptEditorService:UpdateSourceAsync(instance, function()
|
|
||||||
return value
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
return true
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "empty_folder",
|
"name": "rbx_dom_lua",
|
||||||
"tree": {
|
"tree": {
|
||||||
"$path": "src"
|
"$path": "src"
|
||||||
}
|
}
|
||||||
242
plugin/rbx_dom_lua/src/EncodedValue.lua
Normal file
242
plugin/rbx_dom_lua/src/EncodedValue.lua
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
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
|
||||||
127
plugin/rbx_dom_lua/src/EncodedValue.spec.lua
Normal file
127
plugin/rbx_dom_lua/src/EncodedValue.spec.lua
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
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
|
||||||
@@ -25,4 +25,4 @@ function Error:__tostring()
|
|||||||
return ("Error(%s: %s)"):format(self.kind, tostring(self.extra))
|
return ("Error(%s: %s)"):format(self.kind, tostring(self.extra))
|
||||||
end
|
end
|
||||||
|
|
||||||
return Error
|
return Error
|
||||||
@@ -20,22 +20,8 @@ local function set(container, key, value)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function PropertyDescriptor.fromRaw(data, className, propertyName)
|
function PropertyDescriptor.fromRaw(data, className, propertyName)
|
||||||
local key, value = next(data.DataType)
|
|
||||||
|
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
-- The meanings of the key and value in DataType differ when the type of
|
scriptability = data.scriptability,
|
||||||
-- 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,
|
className = className,
|
||||||
name = propertyName,
|
name = propertyName,
|
||||||
}, PropertyDescriptor)
|
}, PropertyDescriptor)
|
||||||
@@ -53,11 +39,6 @@ function PropertyDescriptor:read(instance)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if self.scriptability == "Custom" then
|
if self.scriptability == "Custom" then
|
||||||
if customProperties[self.className] == nil then
|
|
||||||
local fullName = ("%s.%s"):format(instance.className, self.name)
|
|
||||||
return false, Error.new(Error.Kind.PropertyNotReadable, fullName)
|
|
||||||
end
|
|
||||||
|
|
||||||
local interface = customProperties[self.className][self.name]
|
local interface = customProperties[self.className][self.name]
|
||||||
|
|
||||||
return interface.read(instance, self.name)
|
return interface.read(instance, self.name)
|
||||||
@@ -84,11 +65,6 @@ function PropertyDescriptor:write(instance, value)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if self.scriptability == "Custom" then
|
if self.scriptability == "Custom" then
|
||||||
if customProperties[self.className] == nil then
|
|
||||||
local fullName = ("%s.%s"):format(instance.className, self.name)
|
|
||||||
return false, Error.new(Error.Kind.PropertyNotWritable, fullName)
|
|
||||||
end
|
|
||||||
|
|
||||||
local interface = customProperties[self.className][self.name]
|
local interface = customProperties[self.className][self.name]
|
||||||
|
|
||||||
return interface.write(instance, self.name, value)
|
return interface.write(instance, self.name, value)
|
||||||
@@ -101,4 +77,4 @@ function PropertyDescriptor:write(instance, value)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return PropertyDescriptor
|
return PropertyDescriptor
|
||||||
20114
plugin/rbx_dom_lua/src/ReflectionDatabase/classes.lua
Normal file
20114
plugin/rbx_dom_lua/src/ReflectionDatabase/classes.lua
Normal file
File diff suppressed because it is too large
Load Diff
3
plugin/rbx_dom_lua/src/ReflectionDatabase/init.lua
Normal file
3
plugin/rbx_dom_lua/src/ReflectionDatabase/init.lua
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
return {
|
||||||
|
classes = require(script.classes)
|
||||||
|
}
|
||||||
@@ -136,4 +136,4 @@ end
|
|||||||
return {
|
return {
|
||||||
decode = decodeBase64,
|
decode = decodeBase64,
|
||||||
encode = encodeBase64,
|
encode = encodeBase64,
|
||||||
}
|
}
|
||||||
29
plugin/rbx_dom_lua/src/base64.spec.lua
Normal file
29
plugin/rbx_dom_lua/src/base64.spec.lua
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
return function()
|
||||||
|
local base64 = require(script.Parent.base64)
|
||||||
|
|
||||||
|
it("should encode and decode", function()
|
||||||
|
local function try(str, expected)
|
||||||
|
local encoded = base64.encode(str)
|
||||||
|
expect(encoded).to.equal(expected)
|
||||||
|
expect(base64.decode(encoded)).to.equal(str)
|
||||||
|
end
|
||||||
|
|
||||||
|
try("Man", "TWFu")
|
||||||
|
try("Ma", "TWE=")
|
||||||
|
try("M", "TQ==")
|
||||||
|
try("ManM", "TWFuTQ==")
|
||||||
|
try(
|
||||||
|
[[Man is distinguished, not only by his reason, but by this ]]..
|
||||||
|
[[singular passion from other animals, which is a lust of the ]]..
|
||||||
|
[[mind, that by a perseverance of delight in the continued and ]]..
|
||||||
|
[[indefatigable generation of knowledge, exceeds the short ]]..
|
||||||
|
[[vehemence of any carnal pleasure.]],
|
||||||
|
[[TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sI]]..
|
||||||
|
[[GJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYW]]..
|
||||||
|
[[xzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJ]]..
|
||||||
|
[[zZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRl]]..
|
||||||
|
[[ZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZ]]..
|
||||||
|
[[SBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=]]
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end
|
||||||
47
plugin/rbx_dom_lua/src/customProperties.lua
Normal file
47
plugin/rbx_dom_lua/src/customProperties.lua
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
local CollectionService = game:GetService("CollectionService")
|
||||||
|
|
||||||
|
-- Defines how to read and write properties that aren't directly scriptable.
|
||||||
|
--
|
||||||
|
-- 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")
|
||||||
|
end,
|
||||||
|
write = function(instance, key, value)
|
||||||
|
local existingTags = CollectionService:GetTags(instance)
|
||||||
|
|
||||||
|
local unseenTags = {}
|
||||||
|
for _, tag in ipairs(existingTags) do
|
||||||
|
unseenTags[tag] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local tagList = string.split(value, "\0")
|
||||||
|
for _, tag in ipairs(tagList) do
|
||||||
|
unseenTags[tag] = nil
|
||||||
|
CollectionService:AddTag(instance, tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
for tag in pairs(unseenTags) do
|
||||||
|
CollectionService:RemoveTag(instance, tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LocalizationTable = {
|
||||||
|
Contents = {
|
||||||
|
read = function(instance, key)
|
||||||
|
return true, instance:GetContents()
|
||||||
|
end,
|
||||||
|
write = function(instance, key, value)
|
||||||
|
instance:SetContents(value)
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
local database = require(script.database)
|
local ReflectionDatabase = require(script.ReflectionDatabase)
|
||||||
local Error = require(script.Error)
|
local Error = require(script.Error)
|
||||||
local PropertyDescriptor = require(script.PropertyDescriptor)
|
local PropertyDescriptor = require(script.PropertyDescriptor)
|
||||||
|
|
||||||
@@ -6,32 +6,29 @@ local function findCanonicalPropertyDescriptor(className, propertyName)
|
|||||||
local currentClassName = className
|
local currentClassName = className
|
||||||
|
|
||||||
repeat
|
repeat
|
||||||
local currentClass = database.Classes[currentClassName]
|
local currentClass = ReflectionDatabase.classes[currentClassName]
|
||||||
|
|
||||||
if currentClass == nil then
|
if currentClass == nil then
|
||||||
return currentClass
|
return currentClass
|
||||||
end
|
end
|
||||||
|
|
||||||
local propertyData = currentClass.Properties[propertyName]
|
local propertyData = currentClass.properties[propertyName]
|
||||||
if propertyData ~= nil then
|
if propertyData ~= nil then
|
||||||
local canonicalData = propertyData.Kind.Canonical
|
if propertyData.isCanonical then
|
||||||
if canonicalData ~= nil then
|
|
||||||
return PropertyDescriptor.fromRaw(propertyData, currentClassName, propertyName)
|
return PropertyDescriptor.fromRaw(propertyData, currentClassName, propertyName)
|
||||||
end
|
end
|
||||||
|
|
||||||
local aliasData = propertyData.Kind.Alias
|
if propertyData.canonicalName ~= nil then
|
||||||
if aliasData ~= nil then
|
|
||||||
return PropertyDescriptor.fromRaw(
|
return PropertyDescriptor.fromRaw(
|
||||||
currentClass.Properties[aliasData.AliasFor],
|
currentClass.properties[propertyData.canonicalName],
|
||||||
currentClassName,
|
currentClassName,
|
||||||
aliasData.AliasFor
|
propertyData.canonicalName)
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
currentClassName = currentClass.Superclass
|
currentClassName = currentClass.superclass
|
||||||
until currentClassName == nil
|
until currentClassName == nil
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -67,4 +64,4 @@ return {
|
|||||||
findCanonicalPropertyDescriptor = findCanonicalPropertyDescriptor,
|
findCanonicalPropertyDescriptor = findCanonicalPropertyDescriptor,
|
||||||
Error = Error,
|
Error = Error,
|
||||||
EncodedValue = require(script.EncodedValue),
|
EncodedValue = require(script.EncodedValue),
|
||||||
}
|
}
|
||||||
7
plugin/rbx_dom_lua/src/init.spec.lua
Normal file
7
plugin/rbx_dom_lua/src/init.spec.lua
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
return function()
|
||||||
|
local RbxDom = require(script.Parent)
|
||||||
|
|
||||||
|
it("should load", function()
|
||||||
|
expect(RbxDom).to.be.ok()
|
||||||
|
end)
|
||||||
|
end
|
||||||
35
plugin/rbx_dom_lua/test-place.project.json
Normal file
35
plugin/rbx_dom_lua/test-place.project.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
plugin/rbx_dom_lua/test.server.lua
Normal file
7
plugin/rbx_dom_lua/test.server.lua
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
|
||||||
|
local LIB_ROOT = ReplicatedStorage.RbxDom
|
||||||
|
|
||||||
|
local TestEZ = require(ReplicatedStorage.TestEZ)
|
||||||
|
|
||||||
|
TestEZ.TestBootstrap:run({LIB_ROOT})
|
||||||
@@ -1,11 +1,19 @@
|
|||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
|
||||||
local TestEZ = require(ReplicatedStorage.Packages.TestEZ)
|
local TestEZ = require(ReplicatedStorage.TestEZ)
|
||||||
|
|
||||||
local Rojo = ReplicatedStorage.Rojo
|
local Rojo = ReplicatedStorage.Rojo
|
||||||
|
|
||||||
local Settings = require(Rojo.Plugin.Settings)
|
local DevSettings = require(Rojo.Plugin.DevSettings)
|
||||||
Settings:set("logLevel", "Trace")
|
|
||||||
Settings:set("typecheckingEnabled", true)
|
local setDevSettings = not DevSettings:hasChangedValues()
|
||||||
|
|
||||||
|
if setDevSettings then
|
||||||
|
DevSettings:createTestSettings()
|
||||||
|
end
|
||||||
|
|
||||||
require(Rojo.Plugin.runTests)(TestEZ)
|
require(Rojo.Plugin.runTests)(TestEZ)
|
||||||
|
|
||||||
|
if setDevSettings then
|
||||||
|
DevSettings:resetValues()
|
||||||
|
end
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
local Packages = script.Parent.Parent.Packages
|
local Http = require(script.Parent.Parent.Http)
|
||||||
local Http = require(Packages.Http)
|
local Log = require(script.Parent.Parent.Log)
|
||||||
local Log = require(Packages.Log)
|
local Promise = require(script.Parent.Parent.Promise)
|
||||||
local Promise = require(Packages.Promise)
|
|
||||||
|
|
||||||
local Config = require(script.Parent.Config)
|
local Config = require(script.Parent.Config)
|
||||||
local Types = require(script.Parent.Types)
|
local Types = require(script.Parent.Types)
|
||||||
@@ -11,6 +10,13 @@ local validateApiInfo = Types.ifEnabled(Types.ApiInfoResponse)
|
|||||||
local validateApiRead = Types.ifEnabled(Types.ApiReadResponse)
|
local validateApiRead = Types.ifEnabled(Types.ApiReadResponse)
|
||||||
local validateApiSubscribe = Types.ifEnabled(Types.ApiSubscribeResponse)
|
local validateApiSubscribe = Types.ifEnabled(Types.ApiSubscribeResponse)
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Returns a promise that will never resolve nor reject.
|
||||||
|
]]
|
||||||
|
local function hangingPromise()
|
||||||
|
return Promise.new(function() end)
|
||||||
|
end
|
||||||
|
|
||||||
local function rejectFailedRequests(response)
|
local function rejectFailedRequests(response)
|
||||||
if response.code >= 400 then
|
if response.code >= 400 then
|
||||||
local message = string.format("HTTP %s:\n%s", tostring(response.code), response.body)
|
local message = string.format("HTTP %s:\n%s", tostring(response.code), response.body)
|
||||||
@@ -24,17 +30,15 @@ end
|
|||||||
local function rejectWrongProtocolVersion(infoResponseBody)
|
local function rejectWrongProtocolVersion(infoResponseBody)
|
||||||
if infoResponseBody.protocolVersion ~= Config.protocolVersion then
|
if infoResponseBody.protocolVersion ~= Config.protocolVersion then
|
||||||
local message = (
|
local message = (
|
||||||
"Found a Rojo dev server, but it's using a different protocol version, and is incompatible."
|
"Found a Rojo dev server, but it's using a different protocol version, and is incompatible." ..
|
||||||
.. "\nMake sure you have matching versions of both the Rojo plugin and server!"
|
"\nMake sure you have matching versions of both the Rojo plugin and server!" ..
|
||||||
.. "\n\nYour client is version %s, with protocol version %s. It expects server version %s."
|
"\n\nYour client is version %s, with protocol version %s. It expects server version %s." ..
|
||||||
.. "\nYour server is version %s, with protocol version %s."
|
"\nYour server is version %s, with protocol version %s." ..
|
||||||
.. "\n\nGo to https://github.com/rojo-rbx/rojo for more details."
|
"\n\nGo to https://github.com/rojo-rbx/rojo for more details."
|
||||||
):format(
|
):format(
|
||||||
Version.display(Config.version),
|
Version.display(Config.version), Config.protocolVersion,
|
||||||
Config.protocolVersion,
|
|
||||||
Config.expectedServerVersionString,
|
Config.expectedServerVersionString,
|
||||||
infoResponseBody.serverVersion,
|
infoResponseBody.serverVersion, infoResponseBody.protocolVersion
|
||||||
infoResponseBody.protocolVersion
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return Promise.reject(message)
|
return Promise.reject(message)
|
||||||
@@ -61,11 +65,14 @@ local function rejectWrongPlaceId(infoResponseBody)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local message = (
|
local message = (
|
||||||
"Found a Rojo server, but its project is set to only be used with a specific list of places."
|
"Found a Rojo server, but its project is set to only be used with a specific list of places." ..
|
||||||
.. "\nYour place ID is %s, but needs to be one of these:"
|
"\nYour place ID is %s, but needs to be one of these:" ..
|
||||||
.. "\n%s"
|
"\n%s" ..
|
||||||
.. "\n\nTo change this list, edit 'servePlaceIds' in your .project.json file."
|
"\n\nTo change this list, edit 'servePlaceIds' in your .project.json file."
|
||||||
):format(tostring(game.PlaceId), table.concat(idList, "\n"))
|
):format(
|
||||||
|
tostring(game.PlaceId),
|
||||||
|
table.concat(idList, "\n")
|
||||||
|
)
|
||||||
|
|
||||||
return Promise.reject(message)
|
return Promise.reject(message)
|
||||||
end
|
end
|
||||||
@@ -78,14 +85,13 @@ local ApiContext = {}
|
|||||||
ApiContext.__index = ApiContext
|
ApiContext.__index = ApiContext
|
||||||
|
|
||||||
function ApiContext.new(baseUrl)
|
function ApiContext.new(baseUrl)
|
||||||
assert(type(baseUrl) == "string", "baseUrl must be a string")
|
assert(type(baseUrl) == "string")
|
||||||
|
|
||||||
local self = {
|
local self = {
|
||||||
__baseUrl = baseUrl,
|
__baseUrl = baseUrl,
|
||||||
__sessionId = nil,
|
__sessionId = nil,
|
||||||
__messageCursor = -1,
|
__messageCursor = -1,
|
||||||
__connected = true,
|
__connected = true,
|
||||||
__activeRequests = {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return setmetatable(self, ApiContext)
|
return setmetatable(self, ApiContext)
|
||||||
@@ -106,11 +112,6 @@ end
|
|||||||
|
|
||||||
function ApiContext:disconnect()
|
function ApiContext:disconnect()
|
||||||
self.__connected = false
|
self.__connected = false
|
||||||
for request in self.__activeRequests do
|
|
||||||
Log.trace("Cancelling request {}", request)
|
|
||||||
request:cancel()
|
|
||||||
end
|
|
||||||
self.__activeRequests = {}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function ApiContext:setMessageCursor(index)
|
function ApiContext:setMessageCursor(index)
|
||||||
@@ -140,15 +141,18 @@ end
|
|||||||
function ApiContext:read(ids)
|
function ApiContext:read(ids)
|
||||||
local url = ("%s/api/read/%s"):format(self.__baseUrl, table.concat(ids, ","))
|
local url = ("%s/api/read/%s"):format(self.__baseUrl, table.concat(ids, ","))
|
||||||
|
|
||||||
return Http.get(url):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
return Http.get(url)
|
||||||
if body.sessionId ~= self.__sessionId then
|
:andThen(rejectFailedRequests)
|
||||||
return Promise.reject("Server changed ID")
|
:andThen(Http.Response.json)
|
||||||
end
|
:andThen(function(body)
|
||||||
|
if body.sessionId ~= self.__sessionId then
|
||||||
|
return Promise.reject("Server changed ID")
|
||||||
|
end
|
||||||
|
|
||||||
assert(validateApiRead(body))
|
assert(validateApiRead(body))
|
||||||
|
|
||||||
return body
|
return body
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ApiContext:write(patch)
|
function ApiContext:write(patch)
|
||||||
@@ -185,58 +189,63 @@ function ApiContext:write(patch)
|
|||||||
|
|
||||||
body = Http.jsonEncode(body)
|
body = Http.jsonEncode(body)
|
||||||
|
|
||||||
return Http.post(url, body):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
return Http.post(url, body)
|
||||||
Log.info("Write response: {:?}", body)
|
:andThen(rejectFailedRequests)
|
||||||
|
:andThen(Http.Response.json)
|
||||||
|
:andThen(function(body)
|
||||||
|
Log.info("Write response: {:?}", body)
|
||||||
|
|
||||||
return body
|
return body
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ApiContext:retrieveMessages()
|
function ApiContext:retrieveMessages()
|
||||||
local url = ("%s/api/subscribe/%s"):format(self.__baseUrl, self.__messageCursor)
|
local url = ("%s/api/subscribe/%s"):format(self.__baseUrl, self.__messageCursor)
|
||||||
|
|
||||||
local function sendRequest()
|
local function sendRequest()
|
||||||
local request = Http.get(url):catch(function(err)
|
return Http.get(url)
|
||||||
if err.type == Http.Error.Kind.Timeout and self.__connected then
|
:catch(function(err)
|
||||||
return sendRequest()
|
if err.type == Http.Error.Kind.Timeout then
|
||||||
end
|
if self.__connected then
|
||||||
|
return sendRequest()
|
||||||
|
else
|
||||||
|
return hangingPromise()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Log.trace("Tracking request {}", request)
|
|
||||||
self.__activeRequests[request] = true
|
|
||||||
|
|
||||||
return request:finally(function(...)
|
|
||||||
Log.trace("Cleaning up request {}", request)
|
|
||||||
self.__activeRequests[request] = nil
|
|
||||||
return ...
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return sendRequest():andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
return sendRequest()
|
||||||
if body.sessionId ~= self.__sessionId then
|
:andThen(rejectFailedRequests)
|
||||||
return Promise.reject("Server changed ID")
|
:andThen(Http.Response.json)
|
||||||
end
|
:andThen(function(body)
|
||||||
|
if body.sessionId ~= self.__sessionId then
|
||||||
|
return Promise.reject("Server changed ID")
|
||||||
|
end
|
||||||
|
|
||||||
assert(validateApiSubscribe(body))
|
assert(validateApiSubscribe(body))
|
||||||
|
|
||||||
self:setMessageCursor(body.messageCursor)
|
self:setMessageCursor(body.messageCursor)
|
||||||
|
|
||||||
return body.messages
|
return body.messages
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ApiContext:open(id)
|
function ApiContext:open(id)
|
||||||
local url = ("%s/api/open/%s"):format(self.__baseUrl, id)
|
local url = ("%s/api/open/%s"):format(self.__baseUrl, id)
|
||||||
|
|
||||||
return Http.post(url, ""):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
return Http.post(url, "")
|
||||||
if body.sessionId ~= self.__sessionId then
|
:andThen(rejectFailedRequests)
|
||||||
return Promise.reject("Server changed ID")
|
:andThen(Http.Response.json)
|
||||||
end
|
:andThen(function(body)
|
||||||
|
if body.sessionId ~= self.__sessionId then
|
||||||
|
return Promise.reject("Server changed ID")
|
||||||
|
end
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return ApiContext
|
return ApiContext
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Rojo.Roact)
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
@@ -24,10 +23,8 @@ local function BorderedContainer(props)
|
|||||||
layoutOrder = props.layoutOrder,
|
layoutOrder = props.layoutOrder,
|
||||||
}, {
|
}, {
|
||||||
Content = e("Frame", {
|
Content = e("Frame", {
|
||||||
Size = UDim2.new(1, -2, 1, -2),
|
Size = UDim2.new(1, 0, 1, 0),
|
||||||
Position = UDim2.new(0, 1, 0, 1),
|
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
ZIndex = 2,
|
|
||||||
}, props[Roact.Children]),
|
}, props[Roact.Children]),
|
||||||
|
|
||||||
Border = e(SlicedImage, {
|
Border = e(SlicedImage, {
|
||||||
@@ -41,4 +38,4 @@ local function BorderedContainer(props)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return BorderedContainer
|
return BorderedContainer
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Rojo.Roact)
|
||||||
local Flipper = require(Packages.Flipper)
|
local Flipper = require(Rojo.Flipper)
|
||||||
|
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||||
|
|
||||||
local SlicedImage = require(script.Parent.SlicedImage)
|
local SlicedImage = require(script.Parent.SlicedImage)
|
||||||
local Tooltip = require(script.Parent.Tooltip)
|
|
||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
@@ -23,10 +21,12 @@ end
|
|||||||
|
|
||||||
function Checkbox:didUpdate(lastProps)
|
function Checkbox:didUpdate(lastProps)
|
||||||
if lastProps.active ~= self.props.active then
|
if lastProps.active ~= self.props.active then
|
||||||
self.motor:setGoal(Flipper.Spring.new(self.props.active and 1 or 0, {
|
self.motor:setGoal(
|
||||||
frequency = 6,
|
Flipper.Spring.new(self.props.active and 1 or 0, {
|
||||||
dampingRatio = 1.1,
|
frequency = 6,
|
||||||
}))
|
dampingRatio = 1.1,
|
||||||
|
})
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -49,18 +49,8 @@ function Checkbox:render()
|
|||||||
ZIndex = self.props.zIndex,
|
ZIndex = self.props.zIndex,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
|
|
||||||
[Roact.Event.Activated] = function()
|
[Roact.Event.Activated] = self.props.onClick,
|
||||||
if self.props.locked then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
self.props.onClick()
|
|
||||||
end,
|
|
||||||
}, {
|
}, {
|
||||||
StateTip = e(Tooltip.Trigger, {
|
|
||||||
text = (if self.props.locked then "[LOCKED] " else "")
|
|
||||||
.. (if self.props.active then "Enabled" else "Disabled"),
|
|
||||||
}),
|
|
||||||
|
|
||||||
Active = e(SlicedImage, {
|
Active = e(SlicedImage, {
|
||||||
slice = Assets.Slices.RoundedBackground,
|
slice = Assets.Slices.RoundedBackground,
|
||||||
color = theme.Active.BackgroundColor,
|
color = theme.Active.BackgroundColor,
|
||||||
@@ -69,7 +59,7 @@ function Checkbox:render()
|
|||||||
zIndex = 2,
|
zIndex = 2,
|
||||||
}, {
|
}, {
|
||||||
Icon = e("ImageLabel", {
|
Icon = e("ImageLabel", {
|
||||||
Image = if self.props.locked then Assets.Images.Checkbox.Locked else Assets.Images.Checkbox.Active,
|
Image = Assets.Images.Checkbox.Active,
|
||||||
ImageColor3 = theme.Active.IconColor,
|
ImageColor3 = theme.Active.IconColor,
|
||||||
ImageTransparency = activeTransparency,
|
ImageTransparency = activeTransparency,
|
||||||
|
|
||||||
@@ -88,9 +78,7 @@ function Checkbox:render()
|
|||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
}, {
|
}, {
|
||||||
Icon = e("ImageLabel", {
|
Icon = e("ImageLabel", {
|
||||||
Image = if self.props.locked
|
Image = Assets.Images.Checkbox.Inactive,
|
||||||
then Assets.Images.Checkbox.Locked
|
|
||||||
else Assets.Images.Checkbox.Inactive,
|
|
||||||
ImageColor3 = theme.Inactive.IconColor,
|
ImageColor3 = theme.Inactive.IconColor,
|
||||||
ImageTransparency = self.props.transparency,
|
ImageTransparency = self.props.transparency,
|
||||||
|
|
||||||
@@ -105,4 +93,4 @@ function Checkbox:render()
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Checkbox
|
return Checkbox
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
|
||||||
local Highlighter = require(Packages.Highlighter)
|
|
||||||
Highlighter.matchStudioSettings()
|
|
||||||
|
|
||||||
local e = Roact.createElement
|
|
||||||
|
|
||||||
local CodeLabel = Roact.PureComponent:extend("CodeLabel")
|
|
||||||
|
|
||||||
function CodeLabel:init()
|
|
||||||
self.labelRef = Roact.createRef()
|
|
||||||
self.highlightsRef = Roact.createRef()
|
|
||||||
end
|
|
||||||
|
|
||||||
function CodeLabel:didMount()
|
|
||||||
Highlighter.highlight({
|
|
||||||
textObject = self.labelRef:getValue(),
|
|
||||||
})
|
|
||||||
self:updateHighlights()
|
|
||||||
end
|
|
||||||
|
|
||||||
function CodeLabel:didUpdate()
|
|
||||||
self:updateHighlights()
|
|
||||||
end
|
|
||||||
|
|
||||||
function CodeLabel:updateHighlights()
|
|
||||||
local highlights = self.highlightsRef:getValue()
|
|
||||||
if not highlights then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, lineLabel in highlights:GetChildren() do
|
|
||||||
local lineNum = tonumber(string.match(lineLabel.Name, "%d+") or "0")
|
|
||||||
lineLabel.BackgroundColor3 = self.props.lineBackground
|
|
||||||
lineLabel.BorderSizePixel = 0
|
|
||||||
lineLabel.BackgroundTransparency = if self.props.markedLines[lineNum] then 0.25 else 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function CodeLabel:render()
|
|
||||||
return e("TextLabel", {
|
|
||||||
Size = self.props.size,
|
|
||||||
Position = self.props.position,
|
|
||||||
Text = self.props.text,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Font = Enum.Font.RobotoMono,
|
|
||||||
TextSize = 16,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextYAlignment = Enum.TextYAlignment.Top,
|
|
||||||
TextColor3 = Color3.fromRGB(255, 255, 255),
|
|
||||||
[Roact.Ref] = self.labelRef,
|
|
||||||
}, {
|
|
||||||
SyntaxHighlights = e("Folder", {
|
|
||||||
[Roact.Ref] = self.highlightsRef,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
return CodeLabel
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
local TextService = game:GetService("TextService")
|
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
|
||||||
local Plugin = Rojo.Plugin
|
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
|
||||||
local Flipper = require(Packages.Flipper)
|
|
||||||
|
|
||||||
local Assets = require(Plugin.Assets)
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
|
||||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
|
||||||
|
|
||||||
local SlicedImage = require(script.Parent.SlicedImage)
|
|
||||||
local ScrollingFrame = require(script.Parent.ScrollingFrame)
|
|
||||||
|
|
||||||
local e = Roact.createElement
|
|
||||||
|
|
||||||
local Dropdown = Roact.Component:extend("Dropdown")
|
|
||||||
|
|
||||||
function Dropdown:init()
|
|
||||||
self.openMotor = Flipper.SingleMotor.new(0)
|
|
||||||
self.openBinding = bindingUtil.fromMotor(self.openMotor)
|
|
||||||
|
|
||||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
|
||||||
|
|
||||||
self:setState({
|
|
||||||
open = false,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function Dropdown:didUpdate(prevProps)
|
|
||||||
if self.props.locked and not prevProps.locked then
|
|
||||||
self:setState({
|
|
||||||
open = false,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
self.openMotor:setGoal(Flipper.Spring.new(self.state.open and 1 or 0, {
|
|
||||||
frequency = 6,
|
|
||||||
dampingRatio = 1.1,
|
|
||||||
}))
|
|
||||||
end
|
|
||||||
|
|
||||||
function Dropdown:render()
|
|
||||||
return Theme.with(function(theme)
|
|
||||||
theme = theme.Dropdown
|
|
||||||
|
|
||||||
local optionButtons = {}
|
|
||||||
local width = -1
|
|
||||||
for i, option in self.props.options do
|
|
||||||
local text = tostring(option or "")
|
|
||||||
local textSize = TextService:GetTextSize(text, 15, Enum.Font.GothamMedium, Vector2.new(math.huge, 20))
|
|
||||||
if textSize.X > width then
|
|
||||||
width = textSize.X
|
|
||||||
end
|
|
||||||
|
|
||||||
optionButtons[text] = e("TextButton", {
|
|
||||||
Text = text,
|
|
||||||
LayoutOrder = i,
|
|
||||||
Size = UDim2.new(1, 0, 0, 24),
|
|
||||||
BackgroundColor3 = theme.BackgroundColor,
|
|
||||||
TextTransparency = self.props.transparency,
|
|
||||||
BackgroundTransparency = self.props.transparency,
|
|
||||||
BorderSizePixel = 0,
|
|
||||||
TextColor3 = theme.TextColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextSize = 15,
|
|
||||||
Font = Enum.Font.GothamMedium,
|
|
||||||
|
|
||||||
[Roact.Event.Activated] = function()
|
|
||||||
if self.props.locked then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
self:setState({
|
|
||||||
open = false,
|
|
||||||
})
|
|
||||||
self.props.onClick(option)
|
|
||||||
end,
|
|
||||||
}, {
|
|
||||||
Padding = e("UIPadding", {
|
|
||||||
PaddingLeft = UDim.new(0, 6),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
return e("ImageButton", {
|
|
||||||
Size = UDim2.new(0, width + 50, 0, 28),
|
|
||||||
Position = self.props.position,
|
|
||||||
AnchorPoint = self.props.anchorPoint,
|
|
||||||
LayoutOrder = self.props.layoutOrder,
|
|
||||||
ZIndex = self.props.zIndex,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
[Roact.Event.Activated] = function()
|
|
||||||
if self.props.locked then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
self:setState({
|
|
||||||
open = not self.state.open,
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
}, {
|
|
||||||
Border = e(SlicedImage, {
|
|
||||||
slice = Assets.Slices.RoundedBorder,
|
|
||||||
color = theme.BorderColor,
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
|
||||||
}, {
|
|
||||||
DropArrow = e("ImageLabel", {
|
|
||||||
Image = if self.props.locked then Assets.Images.Dropdown.Locked else Assets.Images.Dropdown.Arrow,
|
|
||||||
ImageColor3 = self.openBinding:map(function(a)
|
|
||||||
return theme.Closed.IconColor:Lerp(theme.Open.IconColor, a)
|
|
||||||
end),
|
|
||||||
ImageTransparency = self.props.transparency,
|
|
||||||
|
|
||||||
Size = UDim2.new(0, 18, 0, 18),
|
|
||||||
Position = UDim2.new(1, -6, 0.5, 0),
|
|
||||||
AnchorPoint = Vector2.new(1, 0.5),
|
|
||||||
Rotation = self.openBinding:map(function(a)
|
|
||||||
return a * 180
|
|
||||||
end),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
}),
|
|
||||||
Active = e("TextLabel", {
|
|
||||||
Size = UDim2.new(1, -30, 1, 0),
|
|
||||||
Position = UDim2.new(0, 6, 0, 0),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Text = self.props.active,
|
|
||||||
Font = Enum.Font.GothamMedium,
|
|
||||||
TextSize = 15,
|
|
||||||
TextColor3 = theme.TextColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = self.props.transparency,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
Options = if self.state.open
|
|
||||||
then e(SlicedImage, {
|
|
||||||
slice = Assets.Slices.RoundedBackground,
|
|
||||||
color = theme.BackgroundColor,
|
|
||||||
position = UDim2.new(1, 0, 1, 3),
|
|
||||||
size = self.openBinding:map(function(a)
|
|
||||||
return UDim2.new(1, 0, a * math.min(3, #self.props.options), 0)
|
|
||||||
end),
|
|
||||||
anchorPoint = Vector2.new(1, 0),
|
|
||||||
}, {
|
|
||||||
Border = e(SlicedImage, {
|
|
||||||
slice = Assets.Slices.RoundedBorder,
|
|
||||||
color = theme.BorderColor,
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
|
||||||
}),
|
|
||||||
ScrollingFrame = e(ScrollingFrame, {
|
|
||||||
size = UDim2.new(1, -4, 1, -4),
|
|
||||||
position = UDim2.new(0, 2, 0, 2),
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
contentSize = self.contentSize,
|
|
||||||
}, {
|
|
||||||
Layout = e("UIListLayout", {
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
|
||||||
FillDirection = Enum.FillDirection.Vertical,
|
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
||||||
Padding = UDim.new(0, 0),
|
|
||||||
|
|
||||||
[Roact.Change.AbsoluteContentSize] = function(object)
|
|
||||||
self.setContentSize(object.AbsoluteContentSize)
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
Roact.createFragment(optionButtons),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
else nil,
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Dropdown
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Rojo.Roact)
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
@@ -53,4 +52,4 @@ local function Header(props)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Header
|
return Header
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Rojo.Roact)
|
||||||
local Flipper = require(Packages.Flipper)
|
local Flipper = require(Rojo.Flipper)
|
||||||
|
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||||
@@ -30,7 +29,6 @@ function IconButton:render()
|
|||||||
Position = self.props.position,
|
Position = self.props.position,
|
||||||
AnchorPoint = self.props.anchorPoint,
|
AnchorPoint = self.props.anchorPoint,
|
||||||
|
|
||||||
Visible = self.props.visible,
|
|
||||||
LayoutOrder = self.props.layoutOrder,
|
LayoutOrder = self.props.layoutOrder,
|
||||||
ZIndex = self.props.zIndex,
|
ZIndex = self.props.zIndex,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
@@ -38,11 +36,15 @@ function IconButton:render()
|
|||||||
[Roact.Event.Activated] = self.props.onClick,
|
[Roact.Event.Activated] = self.props.onClick,
|
||||||
|
|
||||||
[Roact.Event.MouseEnter] = function()
|
[Roact.Event.MouseEnter] = function()
|
||||||
self.motor:setGoal(Flipper.Spring.new(1, HOVER_SPRING_PROPS))
|
self.motor:setGoal(
|
||||||
|
Flipper.Spring.new(1, HOVER_SPRING_PROPS)
|
||||||
|
)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
[Roact.Event.MouseLeave] = function()
|
[Roact.Event.MouseLeave] = function()
|
||||||
self.motor:setGoal(Flipper.Spring.new(0, HOVER_SPRING_PROPS))
|
self.motor:setGoal(
|
||||||
|
Flipper.Spring.new(0, HOVER_SPRING_PROPS)
|
||||||
|
)
|
||||||
end,
|
end,
|
||||||
}, {
|
}, {
|
||||||
Icon = e("ImageLabel", {
|
Icon = e("ImageLabel", {
|
||||||
@@ -71,9 +73,7 @@ function IconButton:render()
|
|||||||
|
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Children = Roact.createFragment(self.props[Roact.Children]),
|
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return IconButton
|
return IconButton
|
||||||
@@ -1,265 +0,0 @@
|
|||||||
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 ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
|
||||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
|
||||||
local DisplayValue = require(script.Parent.DisplayValue)
|
|
||||||
|
|
||||||
local EMPTY_TABLE = {}
|
|
||||||
|
|
||||||
local e = Roact.createElement
|
|
||||||
|
|
||||||
local ChangeList = Roact.Component:extend("ChangeList")
|
|
||||||
|
|
||||||
function ChangeList:init()
|
|
||||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChangeList:render()
|
|
||||||
return Theme.with(function(theme)
|
|
||||||
local props = self.props
|
|
||||||
local changes = props.changes
|
|
||||||
|
|
||||||
-- Color alternating rows for readability
|
|
||||||
local rowTransparency = props.transparency:map(function(t)
|
|
||||||
return 0.93 + (0.07 * t)
|
|
||||||
end)
|
|
||||||
|
|
||||||
local rows = {}
|
|
||||||
local pad = {
|
|
||||||
PaddingLeft = UDim.new(0, 5),
|
|
||||||
PaddingRight = UDim.new(0, 5),
|
|
||||||
}
|
|
||||||
|
|
||||||
local headers = e("Frame", {
|
|
||||||
Size = UDim2.new(1, 0, 0, 30),
|
|
||||||
BackgroundTransparency = rowTransparency,
|
|
||||||
BackgroundColor3 = theme.Diff.Row,
|
|
||||||
LayoutOrder = 0,
|
|
||||||
}, {
|
|
||||||
Padding = e("UIPadding", pad),
|
|
||||||
Layout = e("UIListLayout", {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
||||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
}),
|
|
||||||
A = e("TextLabel", {
|
|
||||||
Text = tostring(changes[1][1]),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Font = Enum.Font.GothamBold,
|
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
||||||
Size = UDim2.new(0.3, 0, 1, 0),
|
|
||||||
LayoutOrder = 1,
|
|
||||||
}),
|
|
||||||
B = e("TextLabel", {
|
|
||||||
Text = tostring(changes[1][2]),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Font = Enum.Font.GothamBold,
|
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
||||||
Size = UDim2.new(0.35, 0, 1, 0),
|
|
||||||
LayoutOrder = 2,
|
|
||||||
}),
|
|
||||||
C = e("TextLabel", {
|
|
||||||
Text = tostring(changes[1][3]),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Font = Enum.Font.GothamBold,
|
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
||||||
Size = UDim2.new(0.35, 0, 1, 0),
|
|
||||||
LayoutOrder = 3,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
for row, values in changes do
|
|
||||||
if row == 1 then
|
|
||||||
continue -- Skip headers, already handled above
|
|
||||||
end
|
|
||||||
|
|
||||||
local metadata = values[4] or EMPTY_TABLE
|
|
||||||
local isWarning = metadata.isWarning
|
|
||||||
|
|
||||||
-- Special case for .Source updates
|
|
||||||
-- because we want to display a syntax highlighted diff for better UX
|
|
||||||
if self.props.showSourceDiff and tostring(values[1]) == "Source" then
|
|
||||||
rows[row] = e("Frame", {
|
|
||||||
Size = UDim2.new(1, 0, 0, 30),
|
|
||||||
BackgroundTransparency = row % 2 ~= 0 and rowTransparency or 1,
|
|
||||||
BackgroundColor3 = theme.Diff.Row,
|
|
||||||
BorderSizePixel = 0,
|
|
||||||
LayoutOrder = row,
|
|
||||||
}, {
|
|
||||||
Padding = e("UIPadding", pad),
|
|
||||||
Layout = e("UIListLayout", {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
||||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
}),
|
|
||||||
A = e("TextLabel", {
|
|
||||||
Text = (if isWarning then "⚠ " else "") .. tostring(values[1]),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Font = Enum.Font.GothamMedium,
|
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = if isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
||||||
Size = UDim2.new(0.3, 0, 1, 0),
|
|
||||||
LayoutOrder = 1,
|
|
||||||
}),
|
|
||||||
Button = e("TextButton", {
|
|
||||||
Text = "",
|
|
||||||
Size = UDim2.new(0.7, 0, 1, -4),
|
|
||||||
LayoutOrder = 2,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
[Roact.Event.Activated] = function()
|
|
||||||
if props.showSourceDiff then
|
|
||||||
props.showSourceDiff(tostring(values[2]), tostring(values[3]))
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
}, {
|
|
||||||
e(BorderedContainer, {
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
|
||||||
transparency = self.props.transparency:map(function(t)
|
|
||||||
return 0.5 + (0.5 * t)
|
|
||||||
end),
|
|
||||||
}, {
|
|
||||||
Layout = e("UIListLayout", {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
||||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
Padding = UDim.new(0, 5),
|
|
||||||
}),
|
|
||||||
Label = e("TextLabel", {
|
|
||||||
Text = "View Diff",
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Font = Enum.Font.GothamMedium,
|
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
||||||
Size = UDim2.new(0, 65, 1, 0),
|
|
||||||
LayoutOrder = 1,
|
|
||||||
}),
|
|
||||||
Icon = e("ImageLabel", {
|
|
||||||
Image = Assets.Images.Icons.Expand,
|
|
||||||
ImageColor3 = theme.Settings.Setting.DescriptionColor,
|
|
||||||
ImageTransparency = self.props.transparency,
|
|
||||||
|
|
||||||
Size = UDim2.new(0, 16, 0, 16),
|
|
||||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
LayoutOrder = 2,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
rows[row] = e("Frame", {
|
|
||||||
Size = UDim2.new(1, 0, 0, 30),
|
|
||||||
BackgroundTransparency = row % 2 ~= 0 and rowTransparency or 1,
|
|
||||||
BackgroundColor3 = theme.Diff.Row,
|
|
||||||
BorderSizePixel = 0,
|
|
||||||
LayoutOrder = row,
|
|
||||||
}, {
|
|
||||||
Padding = e("UIPadding", pad),
|
|
||||||
Layout = e("UIListLayout", {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
||||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
}),
|
|
||||||
A = e("TextLabel", {
|
|
||||||
Text = (if isWarning then "⚠ " else "") .. tostring(values[1]),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Font = Enum.Font.GothamMedium,
|
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = if isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
||||||
Size = UDim2.new(0.3, 0, 1, 0),
|
|
||||||
LayoutOrder = 1,
|
|
||||||
}),
|
|
||||||
B = e(
|
|
||||||
"Frame",
|
|
||||||
{
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Size = UDim2.new(0.35, 0, 1, 0),
|
|
||||||
LayoutOrder = 2,
|
|
||||||
},
|
|
||||||
e(DisplayValue, {
|
|
||||||
value = values[2],
|
|
||||||
transparency = props.transparency,
|
|
||||||
textColor = if isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
C = e(
|
|
||||||
"Frame",
|
|
||||||
{
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Size = UDim2.new(0.35, 0, 1, 0),
|
|
||||||
LayoutOrder = 3,
|
|
||||||
},
|
|
||||||
e(DisplayValue, {
|
|
||||||
value = values[3],
|
|
||||||
transparency = props.transparency,
|
|
||||||
textColor = if isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
table.insert(
|
|
||||||
rows,
|
|
||||||
e("UIListLayout", {
|
|
||||||
FillDirection = Enum.FillDirection.Vertical,
|
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
||||||
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Top,
|
|
||||||
|
|
||||||
[Roact.Change.AbsoluteContentSize] = function(object)
|
|
||||||
self.setContentSize(object.AbsoluteContentSize)
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
return e("Frame", {
|
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
}, {
|
|
||||||
Headers = headers,
|
|
||||||
Values = e(ScrollingFrame, {
|
|
||||||
size = UDim2.new(1, 0, 1, -30),
|
|
||||||
position = UDim2.new(0, 0, 0, 30),
|
|
||||||
contentSize = self.contentSize,
|
|
||||||
transparency = props.transparency,
|
|
||||||
}, rows),
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return ChangeList
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
|
||||||
local Plugin = Rojo.Plugin
|
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
|
||||||
|
|
||||||
local e = Roact.createElement
|
|
||||||
|
|
||||||
local function DisplayValue(props)
|
|
||||||
return Theme.with(function(theme)
|
|
||||||
local t = typeof(props.value)
|
|
||||||
if t == "Color3" then
|
|
||||||
-- Colors get a blot that shows the color
|
|
||||||
return Roact.createFragment({
|
|
||||||
Blot = e("Frame", {
|
|
||||||
BackgroundTransparency = props.transparency,
|
|
||||||
BackgroundColor3 = props.value,
|
|
||||||
Size = UDim2.new(0, 20, 0, 20),
|
|
||||||
Position = UDim2.new(0, 0, 0.5, 0),
|
|
||||||
AnchorPoint = Vector2.new(0, 0.5),
|
|
||||||
}, {
|
|
||||||
Corner = e("UICorner", {
|
|
||||||
CornerRadius = UDim.new(0, 4),
|
|
||||||
}),
|
|
||||||
Stroke = e("UIStroke", {
|
|
||||||
Color = theme.BorderedContainer.BorderColor,
|
|
||||||
Transparency = props.transparency,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
Label = e("TextLabel", {
|
|
||||||
Text = string.format("%d,%d,%d", props.value.R * 255, props.value.G * 255, props.value.B * 255),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Font = Enum.Font.GothamMedium,
|
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = props.textColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
||||||
Size = UDim2.new(1, -25, 1, 0),
|
|
||||||
Position = UDim2.new(0, 25, 0, 0),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
elseif t == "table" then
|
|
||||||
-- Showing a memory address for tables is useless, so we want to show the best we can
|
|
||||||
local textRepresentation = nil
|
|
||||||
|
|
||||||
local meta = getmetatable(props.value)
|
|
||||||
if meta and meta.__tostring then
|
|
||||||
-- If the table has a tostring metamethod, use that
|
|
||||||
textRepresentation = tostring(props.value)
|
|
||||||
elseif next(props.value) == nil then
|
|
||||||
-- If it's empty, show empty braces
|
|
||||||
textRepresentation = "{}"
|
|
||||||
elseif next(props.value) == 1 then
|
|
||||||
-- We don't need to support mixed tables, so checking the first key is enough
|
|
||||||
-- to determine if it's a simple array
|
|
||||||
local out, i = table.create(#props.value), 0
|
|
||||||
for k, v in props.value do
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
-- Wrap strings in quotes
|
|
||||||
if type(v) == "string" then
|
|
||||||
v = '"' .. v .. '"'
|
|
||||||
end
|
|
||||||
|
|
||||||
out[i] = tostring(v)
|
|
||||||
end
|
|
||||||
textRepresentation = "{ " .. table.concat(out, ", ") .. " }"
|
|
||||||
else
|
|
||||||
-- Otherwise, show the table contents as a dictionary
|
|
||||||
local out, i = {}, 0
|
|
||||||
for k, v in pairs(props.value) do
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
-- Wrap strings in quotes
|
|
||||||
if type(k) == "string" then
|
|
||||||
k = '"' .. k .. '"'
|
|
||||||
end
|
|
||||||
if type(v) == "string" then
|
|
||||||
v = '"' .. v .. '"'
|
|
||||||
end
|
|
||||||
|
|
||||||
out[i] = string.format("[%s] = %s", tostring(k), tostring(v))
|
|
||||||
end
|
|
||||||
textRepresentation = "{ " .. table.concat(out, ", ") .. " }"
|
|
||||||
end
|
|
||||||
|
|
||||||
return e("TextLabel", {
|
|
||||||
Text = textRepresentation,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Font = Enum.Font.GothamMedium,
|
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = props.textColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
-- TODO: Maybe add visualizations to other datatypes?
|
|
||||||
-- Or special text handling tostring for some?
|
|
||||||
-- Will add as needed, let's see what cases arise.
|
|
||||||
|
|
||||||
return e("TextLabel", {
|
|
||||||
Text = string.gsub(tostring(props.value), "%s", " "),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Font = Enum.Font.GothamMedium,
|
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = props.textColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return DisplayValue
|
|
||||||
@@ -1,220 +0,0 @@
|
|||||||
local SelectionService = game:GetService("Selection")
|
|
||||||
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 Assets = require(Plugin.Assets)
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
|
||||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
|
||||||
|
|
||||||
local e = Roact.createElement
|
|
||||||
|
|
||||||
local ChangeList = require(script.Parent.ChangeList)
|
|
||||||
local Tooltip = require(script.Parent.Parent.Tooltip)
|
|
||||||
|
|
||||||
local Expansion = Roact.Component:extend("Expansion")
|
|
||||||
|
|
||||||
function Expansion:render()
|
|
||||||
local props = self.props
|
|
||||||
|
|
||||||
if not props.rendered then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return e("Frame", {
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Size = UDim2.new(1, -props.indent, 1, -30),
|
|
||||||
Position = UDim2.new(0, props.indent, 0, 30),
|
|
||||||
}, {
|
|
||||||
ChangeList = e(ChangeList, {
|
|
||||||
changes = props.changeList,
|
|
||||||
transparency = props.transparency,
|
|
||||||
showSourceDiff = props.showSourceDiff,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
local DomLabel = Roact.Component:extend("DomLabel")
|
|
||||||
|
|
||||||
function DomLabel:init()
|
|
||||||
local initHeight = self.props.elementHeight:getValue()
|
|
||||||
self.expanded = initHeight > 30
|
|
||||||
|
|
||||||
self.motor = Flipper.SingleMotor.new(initHeight)
|
|
||||||
self.binding = bindingUtil.fromMotor(self.motor)
|
|
||||||
|
|
||||||
self:setState({
|
|
||||||
renderExpansion = self.expanded,
|
|
||||||
})
|
|
||||||
self.motor:onStep(function(value)
|
|
||||||
local renderExpansion = value > 30
|
|
||||||
|
|
||||||
self.props.setElementHeight(value)
|
|
||||||
if self.props.updateEvent then
|
|
||||||
self.props.updateEvent:Fire()
|
|
||||||
end
|
|
||||||
|
|
||||||
self:setState(function(state)
|
|
||||||
if state.renderExpansion == renderExpansion then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
renderExpansion = renderExpansion,
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function DomLabel:didUpdate(prevProps)
|
|
||||||
if
|
|
||||||
prevProps.instance ~= self.props.instance
|
|
||||||
or prevProps.patchType ~= self.props.patchType
|
|
||||||
or prevProps.name ~= self.props.name
|
|
||||||
or prevProps.changeList ~= self.props.changeList
|
|
||||||
then
|
|
||||||
-- Close the expansion when the domlabel is changed to a different thing
|
|
||||||
self.expanded = false
|
|
||||||
self.motor:setGoal(Flipper.Spring.new(30, {
|
|
||||||
frequency = 5,
|
|
||||||
dampingRatio = 1,
|
|
||||||
}))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function DomLabel:render()
|
|
||||||
local props = self.props
|
|
||||||
|
|
||||||
return Theme.with(function(theme)
|
|
||||||
local iconProps = StudioService:GetClassIcon(props.className)
|
|
||||||
local indent = (props.depth or 0) * 20 + 25
|
|
||||||
|
|
||||||
-- Line guides help indent depth remain readable
|
|
||||||
local lineGuides = {}
|
|
||||||
for i = 1, props.depth or 0 do
|
|
||||||
table.insert(
|
|
||||||
lineGuides,
|
|
||||||
e("Frame", {
|
|
||||||
Name = "Line_" .. i,
|
|
||||||
Size = UDim2.new(0, 2, 1, 2),
|
|
||||||
Position = UDim2.new(0, (20 * i) + 15, 0, -1),
|
|
||||||
BorderSizePixel = 0,
|
|
||||||
BackgroundTransparency = props.transparency,
|
|
||||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
return e("Frame", {
|
|
||||||
Name = "Change",
|
|
||||||
ClipsDescendants = true,
|
|
||||||
BackgroundColor3 = if props.patchType then theme.Diff[props.patchType] else nil,
|
|
||||||
BorderSizePixel = 0,
|
|
||||||
BackgroundTransparency = props.patchType and props.transparency or 1,
|
|
||||||
Size = self.binding:map(function(expand)
|
|
||||||
return UDim2.new(1, 0, 0, expand)
|
|
||||||
end),
|
|
||||||
}, {
|
|
||||||
Padding = e("UIPadding", {
|
|
||||||
PaddingLeft = UDim.new(0, 10),
|
|
||||||
PaddingRight = UDim.new(0, 10),
|
|
||||||
}),
|
|
||||||
Button = e("TextButton", {
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Text = "",
|
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
|
||||||
[Roact.Event.Activated] = function(_rbx: Instance, _input: InputObject, clickCount: number)
|
|
||||||
if clickCount == 1 then
|
|
||||||
-- Double click opens the instance in explorer
|
|
||||||
self.lastDoubleClickTime = os.clock()
|
|
||||||
if props.instance then
|
|
||||||
SelectionService:Set({ props.instance })
|
|
||||||
end
|
|
||||||
elseif clickCount == 0 then
|
|
||||||
-- Single click expands the changes
|
|
||||||
task.wait(0.25)
|
|
||||||
if os.clock() - (self.lastDoubleClickTime or 0) <= 0.25 then
|
|
||||||
-- This is a double click, so don't expand
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if props.changeList then
|
|
||||||
self.expanded = not self.expanded
|
|
||||||
local goalHeight = 30
|
|
||||||
+ (if self.expanded then math.clamp(#props.changeList * 30, 30, 30 * 6) else 0)
|
|
||||||
self.motor:setGoal(Flipper.Spring.new(goalHeight, {
|
|
||||||
frequency = 5,
|
|
||||||
dampingRatio = 1,
|
|
||||||
}))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
}, {
|
|
||||||
StateTip = if (props.instance or props.changeList)
|
|
||||||
then e(Tooltip.Trigger, {
|
|
||||||
text = (if props.changeList
|
|
||||||
then "Click to " .. (if self.expanded then "hide" else "view") .. " changes"
|
|
||||||
else "") .. (if props.instance
|
|
||||||
then (if props.changeList then " & d" else "D") .. "ouble click to open in Explorer"
|
|
||||||
else ""),
|
|
||||||
})
|
|
||||||
else nil,
|
|
||||||
}),
|
|
||||||
Expansion = if props.changeList
|
|
||||||
then e(Expansion, {
|
|
||||||
rendered = self.state.renderExpansion,
|
|
||||||
indent = indent,
|
|
||||||
transparency = props.transparency,
|
|
||||||
changeList = props.changeList,
|
|
||||||
showSourceDiff = props.showSourceDiff,
|
|
||||||
})
|
|
||||||
else nil,
|
|
||||||
DiffIcon = if props.patchType
|
|
||||||
then e("ImageLabel", {
|
|
||||||
Image = Assets.Images.Diff[props.patchType],
|
|
||||||
ImageColor3 = theme.AddressEntry.PlaceholderColor,
|
|
||||||
ImageTransparency = props.transparency,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Size = UDim2.new(0, 20, 0, 20),
|
|
||||||
Position = UDim2.new(0, 0, 0, 15),
|
|
||||||
AnchorPoint = Vector2.new(0, 0.5),
|
|
||||||
})
|
|
||||||
else nil,
|
|
||||||
ClassIcon = e("ImageLabel", {
|
|
||||||
Image = iconProps.Image,
|
|
||||||
ImageTransparency = props.transparency,
|
|
||||||
ImageRectOffset = iconProps.ImageRectOffset,
|
|
||||||
ImageRectSize = iconProps.ImageRectSize,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Size = UDim2.new(0, 20, 0, 20),
|
|
||||||
Position = UDim2.new(0, indent, 0, 15),
|
|
||||||
AnchorPoint = Vector2.new(0, 0.5),
|
|
||||||
}),
|
|
||||||
InstanceName = e("TextLabel", {
|
|
||||||
Text = (if props.isWarning then "⚠ " else "") .. props.name .. (props.hint and string.format(
|
|
||||||
' <font color="#%s">%s</font>',
|
|
||||||
theme.AddressEntry.PlaceholderColor:ToHex(),
|
|
||||||
props.hint
|
|
||||||
) or ""),
|
|
||||||
RichText = true,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Font = Enum.Font.GothamMedium,
|
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = if props.isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
||||||
Size = UDim2.new(1, -indent - 50, 0, 30),
|
|
||||||
Position = UDim2.new(0, indent + 30, 0, 0),
|
|
||||||
}),
|
|
||||||
LineGuides = e("Folder", nil, lineGuides),
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return DomLabel
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
|
||||||
local Plugin = Rojo.Plugin
|
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
|
||||||
|
|
||||||
local PatchTree = require(Plugin.PatchTree)
|
|
||||||
local PatchSet = require(Plugin.PatchSet)
|
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
|
||||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
|
||||||
local VirtualScroller = require(Plugin.App.Components.VirtualScroller)
|
|
||||||
|
|
||||||
local e = Roact.createElement
|
|
||||||
|
|
||||||
local DomLabel = require(script.DomLabel)
|
|
||||||
|
|
||||||
local PatchVisualizer = Roact.Component:extend("PatchVisualizer")
|
|
||||||
|
|
||||||
function PatchVisualizer:init()
|
|
||||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
|
||||||
|
|
||||||
self.updateEvent = Instance.new("BindableEvent")
|
|
||||||
end
|
|
||||||
|
|
||||||
function PatchVisualizer:willUnmount()
|
|
||||||
self.updateEvent:Destroy()
|
|
||||||
end
|
|
||||||
|
|
||||||
function PatchVisualizer:shouldUpdate(nextProps)
|
|
||||||
if self.props.patchTree ~= nextProps.patchTree then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
local currentPatch, nextPatch = self.props.patch, nextProps.patch
|
|
||||||
if currentPatch ~= nil or nextPatch ~= nil then
|
|
||||||
return not PatchSet.isEqual(currentPatch, nextPatch)
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function PatchVisualizer:render()
|
|
||||||
local patchTree = self.props.patchTree
|
|
||||||
if patchTree == nil and self.props.patch ~= nil then
|
|
||||||
patchTree = PatchTree.build(
|
|
||||||
self.props.patch,
|
|
||||||
self.props.instanceMap,
|
|
||||||
self.props.changeListHeaders or { "Property", "Current", "Incoming" }
|
|
||||||
)
|
|
||||||
if self.props.unappliedPatch then
|
|
||||||
patchTree =
|
|
||||||
PatchTree.updateMetadata(patchTree, self.props.patch, self.props.instanceMap, self.props.unappliedPatch)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Recusively draw tree
|
|
||||||
local scrollElements, elementHeights = {}, {}
|
|
||||||
|
|
||||||
if patchTree then
|
|
||||||
local function drawNode(node, depth)
|
|
||||||
local elementHeight, setElementHeight = Roact.createBinding(30)
|
|
||||||
table.insert(elementHeights, elementHeight)
|
|
||||||
table.insert(
|
|
||||||
scrollElements,
|
|
||||||
e(DomLabel, {
|
|
||||||
updateEvent = self.updateEvent,
|
|
||||||
elementHeight = elementHeight,
|
|
||||||
setElementHeight = setElementHeight,
|
|
||||||
patchType = node.patchType,
|
|
||||||
className = node.className,
|
|
||||||
isWarning = node.isWarning,
|
|
||||||
instance = node.instance,
|
|
||||||
name = node.name,
|
|
||||||
hint = node.hint,
|
|
||||||
changeList = node.changeList,
|
|
||||||
depth = depth,
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
showSourceDiff = self.props.showSourceDiff,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
patchTree:forEach(function(node, depth)
|
|
||||||
drawNode(node, depth)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Theme.with(function(theme)
|
|
||||||
return e(BorderedContainer, {
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
size = self.props.size,
|
|
||||||
position = self.props.position,
|
|
||||||
layoutOrder = self.props.layoutOrder,
|
|
||||||
}, {
|
|
||||||
CleanMerge = e("TextLabel", {
|
|
||||||
Visible = #scrollElements == 0,
|
|
||||||
Text = "No changes to sync, project is up to date.",
|
|
||||||
Font = Enum.Font.GothamMedium,
|
|
||||||
TextSize = 15,
|
|
||||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
|
||||||
TextWrapped = true,
|
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
}),
|
|
||||||
|
|
||||||
VirtualScroller = e(VirtualScroller, {
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
count = #scrollElements,
|
|
||||||
updateEvent = self.updateEvent.Event,
|
|
||||||
render = function(i)
|
|
||||||
return scrollElements[i]
|
|
||||||
end,
|
|
||||||
getHeightBinding = function(i)
|
|
||||||
return elementHeights[i]
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return PatchVisualizer
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Rojo.Roact)
|
||||||
|
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
@@ -23,28 +22,21 @@ local function ScrollingFrame(props)
|
|||||||
BottomImage = Assets.Images.ScrollBar.Bottom,
|
BottomImage = Assets.Images.ScrollBar.Bottom,
|
||||||
|
|
||||||
ElasticBehavior = Enum.ElasticBehavior.Always,
|
ElasticBehavior = Enum.ElasticBehavior.Always,
|
||||||
ScrollingDirection = props.scrollingDirection or Enum.ScrollingDirection.Y,
|
ScrollingDirection = Enum.ScrollingDirection.Y,
|
||||||
|
|
||||||
Size = props.size,
|
Size = props.size,
|
||||||
Position = props.position,
|
Position = props.position,
|
||||||
AnchorPoint = props.anchorPoint,
|
AnchorPoint = props.anchorPoint,
|
||||||
CanvasSize = props.contentSize:map(function(value)
|
CanvasSize = props.contentSize:map(function(value)
|
||||||
return UDim2.new(
|
return UDim2.new(0, 0, 0, value.Y)
|
||||||
0,
|
|
||||||
if (props.scrollingDirection and props.scrollingDirection ~= Enum.ScrollingDirection.Y)
|
|
||||||
then value.X
|
|
||||||
else 0,
|
|
||||||
0,
|
|
||||||
value.Y
|
|
||||||
)
|
|
||||||
end),
|
end),
|
||||||
|
|
||||||
BorderSizePixel = 0,
|
BorderSizePixel = 0,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
|
|
||||||
[Roact.Change.AbsoluteSize] = props[Roact.Change.AbsoluteSize],
|
[Roact.Change.AbsoluteSize] = props[Roact.Change.AbsoluteSize]
|
||||||
}, props[Roact.Children])
|
}, props[Roact.Children])
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return ScrollingFrame
|
return ScrollingFrame
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Rojo.Roact)
|
||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
@@ -27,4 +26,4 @@ local function SlicedImage(props)
|
|||||||
}, props[Roact.Children])
|
}, props[Roact.Children])
|
||||||
end
|
end
|
||||||
|
|
||||||
return SlicedImage
|
return SlicedImage
|
||||||
@@ -2,9 +2,8 @@ local RunService = game:GetService("RunService")
|
|||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Rojo.Roact)
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
@@ -64,4 +63,4 @@ function Spinner:willUnmount()
|
|||||||
self.stepper:Disconnect()
|
self.stepper:Disconnect()
|
||||||
end
|
end
|
||||||
|
|
||||||
return Spinner
|
return Spinner
|
||||||
@@ -1,441 +0,0 @@
|
|||||||
--[[
|
|
||||||
Based on DiffMatchPatch by Neil Fraser.
|
|
||||||
https://github.com/google/diff-match-patch
|
|
||||||
]]
|
|
||||||
|
|
||||||
export type DiffAction = number
|
|
||||||
export type Diff = { actionType: DiffAction, value: string }
|
|
||||||
export type Diffs = { Diff }
|
|
||||||
|
|
||||||
local StringDiff = {
|
|
||||||
ActionTypes = table.freeze({
|
|
||||||
Equal = 0,
|
|
||||||
Delete = 1,
|
|
||||||
Insert = 2,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
function StringDiff.findDiffs(text1: string, text2: string): Diffs
|
|
||||||
-- Validate inputs
|
|
||||||
if type(text1) ~= "string" or type(text2) ~= "string" then
|
|
||||||
error(
|
|
||||||
string.format(
|
|
||||||
"Invalid inputs to StringDiff.findDiffs, expected strings and got (%s, %s)",
|
|
||||||
type(text1),
|
|
||||||
type(text2)
|
|
||||||
),
|
|
||||||
2
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Shortcut if the texts are identical
|
|
||||||
if text1 == text2 then
|
|
||||||
return { { actionType = StringDiff.ActionTypes.Equal, value = text1 } }
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Trim off any shared prefix and suffix
|
|
||||||
-- These are easy to detect and can be dealt with quickly without needing a complex diff
|
|
||||||
-- and later we simply add them as Equal to the start and end of the diff
|
|
||||||
local sharedPrefix, sharedSuffix
|
|
||||||
local prefixLength = StringDiff._sharedPrefix(text1, text2)
|
|
||||||
if prefixLength > 0 then
|
|
||||||
-- Store the prefix
|
|
||||||
sharedPrefix = string.sub(text1, 1, prefixLength)
|
|
||||||
-- Now trim it off
|
|
||||||
text1 = string.sub(text1, prefixLength + 1)
|
|
||||||
text2 = string.sub(text2, prefixLength + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
local suffixLength = StringDiff._sharedSuffix(text1, text2)
|
|
||||||
if suffixLength > 0 then
|
|
||||||
-- Store the suffix
|
|
||||||
sharedSuffix = string.sub(text1, -suffixLength)
|
|
||||||
-- Now trim it off
|
|
||||||
text1 = string.sub(text1, 1, -suffixLength - 1)
|
|
||||||
text2 = string.sub(text2, 1, -suffixLength - 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Compute the diff on the middle block where the changes lie
|
|
||||||
local diffs = StringDiff._computeDiff(text1, text2)
|
|
||||||
|
|
||||||
-- Restore the prefix and suffix
|
|
||||||
if sharedPrefix then
|
|
||||||
table.insert(diffs, 1, { actionType = StringDiff.ActionTypes.Equal, value = sharedPrefix })
|
|
||||||
end
|
|
||||||
if sharedSuffix then
|
|
||||||
table.insert(diffs, { actionType = StringDiff.ActionTypes.Equal, value = sharedSuffix })
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Cleanup the diff
|
|
||||||
diffs = StringDiff._reorderAndMerge(diffs)
|
|
||||||
|
|
||||||
return diffs
|
|
||||||
end
|
|
||||||
|
|
||||||
function StringDiff._sharedPrefix(text1: string, text2: string): number
|
|
||||||
-- Uses a binary search to find the largest common prefix between the two strings
|
|
||||||
-- Performance analysis: http://neil.fraser.name/news/2007/10/09/
|
|
||||||
|
|
||||||
-- Shortcut common cases
|
|
||||||
if (#text1 == 0) or (#text2 == 0) or (string.byte(text1, 1) ~= string.byte(text2, 1)) then
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
local pointerMin = 1
|
|
||||||
local pointerMax = math.min(#text1, #text2)
|
|
||||||
local pointerMid = pointerMax
|
|
||||||
local pointerStart = 1
|
|
||||||
while pointerMin < pointerMid do
|
|
||||||
if string.sub(text1, pointerStart, pointerMid) == string.sub(text2, pointerStart, pointerMid) then
|
|
||||||
pointerMin = pointerMid
|
|
||||||
pointerStart = pointerMin
|
|
||||||
else
|
|
||||||
pointerMax = pointerMid
|
|
||||||
end
|
|
||||||
pointerMid = math.floor(pointerMin + (pointerMax - pointerMin) / 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
return pointerMid
|
|
||||||
end
|
|
||||||
|
|
||||||
function StringDiff._sharedSuffix(text1: string, text2: string): number
|
|
||||||
-- Uses a binary search to find the largest common suffix between the two strings
|
|
||||||
-- Performance analysis: http://neil.fraser.name/news/2007/10/09/
|
|
||||||
|
|
||||||
-- Shortcut common cases
|
|
||||||
if (#text1 == 0) or (#text2 == 0) or (string.byte(text1, -1) ~= string.byte(text2, -1)) then
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
local pointerMin = 1
|
|
||||||
local pointerMax = math.min(#text1, #text2)
|
|
||||||
local pointerMid = pointerMax
|
|
||||||
local pointerEnd = 1
|
|
||||||
while pointerMin < pointerMid do
|
|
||||||
if string.sub(text1, -pointerMid, -pointerEnd) == string.sub(text2, -pointerMid, -pointerEnd) then
|
|
||||||
pointerMin = pointerMid
|
|
||||||
pointerEnd = pointerMin
|
|
||||||
else
|
|
||||||
pointerMax = pointerMid
|
|
||||||
end
|
|
||||||
pointerMid = math.floor(pointerMin + (pointerMax - pointerMin) / 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
return pointerMid
|
|
||||||
end
|
|
||||||
|
|
||||||
function StringDiff._computeDiff(text1: string, text2: string): Diffs
|
|
||||||
-- Assumes that the prefix and suffix have already been trimmed off
|
|
||||||
-- and shortcut returns have been made so these texts must be different
|
|
||||||
|
|
||||||
local text1Length, text2Length = #text1, #text2
|
|
||||||
|
|
||||||
if text1Length == 0 then
|
|
||||||
-- It's simply inserting all of text2 into text1
|
|
||||||
return { { actionType = StringDiff.ActionTypes.Insert, value = text2 } }
|
|
||||||
end
|
|
||||||
|
|
||||||
if text2Length == 0 then
|
|
||||||
-- It's simply deleting all of text1
|
|
||||||
return { { actionType = StringDiff.ActionTypes.Delete, value = text1 } }
|
|
||||||
end
|
|
||||||
|
|
||||||
local longText = if text1Length > text2Length then text1 else text2
|
|
||||||
local shortText = if text1Length > text2Length then text2 else text1
|
|
||||||
local shortTextLength = #shortText
|
|
||||||
|
|
||||||
-- Shortcut if the shorter string exists entirely inside the longer one
|
|
||||||
local indexOf = if shortTextLength == 0 then nil else string.find(longText, shortText, 1, true)
|
|
||||||
if indexOf ~= nil then
|
|
||||||
local diffs = {
|
|
||||||
{ actionType = StringDiff.ActionTypes.Insert, value = string.sub(longText, 1, indexOf - 1) },
|
|
||||||
{ actionType = StringDiff.ActionTypes.Equal, value = shortText },
|
|
||||||
{ actionType = StringDiff.ActionTypes.Insert, value = string.sub(longText, indexOf + shortTextLength) },
|
|
||||||
}
|
|
||||||
-- Swap insertions for deletions if diff is reversed
|
|
||||||
if text1Length > text2Length then
|
|
||||||
diffs[1].actionType, diffs[3].actionType = StringDiff.ActionTypes.Delete, StringDiff.ActionTypes.Delete
|
|
||||||
end
|
|
||||||
return diffs
|
|
||||||
end
|
|
||||||
|
|
||||||
if shortTextLength == 1 then
|
|
||||||
-- Single character string
|
|
||||||
-- After the previous shortcut, the character can't be an equality
|
|
||||||
return {
|
|
||||||
{ actionType = StringDiff.ActionTypes.Delete, value = text1 },
|
|
||||||
{ actionType = StringDiff.ActionTypes.Insert, value = text2 },
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return StringDiff._bisect(text1, text2)
|
|
||||||
end
|
|
||||||
|
|
||||||
function StringDiff._bisect(text1: string, text2: string): Diffs
|
|
||||||
-- Find the 'middle snake' of a diff, split the problem in two
|
|
||||||
-- and return the recursively constructed diff
|
|
||||||
-- See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations
|
|
||||||
|
|
||||||
-- Cache the text lengths to prevent multiple calls
|
|
||||||
local text1Length = #text1
|
|
||||||
local text2Length = #text2
|
|
||||||
|
|
||||||
local _sub, _element
|
|
||||||
local maxD = math.ceil((text1Length + text2Length) / 2)
|
|
||||||
local vOffset = maxD
|
|
||||||
local vLength = 2 * maxD
|
|
||||||
local v1 = table.create(vLength)
|
|
||||||
local v2 = table.create(vLength)
|
|
||||||
|
|
||||||
-- Setting all elements to -1 is faster in Lua than mixing integers and nil
|
|
||||||
for x = 0, vLength - 1 do
|
|
||||||
v1[x] = -1
|
|
||||||
v2[x] = -1
|
|
||||||
end
|
|
||||||
v1[vOffset + 1] = 0
|
|
||||||
v2[vOffset + 1] = 0
|
|
||||||
local delta = text1Length - text2Length
|
|
||||||
|
|
||||||
-- If the total number of characters is odd, then
|
|
||||||
-- the front path will collide with the reverse path
|
|
||||||
local front = (delta % 2 ~= 0)
|
|
||||||
|
|
||||||
-- Offsets for start and end of k loop
|
|
||||||
-- Prevents mapping of space beyond the grid
|
|
||||||
local k1Start = 0
|
|
||||||
local k1End = 0
|
|
||||||
local k2Start = 0
|
|
||||||
local k2End = 0
|
|
||||||
for d = 0, maxD - 1 do
|
|
||||||
-- Walk the front path one step
|
|
||||||
for k1 = -d + k1Start, d - k1End, 2 do
|
|
||||||
local k1_offset = vOffset + k1
|
|
||||||
local x1
|
|
||||||
if (k1 == -d) or ((k1 ~= d) and (v1[k1_offset - 1] < v1[k1_offset + 1])) then
|
|
||||||
x1 = v1[k1_offset + 1]
|
|
||||||
else
|
|
||||||
x1 = v1[k1_offset - 1] + 1
|
|
||||||
end
|
|
||||||
local y1 = x1 - k1
|
|
||||||
while
|
|
||||||
(x1 <= text1Length)
|
|
||||||
and (y1 <= text2Length)
|
|
||||||
and (string.sub(text1, x1, x1) == string.sub(text2, y1, y1))
|
|
||||||
do
|
|
||||||
x1 = x1 + 1
|
|
||||||
y1 = y1 + 1
|
|
||||||
end
|
|
||||||
v1[k1_offset] = x1
|
|
||||||
if x1 > text1Length + 1 then
|
|
||||||
-- Ran off the right of the graph
|
|
||||||
k1End = k1End + 2
|
|
||||||
elseif y1 > text2Length + 1 then
|
|
||||||
-- Ran off the bottom of the graph
|
|
||||||
k1Start = k1Start + 2
|
|
||||||
elseif front then
|
|
||||||
local k2_offset = vOffset + delta - k1
|
|
||||||
if k2_offset >= 0 and k2_offset < vLength and v2[k2_offset] ~= -1 then
|
|
||||||
-- Mirror x2 onto top-left coordinate system
|
|
||||||
local x2 = text1Length - v2[k2_offset] + 1
|
|
||||||
if x1 > x2 then
|
|
||||||
-- Overlap detected
|
|
||||||
return StringDiff._bisectSplit(text1, text2, x1, y1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Walk the reverse path one step
|
|
||||||
for k2 = -d + k2Start, d - k2End, 2 do
|
|
||||||
local k2_offset = vOffset + k2
|
|
||||||
local x2
|
|
||||||
if (k2 == -d) or ((k2 ~= d) and (v2[k2_offset - 1] < v2[k2_offset + 1])) then
|
|
||||||
x2 = v2[k2_offset + 1]
|
|
||||||
else
|
|
||||||
x2 = v2[k2_offset - 1] + 1
|
|
||||||
end
|
|
||||||
local y2 = x2 - k2
|
|
||||||
while
|
|
||||||
(x2 <= text1Length)
|
|
||||||
and (y2 <= text2Length)
|
|
||||||
and (string.sub(text1, -x2, -x2) == string.sub(text2, -y2, -y2))
|
|
||||||
do
|
|
||||||
x2 = x2 + 1
|
|
||||||
y2 = y2 + 1
|
|
||||||
end
|
|
||||||
v2[k2_offset] = x2
|
|
||||||
if x2 > text1Length + 1 then
|
|
||||||
-- Ran off the left of the graph
|
|
||||||
k2End = k2End + 2
|
|
||||||
elseif y2 > text2Length + 1 then
|
|
||||||
-- Ran off the top of the graph
|
|
||||||
k2Start = k2Start + 2
|
|
||||||
elseif not front then
|
|
||||||
local k1_offset = vOffset + delta - k2
|
|
||||||
if k1_offset >= 0 and k1_offset < vLength and v1[k1_offset] ~= -1 then
|
|
||||||
local x1 = v1[k1_offset]
|
|
||||||
local y1 = vOffset + x1 - k1_offset
|
|
||||||
-- Mirror x2 onto top-left coordinate system
|
|
||||||
x2 = text1Length - x2 + 1
|
|
||||||
if x1 > x2 then
|
|
||||||
-- Overlap detected
|
|
||||||
return StringDiff._bisectSplit(text1, text2, x1, y1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Number of diffs equals number of characters, no commonality at all
|
|
||||||
return {
|
|
||||||
{ actionType = StringDiff.ActionTypes.Delete, value = text1 },
|
|
||||||
{ actionType = StringDiff.ActionTypes.Insert, value = text2 },
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
function StringDiff._bisectSplit(text1: string, text2: string, x: number, y: number): Diffs
|
|
||||||
-- Given the location of the 'middle snake',
|
|
||||||
-- split the diff in two parts and recurse
|
|
||||||
|
|
||||||
local text1a = string.sub(text1, 1, x - 1)
|
|
||||||
local text2a = string.sub(text2, 1, y - 1)
|
|
||||||
local text1b = string.sub(text1, x)
|
|
||||||
local text2b = string.sub(text2, y)
|
|
||||||
|
|
||||||
-- Compute both diffs serially
|
|
||||||
local diffs = StringDiff.findDiffs(text1a, text2a)
|
|
||||||
local diffsB = StringDiff.findDiffs(text1b, text2b)
|
|
||||||
|
|
||||||
-- Merge diffs
|
|
||||||
table.move(diffsB, 1, #diffsB, #diffs + 1, diffs)
|
|
||||||
return diffs
|
|
||||||
end
|
|
||||||
|
|
||||||
function StringDiff._reorderAndMerge(diffs: Diffs): Diffs
|
|
||||||
-- Reorder and merge like edit sections and merge equalities
|
|
||||||
-- Any edit section can move as long as it doesn't cross an equality
|
|
||||||
|
|
||||||
-- Add a dummy entry at the end
|
|
||||||
table.insert(diffs, { actionType = StringDiff.ActionTypes.Equal, value = "" })
|
|
||||||
|
|
||||||
local pointer = 1
|
|
||||||
local countDelete, countInsert = 0, 0
|
|
||||||
local textDelete, textInsert = "", ""
|
|
||||||
local commonLength
|
|
||||||
while diffs[pointer] do
|
|
||||||
local actionType = diffs[pointer].actionType
|
|
||||||
if actionType == StringDiff.ActionTypes.Insert then
|
|
||||||
countInsert = countInsert + 1
|
|
||||||
textInsert = textInsert .. diffs[pointer].value
|
|
||||||
pointer = pointer + 1
|
|
||||||
elseif actionType == StringDiff.ActionTypes.Delete then
|
|
||||||
countDelete = countDelete + 1
|
|
||||||
textDelete = textDelete .. diffs[pointer].value
|
|
||||||
pointer = pointer + 1
|
|
||||||
elseif actionType == StringDiff.ActionTypes.Equal then
|
|
||||||
-- Upon reaching an equality, check for prior redundancies
|
|
||||||
if countDelete + countInsert > 1 then
|
|
||||||
if (countDelete > 0) and (countInsert > 0) then
|
|
||||||
-- Factor out any common prefixies
|
|
||||||
commonLength = StringDiff._sharedPrefix(textInsert, textDelete)
|
|
||||||
if commonLength > 0 then
|
|
||||||
local back_pointer = pointer - countDelete - countInsert
|
|
||||||
if
|
|
||||||
(back_pointer > 1) and (diffs[back_pointer - 1].actionType == StringDiff.ActionTypes.Equal)
|
|
||||||
then
|
|
||||||
diffs[back_pointer - 1].value = diffs[back_pointer - 1].value
|
|
||||||
.. string.sub(textInsert, 1, commonLength)
|
|
||||||
else
|
|
||||||
table.insert(diffs, 1, {
|
|
||||||
actionType = StringDiff.ActionTypes.Equal,
|
|
||||||
value = string.sub(textInsert, 1, commonLength),
|
|
||||||
})
|
|
||||||
pointer = pointer + 1
|
|
||||||
end
|
|
||||||
textInsert = string.sub(textInsert, commonLength + 1)
|
|
||||||
textDelete = string.sub(textDelete, commonLength + 1)
|
|
||||||
end
|
|
||||||
-- Factor out any common suffixies
|
|
||||||
commonLength = StringDiff._sharedSuffix(textInsert, textDelete)
|
|
||||||
if commonLength ~= 0 then
|
|
||||||
diffs[pointer].value = string.sub(textInsert, -commonLength) .. diffs[pointer].value
|
|
||||||
textInsert = string.sub(textInsert, 1, -commonLength - 1)
|
|
||||||
textDelete = string.sub(textDelete, 1, -commonLength - 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Delete the offending records and add the merged ones
|
|
||||||
pointer = pointer - countDelete - countInsert
|
|
||||||
for _ = 1, countDelete + countInsert do
|
|
||||||
table.remove(diffs, pointer)
|
|
||||||
end
|
|
||||||
if #textDelete > 0 then
|
|
||||||
table.insert(diffs, pointer, { actionType = StringDiff.ActionTypes.Delete, value = textDelete })
|
|
||||||
pointer = pointer + 1
|
|
||||||
end
|
|
||||||
if #textInsert > 0 then
|
|
||||||
table.insert(diffs, pointer, { actionType = StringDiff.ActionTypes.Insert, value = textInsert })
|
|
||||||
pointer = pointer + 1
|
|
||||||
end
|
|
||||||
pointer = pointer + 1
|
|
||||||
elseif (pointer > 1) and (diffs[pointer - 1].actionType == StringDiff.ActionTypes.Equal) then
|
|
||||||
-- Merge this equality with the previous one
|
|
||||||
diffs[pointer - 1].value = diffs[pointer - 1].value .. diffs[pointer].value
|
|
||||||
table.remove(diffs, pointer)
|
|
||||||
else
|
|
||||||
pointer = pointer + 1
|
|
||||||
end
|
|
||||||
countInsert, countDelete = 0, 0
|
|
||||||
textDelete, textInsert = "", ""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if diffs[#diffs].value == "" then
|
|
||||||
-- Remove the dummy entry at the end
|
|
||||||
diffs[#diffs] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Second pass: look for single edits surrounded on both sides by equalities
|
|
||||||
-- which can be shifted sideways to eliminate an equality
|
|
||||||
-- e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
|
|
||||||
local changes = false
|
|
||||||
pointer = 2
|
|
||||||
-- Intentionally ignore the first and last element (don't need checking)
|
|
||||||
while pointer < #diffs do
|
|
||||||
local prevDiff, nextDiff = diffs[pointer - 1], diffs[pointer + 1]
|
|
||||||
if
|
|
||||||
(prevDiff.actionType == StringDiff.ActionTypes.Equal)
|
|
||||||
and (nextDiff.actionType == StringDiff.ActionTypes.Equal)
|
|
||||||
then
|
|
||||||
-- This is a single edit surrounded by equalities
|
|
||||||
local currentDiff = diffs[pointer]
|
|
||||||
local currentText = currentDiff.value
|
|
||||||
local prevText = prevDiff.value
|
|
||||||
local nextText = nextDiff.value
|
|
||||||
if #prevText == 0 then
|
|
||||||
table.remove(diffs, pointer - 1)
|
|
||||||
changes = true
|
|
||||||
elseif string.sub(currentText, -#prevText) == prevText then
|
|
||||||
-- Shift the edit over the previous equality
|
|
||||||
currentDiff.value = prevText .. string.sub(currentText, 1, -#prevText - 1)
|
|
||||||
nextDiff.value = prevText .. nextDiff.value
|
|
||||||
table.remove(diffs, pointer - 1)
|
|
||||||
changes = true
|
|
||||||
elseif string.sub(currentText, 1, #nextText) == nextText then
|
|
||||||
-- Shift the edit over the next equality
|
|
||||||
prevDiff.value = prevText .. nextText
|
|
||||||
currentDiff.value = string.sub(currentText, #nextText + 1) .. nextText
|
|
||||||
table.remove(diffs, pointer + 1)
|
|
||||||
changes = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
pointer = pointer + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If shifts were made, the diffs need reordering and another shift sweep
|
|
||||||
if changes then
|
|
||||||
return StringDiff._reorderAndMerge(diffs)
|
|
||||||
end
|
|
||||||
|
|
||||||
return diffs
|
|
||||||
end
|
|
||||||
|
|
||||||
return StringDiff
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
local TextService = game:GetService("TextService")
|
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
|
||||||
local Plugin = Rojo.Plugin
|
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
|
||||||
local Log = require(Packages.Log)
|
|
||||||
local Highlighter = require(Packages.Highlighter)
|
|
||||||
local StringDiff = require(script:FindFirstChild("StringDiff"))
|
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
|
||||||
|
|
||||||
local CodeLabel = require(Plugin.App.Components.CodeLabel)
|
|
||||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
|
||||||
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
|
||||||
|
|
||||||
local e = Roact.createElement
|
|
||||||
|
|
||||||
local StringDiffVisualizer = Roact.Component:extend("StringDiffVisualizer")
|
|
||||||
|
|
||||||
function StringDiffVisualizer:init()
|
|
||||||
self.scriptBackground, self.setScriptBackground = Roact.createBinding(Color3.fromRGB(0, 0, 0))
|
|
||||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
|
||||||
|
|
||||||
-- Ensure that the script background is up to date with the current theme
|
|
||||||
self.themeChangedConnection = settings().Studio.ThemeChanged:Connect(function()
|
|
||||||
task.defer(function()
|
|
||||||
-- Defer to allow Highlighter to process the theme change first
|
|
||||||
self:updateScriptBackground()
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
self:calculateContentSize()
|
|
||||||
self:updateScriptBackground()
|
|
||||||
|
|
||||||
self:setState({
|
|
||||||
add = {},
|
|
||||||
remove = {},
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function StringDiffVisualizer:willUnmount()
|
|
||||||
self.themeChangedConnection:Disconnect()
|
|
||||||
end
|
|
||||||
|
|
||||||
function StringDiffVisualizer:updateScriptBackground()
|
|
||||||
local backgroundColor = Highlighter.getTokenColor("background")
|
|
||||||
if backgroundColor ~= self.scriptBackground:getValue() then
|
|
||||||
self.setScriptBackground(backgroundColor)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function StringDiffVisualizer:didUpdate(previousProps)
|
|
||||||
if previousProps.oldText ~= self.props.oldText or previousProps.newText ~= self.props.newText then
|
|
||||||
self:calculateContentSize()
|
|
||||||
local add, remove = self:calculateDiffLines()
|
|
||||||
self:setState({
|
|
||||||
add = add,
|
|
||||||
remove = remove,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function StringDiffVisualizer:calculateContentSize()
|
|
||||||
local oldText, newText = self.props.oldText, self.props.newText
|
|
||||||
|
|
||||||
local oldTextBounds = TextService:GetTextSize(oldText, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999))
|
|
||||||
local newTextBounds = TextService:GetTextSize(newText, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999))
|
|
||||||
|
|
||||||
self.setContentSize(
|
|
||||||
Vector2.new(math.max(oldTextBounds.X, newTextBounds.X), math.max(oldTextBounds.Y, newTextBounds.Y))
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
function StringDiffVisualizer:calculateDiffLines()
|
|
||||||
local oldText, newText = self.props.oldText, self.props.newText
|
|
||||||
|
|
||||||
-- Diff the two texts
|
|
||||||
local startClock = os.clock()
|
|
||||||
local diffs = StringDiff.findDiffs(oldText, newText)
|
|
||||||
local stopClock = os.clock()
|
|
||||||
|
|
||||||
Log.trace(
|
|
||||||
"Diffing {} byte and {} byte strings took {} microseconds and found {} diff sections",
|
|
||||||
#oldText,
|
|
||||||
#newText,
|
|
||||||
math.round((stopClock - startClock) * 1000 * 1000),
|
|
||||||
#diffs
|
|
||||||
)
|
|
||||||
|
|
||||||
-- Determine which lines to highlight
|
|
||||||
local add, remove = {}, {}
|
|
||||||
|
|
||||||
local oldLineNum, newLineNum = 1, 1
|
|
||||||
for _, diff in diffs do
|
|
||||||
local actionType, text = diff.actionType, diff.value
|
|
||||||
local lines = select(2, string.gsub(text, "\n", "\n"))
|
|
||||||
|
|
||||||
if actionType == StringDiff.ActionTypes.Equal then
|
|
||||||
oldLineNum += lines
|
|
||||||
newLineNum += lines
|
|
||||||
elseif actionType == StringDiff.ActionTypes.Insert then
|
|
||||||
if lines > 0 then
|
|
||||||
local textLines = string.split(text, "\n")
|
|
||||||
for i, textLine in textLines do
|
|
||||||
if string.match(textLine, "%S") then
|
|
||||||
add[newLineNum + i - 1] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if string.match(text, "%S") then
|
|
||||||
add[newLineNum] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
newLineNum += lines
|
|
||||||
elseif actionType == StringDiff.ActionTypes.Delete then
|
|
||||||
if lines > 0 then
|
|
||||||
local textLines = string.split(text, "\n")
|
|
||||||
for i, textLine in textLines do
|
|
||||||
if string.match(textLine, "%S") then
|
|
||||||
remove[oldLineNum + i - 1] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if string.match(text, "%S") then
|
|
||||||
remove[oldLineNum] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
oldLineNum += lines
|
|
||||||
else
|
|
||||||
Log.warn("Unknown diff action: {} {}", actionType, text)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return add, remove
|
|
||||||
end
|
|
||||||
|
|
||||||
function StringDiffVisualizer:render()
|
|
||||||
local oldText, newText = self.props.oldText, self.props.newText
|
|
||||||
|
|
||||||
return Theme.with(function(theme)
|
|
||||||
return e(BorderedContainer, {
|
|
||||||
size = self.props.size,
|
|
||||||
position = self.props.position,
|
|
||||||
anchorPoint = self.props.anchorPoint,
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
}, {
|
|
||||||
Background = e("Frame", {
|
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
|
||||||
Position = UDim2.new(0, 0, 0, 0),
|
|
||||||
BorderSizePixel = 0,
|
|
||||||
BackgroundColor3 = self.scriptBackground,
|
|
||||||
ZIndex = -10,
|
|
||||||
}, {
|
|
||||||
UICorner = e("UICorner", {
|
|
||||||
CornerRadius = UDim.new(0, 5),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
Separator = e("Frame", {
|
|
||||||
Size = UDim2.new(0, 2, 1, 0),
|
|
||||||
Position = UDim2.new(0.5, 0, 0, 0),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0),
|
|
||||||
BorderSizePixel = 0,
|
|
||||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
|
||||||
BackgroundTransparency = 0.5,
|
|
||||||
}),
|
|
||||||
Old = e(ScrollingFrame, {
|
|
||||||
position = UDim2.new(0, 2, 0, 2),
|
|
||||||
size = UDim2.new(0.5, -7, 1, -4),
|
|
||||||
scrollingDirection = Enum.ScrollingDirection.XY,
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
contentSize = self.contentSize,
|
|
||||||
}, {
|
|
||||||
Source = e(CodeLabel, {
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
|
||||||
position = UDim2.new(0, 0, 0, 0),
|
|
||||||
text = oldText,
|
|
||||||
lineBackground = theme.Diff.Remove,
|
|
||||||
markedLines = self.state.remove,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
New = e(ScrollingFrame, {
|
|
||||||
position = UDim2.new(0.5, 5, 0, 2),
|
|
||||||
size = UDim2.new(0.5, -7, 1, -4),
|
|
||||||
scrollingDirection = Enum.ScrollingDirection.XY,
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
contentSize = self.contentSize,
|
|
||||||
}, {
|
|
||||||
Source = e(CodeLabel, {
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
|
||||||
position = UDim2.new(0, 0, 0, 0),
|
|
||||||
text = newText,
|
|
||||||
lineBackground = theme.Diff.Add,
|
|
||||||
markedLines = self.state.add,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
return StringDiffVisualizer
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
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,8 +1,7 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Rojo.Roact)
|
||||||
|
|
||||||
local StudioPluginContext = Roact.createContext(nil)
|
local StudioPluginContext = Roact.createContext(nil)
|
||||||
|
|
||||||
return StudioPluginContext
|
return StudioPluginContext
|
||||||
@@ -1,13 +1,9 @@
|
|||||||
local HttpService = game:GetService("HttpService")
|
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Rojo.Roact)
|
||||||
|
|
||||||
local Dictionary = require(Plugin.Dictionary)
|
local Dictionary = require(Plugin.Dictionary)
|
||||||
local Theme = require(Plugin.App.Theme)
|
|
||||||
|
|
||||||
local StudioPluginContext = require(script.Parent.StudioPluginContext)
|
local StudioPluginContext = require(script.Parent.StudioPluginContext)
|
||||||
|
|
||||||
@@ -32,16 +28,11 @@ function StudioPluginGui:init()
|
|||||||
self.props.initDockState,
|
self.props.initDockState,
|
||||||
self.props.active,
|
self.props.active,
|
||||||
self.props.overridePreviousState,
|
self.props.overridePreviousState,
|
||||||
floatingSize.X,
|
floatingSize.X, floatingSize.Y,
|
||||||
floatingSize.Y,
|
minimumSize.X, minimumSize.Y
|
||||||
minimumSize.X,
|
|
||||||
minimumSize.Y
|
|
||||||
)
|
)
|
||||||
|
|
||||||
local pluginGui = self.props.plugin:CreateDockWidgetPluginGui(
|
local pluginGui = self.props.plugin:CreateDockWidgetPluginGui(self.props.id, dockWidgetPluginGuiInfo)
|
||||||
if self.props.isEphemeral then HttpService:GenerateGUID(false) else self.props.id,
|
|
||||||
dockWidgetPluginGuiInfo
|
|
||||||
)
|
|
||||||
|
|
||||||
pluginGui.Name = self.props.id
|
pluginGui.Name = self.props.id
|
||||||
pluginGui.Title = self.props.title
|
pluginGui.Title = self.props.title
|
||||||
@@ -65,28 +56,13 @@ end
|
|||||||
function StudioPluginGui:render()
|
function StudioPluginGui:render()
|
||||||
return e(Roact.Portal, {
|
return e(Roact.Portal, {
|
||||||
target = self.pluginGui,
|
target = self.pluginGui,
|
||||||
}, {
|
}, self.props[Roact.Children])
|
||||||
Background = Theme.with(function(theme)
|
|
||||||
return e("Frame", {
|
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
|
||||||
BackgroundColor3 = theme.BackgroundColor,
|
|
||||||
ZIndex = 0,
|
|
||||||
BorderSizePixel = 0,
|
|
||||||
}, self.props[Roact.Children])
|
|
||||||
end),
|
|
||||||
})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function StudioPluginGui:didUpdate(lastProps)
|
function StudioPluginGui:didUpdate(lastProps)
|
||||||
if self.props.active ~= lastProps.active then
|
if self.props.active ~= lastProps.active then
|
||||||
-- This is intentionally in didUpdate to make sure the initial active state
|
-- This is intentionally in didUpdate to make sure the initial active state
|
||||||
-- (if the PluginGui is open initially) is preserved.
|
-- (if the PluginGui is open initially) is preserved.
|
||||||
|
|
||||||
-- Studio widgets are very unreliable and sometimes need to be flickered
|
|
||||||
-- in order to force them to render correctly
|
|
||||||
-- This happens within a single frame so it doesn't flicker visibly
|
|
||||||
self.pluginGui.Enabled = self.props.active
|
|
||||||
self.pluginGui.Enabled = not self.props.active
|
|
||||||
self.pluginGui.Enabled = self.props.active
|
self.pluginGui.Enabled = self.props.active
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -98,14 +74,11 @@ end
|
|||||||
local function StudioPluginGuiWrapper(props)
|
local function StudioPluginGuiWrapper(props)
|
||||||
return e(StudioPluginContext.Consumer, {
|
return e(StudioPluginContext.Consumer, {
|
||||||
render = function(plugin)
|
render = function(plugin)
|
||||||
return e(
|
return e(StudioPluginGui, Dictionary.merge(props, {
|
||||||
StudioPluginGui,
|
plugin = plugin,
|
||||||
Dictionary.merge(props, {
|
}))
|
||||||
plugin = plugin,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return StudioPluginGuiWrapper
|
return StudioPluginGuiWrapper
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user