Compare commits
41 Commits
v7.4.4
...
aarch-wind
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6688bcb488 | ||
|
|
73097075d4 | ||
|
|
5e1cab2e75 | ||
|
|
30f439caec | ||
|
|
4b5db4e5a9 | ||
|
|
3fa1d6b09c | ||
|
|
6051a5f1f1 | ||
|
|
5f7dd45361 | ||
|
|
3ca975d81d | ||
|
|
7e2bab921a | ||
|
|
a7b45ee859 | ||
|
|
62f4a1f3c2 | ||
|
|
3d4e387d35 | ||
|
|
2c46640105 | ||
|
|
41443d3989 | ||
|
|
4b3470d30b | ||
|
|
ce71a3df4d | ||
|
|
7232721b87 | ||
|
|
b2f133e6f1 | ||
|
|
87920964d7 | ||
|
|
c7a4f892e3 | ||
|
|
8f9e307930 | ||
|
|
856d43ce69 | ||
|
|
26181a5a1f | ||
|
|
edf87bf9a3 | ||
|
|
5f51538e0b | ||
|
|
48bb760739 | ||
|
|
42121a9fc9 | ||
|
|
02d79a4749 | ||
|
|
ddb26c73bd | ||
|
|
8ff064fe28 | ||
|
|
cf25eb0833 | ||
|
|
5c4260f3ac | ||
|
|
7abf19804c | ||
|
|
df707d5bef | ||
|
|
f3b0b0027e | ||
|
|
106a01223e | ||
|
|
506a60d0be | ||
|
|
4018607b77 | ||
|
|
1cc720ad34 | ||
|
|
73828af715 |
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.lua linguist-language=Luau
|
||||||
22
.github/workflows/ci.yml
vendored
@@ -19,16 +19,12 @@ jobs:
|
|||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
profile: minimal
|
|
||||||
|
|
||||||
- name: Rust cache
|
- name: Rust cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
@@ -49,16 +45,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@1.70.0
|
||||||
with:
|
|
||||||
toolchain: 1.70.0
|
|
||||||
override: true
|
|
||||||
profile: minimal
|
|
||||||
|
|
||||||
- name: Rust cache
|
- name: Rust cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
@@ -76,15 +68,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Install Rust
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
- name: Rust cache
|
- name: Rust cache
|
||||||
|
|||||||
87
.github/workflows/release.yml
vendored
@@ -8,26 +8,20 @@ jobs:
|
|||||||
create-release:
|
create-release:
|
||||||
name: Create Release
|
name: Create Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
id: create_release
|
|
||||||
uses: actions/create-release@v1
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
run: |
|
||||||
tag_name: ${{ github.ref }}
|
gh release create ${{ github.ref_name }} --draft --verify-tag --title ${{ github.ref_name }}
|
||||||
release_name: ${{ github.ref }}
|
|
||||||
draft: true
|
|
||||||
prerelease: false
|
|
||||||
|
|
||||||
build-plugin:
|
build-plugin:
|
||||||
needs: ["create-release"]
|
needs: ["create-release"]
|
||||||
name: Build Roblox Studio Plugin
|
name: Build Roblox Studio Plugin
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
@@ -36,23 +30,19 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
trust-check: false
|
trust-check: false
|
||||||
version: 'v0.3.0'
|
version: 'v0.2.6'
|
||||||
|
|
||||||
- name: Build Plugin
|
- name: Build Plugin
|
||||||
run: rojo build plugin --output Rojo.rbxm
|
run: rojo build plugin --output Rojo.rbxm
|
||||||
|
|
||||||
- name: Upload Plugin to Release
|
- name: Upload Plugin to Release
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
run: |
|
||||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
gh release upload ${{ github.ref_name }} Rojo.rbxm
|
||||||
asset_path: Rojo.rbxm
|
|
||||||
asset_name: Rojo.rbxm
|
|
||||||
asset_content_type: application/octet-stream
|
|
||||||
|
|
||||||
- name: Upload Plugin to Artifacts
|
- name: Upload Plugin to Artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: Rojo.rbxm
|
name: Rojo.rbxm
|
||||||
path: Rojo.rbxm
|
path: Rojo.rbxm
|
||||||
@@ -69,11 +59,21 @@ jobs:
|
|||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu
|
||||||
label: linux-x86_64
|
label: linux-x86_64
|
||||||
|
|
||||||
|
- host: linux
|
||||||
|
os: ubuntu-20.04
|
||||||
|
target: aarch64-unknown-linux-gnu
|
||||||
|
label: linux-aarch64
|
||||||
|
|
||||||
- host: windows
|
- host: windows
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
target: x86_64-pc-windows-msvc
|
target: x86_64-pc-windows-msvc
|
||||||
label: windows-x86_64
|
label: windows-x86_64
|
||||||
|
|
||||||
|
- host: windows
|
||||||
|
os: windows-latest
|
||||||
|
target: aarch64-pc-windows-msvc
|
||||||
|
label: windows-aarch64
|
||||||
|
|
||||||
- host: macos
|
- host: macos
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
@@ -89,24 +89,14 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
BIN: rojo
|
BIN: rojo
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
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
|
- name: Install Rust
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
targets: ${{ matrix.target }}
|
||||||
target: ${{ matrix.target }}
|
|
||||||
override: true
|
|
||||||
profile: minimal
|
|
||||||
|
|
||||||
- name: Setup Aftman
|
- name: Setup Aftman
|
||||||
uses: ok-nick/setup-aftman@v0.1.0
|
uses: ok-nick/setup-aftman@v0.1.0
|
||||||
@@ -122,37 +112,34 @@ jobs:
|
|||||||
# easily.
|
# easily.
|
||||||
CARGO_TARGET_DIR: output
|
CARGO_TARGET_DIR: output
|
||||||
|
|
||||||
# On platforms that use OpenSSL, ensure it is statically linked to
|
- name: Generate Artifact Name
|
||||||
# make binaries more portable.
|
|
||||||
OPENSSL_STATIC: 1
|
|
||||||
|
|
||||||
- name: Create Release Archive
|
|
||||||
shell: bash
|
shell: bash
|
||||||
|
env:
|
||||||
|
TAG_NAME: ${{ github.ref_name }}
|
||||||
|
run: |
|
||||||
|
echo "ARTIFACT_NAME=$BIN-${TAG_NAME#v}-${{ matrix.label }}.zip" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Create Archive and Upload to Release
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
mkdir staging
|
mkdir staging
|
||||||
|
|
||||||
if [ "${{ matrix.host }}" = "windows" ]; then
|
if [ "${{ matrix.host }}" = "windows" ]; then
|
||||||
cp "output/${{ matrix.target }}/release/$BIN.exe" staging/
|
cp "output/${{ matrix.target }}/release/$BIN.exe" staging/
|
||||||
cd staging
|
cd staging
|
||||||
7z a ../release.zip *
|
7z a ../$ARTIFACT_NAME *
|
||||||
else
|
else
|
||||||
cp "output/${{ matrix.target }}/release/$BIN" staging/
|
cp "output/${{ matrix.target }}/release/$BIN" staging/
|
||||||
cd staging
|
cd staging
|
||||||
zip ../release.zip *
|
zip ../$ARTIFACT_NAME *
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload Archive to Release
|
gh release upload ${{ github.ref_name }} ../$ARTIFACT_NAME
|
||||||
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
|
- name: Upload Archive to Artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.BIN }}-${{ env.PROJECT_VERSION }}-${{ matrix.label }}.zip
|
path: ${{ env.ARTIFACT_NAME }}
|
||||||
path: release.zip
|
name: ${{ env.ARTIFACT_NAME }}
|
||||||
|
|||||||
89
CHANGELOG.md
@@ -1,16 +1,96 @@
|
|||||||
# Rojo Changelog
|
# Rojo Changelog
|
||||||
|
|
||||||
## Unreleased Changes
|
## Unreleased Changes
|
||||||
|
* Projects may now manually link `Ref` properties together using `Attributes`. ([#843])
|
||||||
|
This has two parts: using `id` or `$id` in JSON files or a `Rojo_Target` attribute, an Instance
|
||||||
|
is given an ID. Then, that ID may be used elsewhere in the project to point to an Instance
|
||||||
|
using an attribute named `Rojo_Target_PROP_NAME`, where `PROP_NAME` is the name of a property.
|
||||||
|
|
||||||
## [7.4.4] - August 22nd, 2024
|
As an example, here is a `model.json` for an ObjectValue that refers to itself:
|
||||||
* Fixed issue with reading attributes from `Lighting` in new place files
|
|
||||||
* `Instance.Archivable` will now default to `true` when building a project into a binary (`rbxm`/`rbxl`) file rather than `false`.
|
```json
|
||||||
|
{
|
||||||
|
"id": "arbitrary string",
|
||||||
|
"attributes": {
|
||||||
|
"Rojo_Target_Value": "arbitrary string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a very rough implementation and the usage will become more ergonomic
|
||||||
|
over time.
|
||||||
|
|
||||||
|
* Updated Undo/Redo history to be more robust ([#915])
|
||||||
|
* Added popout diff visualizer for table properties like Attributes and Tags ([#834])
|
||||||
|
* Updated Theme to use Studio colors ([#838])
|
||||||
|
* Improved patch visualizer UX ([#883])
|
||||||
|
* Added update notifications for newer compatible versions in the Studio plugin. ([#832])
|
||||||
|
* Added experimental setting for Auto Connect in playtests ([#840])
|
||||||
|
* Improved settings UI ([#886])
|
||||||
|
* `Open Scripts Externally` option can now be changed while syncing ([#911])
|
||||||
|
* Projects may now specify rules for syncing files as if they had a different file extension. ([#813])
|
||||||
|
This is specified via a new field on project files, `syncRules`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"syncRules": [
|
||||||
|
{
|
||||||
|
"pattern": "*.foo",
|
||||||
|
"use": "text",
|
||||||
|
"exclude": "*.exclude.foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "*.bar.baz",
|
||||||
|
"use": "json",
|
||||||
|
"suffix": ".bar.baz",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"name": "SyncRulesAreCool",
|
||||||
|
"tree": {
|
||||||
|
"$path": "src"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `pattern` field is a glob used to match the sync rule to files. If present, the `suffix` field allows you to specify parts of a file's name get cut off by Rojo to name the Instance, including the file extension. If it isn't specified, Rojo will only cut off the first part of the file extension, up to the first dot.
|
||||||
|
|
||||||
|
Additionally, the `exclude` field allows files to be excluded from the sync rule if they match a pattern specified by it. If it's not present, all files that match `pattern` will be modified using the sync rule.
|
||||||
|
|
||||||
|
The `use` field corresponds to one of the potential file type that Rojo will currently include in a project. Files that match the provided pattern will be treated as if they had the file extension for that file type. A full list is below:
|
||||||
|
|
||||||
|
| `use` value | file extension |
|
||||||
|
|:---------------|:----------------|
|
||||||
|
| `serverScript` | `.server.lua` |
|
||||||
|
| `clientScript` | `.client.lua` |
|
||||||
|
| `moduleScript` | `.lua` |
|
||||||
|
| `json` | `.json` |
|
||||||
|
| `toml` | `.toml` |
|
||||||
|
| `csv` | `.csv` |
|
||||||
|
| `text` | `.txt` |
|
||||||
|
| `jsonModel` | `.model.json` |
|
||||||
|
| `rbxm` | `.rbxm` |
|
||||||
|
| `rbxmx` | `.rbxmx` |
|
||||||
|
| `project` | `.project.json` |
|
||||||
|
| `ignore` | None! |
|
||||||
|
|
||||||
|
**All** sync rules are reset between project files, so they must be specified in each one when nesting them. This is to ensure that nothing can break other projects by changing how files are synced!
|
||||||
|
|
||||||
|
[#813]: https://github.com/rojo-rbx/rojo/pull/813
|
||||||
|
[#832]: https://github.com/rojo-rbx/rojo/pull/832
|
||||||
|
[#834]: https://github.com/rojo-rbx/rojo/pull/834
|
||||||
|
[#838]: https://github.com/rojo-rbx/rojo/pull/838
|
||||||
|
[#840]: https://github.com/rojo-rbx/rojo/pull/840
|
||||||
|
[#843]: https://github.com/rojo-rbx/rojo/pull/843
|
||||||
|
[#883]: https://github.com/rojo-rbx/rojo/pull/883
|
||||||
|
[#886]: https://github.com/rojo-rbx/rojo/pull/886
|
||||||
|
[#911]: https://github.com/rojo-rbx/rojo/pull/911
|
||||||
|
[#915]: https://github.com/rojo-rbx/rojo/pull/915
|
||||||
|
|
||||||
## [7.4.3] - August 6th, 2024
|
## [7.4.3] - August 6th, 2024
|
||||||
* Fixed issue with building binary files introduced in 7.4.2
|
* Fixed issue with building binary files introduced in 7.4.2
|
||||||
* Fixed `value of type nil cannot be converted to number` warning spam in output. [#955]
|
* Fixed `value of type nil cannot be converted to number` warning spam in output. [#955]
|
||||||
|
|
||||||
[#955]: https://github.com/rojo-rbx/rojo/pull/893
|
[#955]: https://github.com/rojo-rbx/rojo/pull/955
|
||||||
|
|
||||||
## [7.4.2] - July 23, 2024
|
## [7.4.2] - July 23, 2024
|
||||||
* Added Never option to Confirmation ([#893])
|
* Added Never option to Confirmation ([#893])
|
||||||
@@ -25,6 +105,7 @@
|
|||||||
|
|
||||||
## [7.4.1] - February 20, 2024
|
## [7.4.1] - February 20, 2024
|
||||||
* Made the `name` field optional on project files ([#870])
|
* Made the `name` field optional on project files ([#870])
|
||||||
|
|
||||||
Files named `default.project.json` inherit the name of the folder they're in and all other projects
|
Files named `default.project.json` inherit the name of the folder they're in and all other projects
|
||||||
are named as expect (e.g. `foo.project.json` becomes an Instance named `foo`)
|
are named as expect (e.g. `foo.project.json` becomes an Instance named `foo`)
|
||||||
|
|
||||||
|
|||||||
991
Cargo.lock
generated
77
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rojo"
|
name = "rojo"
|
||||||
version = "7.4.4"
|
version = "7.4.0"
|
||||||
rust-version = "1.70.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"
|
||||||
@@ -26,7 +26,9 @@ 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"]
|
# Run Rojo with this feature to open a Tracy session.
|
||||||
|
# Currently uses protocol v63, last supported in Tracy 0.9.1.
|
||||||
|
profile-with-tracy = ["profiling/profile-with-tracy"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*"]
|
members = ["crates/*"]
|
||||||
@@ -55,40 +57,39 @@ rbx_reflection = "4.7.0"
|
|||||||
rbx_reflection_database = "0.2.12"
|
rbx_reflection_database = "0.2.12"
|
||||||
rbx_xml = "0.13.5"
|
rbx_xml = "0.13.5"
|
||||||
|
|
||||||
anyhow = "1.0.44"
|
anyhow = "1.0.80"
|
||||||
backtrace = "0.3.61"
|
backtrace = "0.3.69"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
crossbeam-channel = "0.5.1"
|
crossbeam-channel = "0.5.12"
|
||||||
csv = "1.1.6"
|
csv = "1.3.0"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.9.3"
|
||||||
fs-err = "2.6.0"
|
fs-err = "2.11.0"
|
||||||
futures = "0.3.17"
|
futures = "0.3.30"
|
||||||
globset = "0.4.8"
|
globset = "0.4.14"
|
||||||
humantime = "2.1.0"
|
humantime = "2.1.0"
|
||||||
hyper = { version = "0.14.13", features = ["server", "tcp", "http1"] }
|
hyper = { version = "0.14.28", features = ["server", "tcp", "http1"] }
|
||||||
jod-thread = "0.1.2"
|
jod-thread = "0.1.2"
|
||||||
log = "0.4.14"
|
log = "0.4.21"
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
num_cpus = "1.15.0"
|
num_cpus = "1.16.0"
|
||||||
opener = "0.5.0"
|
opener = "0.5.2"
|
||||||
rayon = "1.7.0"
|
rayon = "1.9.0"
|
||||||
reqwest = { version = "0.11.10", features = [
|
reqwest = { version = "0.11.24", default-features = false, features = [
|
||||||
"blocking",
|
"blocking",
|
||||||
"json",
|
"json",
|
||||||
"native-tls-vendored",
|
"rustls-tls",
|
||||||
] }
|
] }
|
||||||
ritz = "0.1.0"
|
ritz = "0.1.0"
|
||||||
roblox_install = "1.0.0"
|
roblox_install = "1.0.0"
|
||||||
serde = { version = "1.0.130", features = ["derive", "rc"] }
|
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||||
serde_json = "1.0.68"
|
serde_json = "1.0.114"
|
||||||
toml = "0.5.9"
|
toml = "0.5.11"
|
||||||
termcolor = "1.1.2"
|
termcolor = "1.4.1"
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.57"
|
||||||
tokio = { version = "1.12.0", features = ["rt", "rt-multi-thread"] }
|
tokio = { version = "1.36.0", features = ["rt", "rt-multi-thread"] }
|
||||||
uuid = { version = "1.0.0", features = ["v4", "serde"] }
|
uuid = { version = "1.7.0", features = ["v4", "serde"] }
|
||||||
clap = { version = "3.1.18", features = ["derive"] }
|
clap = { version = "3.2.25", features = ["derive"] }
|
||||||
profiling = "1.0.6"
|
profiling = "1.0.15"
|
||||||
tracy-client = { version = "0.13.2", optional = true }
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.10.1"
|
winreg = "0.10.1"
|
||||||
@@ -96,20 +97,20 @@ winreg = "0.10.1"
|
|||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
memofs = { version = "0.3.0", path = "crates/memofs" }
|
memofs = { version = "0.3.0", path = "crates/memofs" }
|
||||||
|
|
||||||
embed-resource = "1.6.4"
|
embed-resource = "1.8.0"
|
||||||
anyhow = "1.0.44"
|
anyhow = "1.0.80"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
fs-err = "2.6.0"
|
fs-err = "2.11.0"
|
||||||
maplit = "1.0.2"
|
maplit = "1.0.2"
|
||||||
semver = "1.0.19"
|
semver = "1.0.22"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rojo-insta-ext = { path = "crates/rojo-insta-ext" }
|
rojo-insta-ext = { path = "crates/rojo-insta-ext" }
|
||||||
|
|
||||||
criterion = "0.3.5"
|
criterion = "0.3.6"
|
||||||
insta = { version = "1.8.0", features = ["redactions", "yaml"] }
|
insta = { version = "1.36.1", features = ["redactions", "yaml"] }
|
||||||
paste = "1.0.5"
|
paste = "1.0.14"
|
||||||
pretty_assertions = "1.2.1"
|
pretty_assertions = "1.4.0"
|
||||||
serde_yaml = "0.8.21"
|
serde_yaml = "0.8.26"
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.10.1"
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.5.0"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<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/brand_images/logo-512.png" alt="Rojo" height="217" /></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div> </div>
|
<div> </div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[tools]
|
[tools]
|
||||||
rojo = "rojo-rbx/rojo@7.4.1"
|
rojo = "rojo-rbx/rojo@7.3.0"
|
||||||
selene = "Kampfkarren/selene@0.26.1"
|
selene = "Kampfkarren/selene@0.26.1"
|
||||||
stylua = "JohnnyMorganz/stylua@0.18.2"
|
stylua = "JohnnyMorganz/stylua@0.18.2"
|
||||||
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"
|
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 975 B After Width: | Height: | Size: 975 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 229 B After Width: | Height: | Size: 229 B |
|
Before Width: | Height: | Size: 584 B After Width: | Height: | Size: 584 B |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
BIN
assets/images/icons/debug.png
Normal file
|
After Width: | Height: | Size: 183 B |
BIN
assets/images/icons/expand.png
Normal file
|
After Width: | Height: | Size: 273 B |
BIN
assets/images/icons/reset.png
Normal file
|
After Width: | Height: | Size: 933 B |
BIN
assets/images/icons/warning.png
Normal file
|
After Width: | Height: | Size: 241 B |
|
Before Width: | Height: | Size: 175 B After Width: | Height: | Size: 175 B |
BIN
assets/images/syncsuccess.png
Normal file
|
After Width: | Height: | Size: 574 B |
BIN
assets/images/syncwarning.png
Normal file
|
After Width: | Height: | Size: 607 B |
@@ -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.5.12"
|
||||||
fs-err = "2.3.0"
|
fs-err = "2.11.0"
|
||||||
notify = "4.0.15"
|
notify = "4.0.17"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ edition = "2018"
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = "1.0.99"
|
serde = "1.0.197"
|
||||||
serde_yaml = "0.8.9"
|
serde_yaml = "0.8.26"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
7.4.4
|
7.4.0
|
||||||
126
plugin/src/App/Components/ClassIcon.lua
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
local StudioService = game:GetService("StudioService")
|
||||||
|
local AssetService = game:GetService("AssetService")
|
||||||
|
|
||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local EditableImage = require(Plugin.App.Components.EditableImage)
|
||||||
|
|
||||||
|
local imageCache = {}
|
||||||
|
local function getImageSizeAndPixels(image)
|
||||||
|
if not imageCache[image] then
|
||||||
|
local editableImage = AssetService:CreateEditableImageAsync(image)
|
||||||
|
imageCache[image] = {
|
||||||
|
Size = editableImage.Size,
|
||||||
|
Pixels = editableImage:ReadPixels(Vector2.zero, editableImage.Size),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
return imageCache[image].Size, table.clone(imageCache[image].Pixels)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getRecoloredClassIcon(className, color)
|
||||||
|
local iconProps = StudioService:GetClassIcon(className)
|
||||||
|
|
||||||
|
if iconProps and color then
|
||||||
|
local success, editableImageSize, editableImagePixels = pcall(function()
|
||||||
|
local size, pixels = getImageSizeAndPixels(iconProps.Image)
|
||||||
|
|
||||||
|
local minVal, maxVal = math.huge, -math.huge
|
||||||
|
for i = 1, #pixels, 4 do
|
||||||
|
if pixels[i + 3] == 0 then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
local pixelVal = math.max(pixels[i], pixels[i + 1], pixels[i + 2])
|
||||||
|
|
||||||
|
minVal = math.min(minVal, pixelVal)
|
||||||
|
maxVal = math.max(maxVal, pixelVal)
|
||||||
|
end
|
||||||
|
|
||||||
|
local hue, sat, val = color:ToHSV()
|
||||||
|
for i = 1, #pixels, 4 do
|
||||||
|
if pixels[i + 3] == 0 then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local pixelVal = math.max(pixels[i], pixels[i + 1], pixels[i + 2])
|
||||||
|
local newVal = val
|
||||||
|
if minVal < maxVal then
|
||||||
|
-- Remap minVal - maxVal to val*0.9 - val
|
||||||
|
newVal = val * (0.9 + 0.1 * (pixelVal - minVal) / (maxVal - minVal))
|
||||||
|
end
|
||||||
|
|
||||||
|
local newPixelColor = Color3.fromHSV(hue, sat, newVal)
|
||||||
|
pixels[i], pixels[i + 1], pixels[i + 2] = newPixelColor.R, newPixelColor.G, newPixelColor.B
|
||||||
|
end
|
||||||
|
return size, pixels
|
||||||
|
end)
|
||||||
|
if success then
|
||||||
|
iconProps.EditableImagePixels = editableImagePixels
|
||||||
|
iconProps.EditableImageSize = editableImageSize
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return iconProps
|
||||||
|
end
|
||||||
|
|
||||||
|
local ClassIcon = Roact.PureComponent:extend("ClassIcon")
|
||||||
|
|
||||||
|
function ClassIcon:init()
|
||||||
|
self.state = {
|
||||||
|
iconProps = nil,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function ClassIcon:updateIcon()
|
||||||
|
local props = self.props
|
||||||
|
local iconProps = getRecoloredClassIcon(props.className, props.color)
|
||||||
|
self:setState({
|
||||||
|
iconProps = iconProps,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function ClassIcon:didMount()
|
||||||
|
self:updateIcon()
|
||||||
|
end
|
||||||
|
|
||||||
|
function ClassIcon:didUpdate(lastProps)
|
||||||
|
if lastProps.className ~= self.props.className or lastProps.color ~= self.props.color then
|
||||||
|
self:updateIcon()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ClassIcon:render()
|
||||||
|
local iconProps = self.state.iconProps
|
||||||
|
if not iconProps then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return e(
|
||||||
|
"ImageLabel",
|
||||||
|
{
|
||||||
|
Size = self.props.size,
|
||||||
|
Position = self.props.position,
|
||||||
|
LayoutOrder = self.props.layoutOrder,
|
||||||
|
AnchorPoint = self.props.anchorPoint,
|
||||||
|
ImageTransparency = self.props.transparency,
|
||||||
|
Image = iconProps.Image,
|
||||||
|
ImageRectOffset = iconProps.ImageRectOffset,
|
||||||
|
ImageRectSize = iconProps.ImageRectSize,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
},
|
||||||
|
if iconProps.EditableImagePixels
|
||||||
|
then e(EditableImage, {
|
||||||
|
size = iconProps.EditableImageSize,
|
||||||
|
pixels = iconProps.EditableImagePixels,
|
||||||
|
})
|
||||||
|
else nil
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return ClassIcon
|
||||||
@@ -109,9 +109,7 @@ function Dropdown:render()
|
|||||||
}, {
|
}, {
|
||||||
DropArrow = e("ImageLabel", {
|
DropArrow = e("ImageLabel", {
|
||||||
Image = if self.props.locked then Assets.Images.Dropdown.Locked else Assets.Images.Dropdown.Arrow,
|
Image = if self.props.locked then Assets.Images.Dropdown.Locked else Assets.Images.Dropdown.Arrow,
|
||||||
ImageColor3 = self.openBinding:map(function(a)
|
ImageColor3 = theme.IconColor,
|
||||||
return theme.Closed.IconColor:Lerp(theme.Open.IconColor, a)
|
|
||||||
end),
|
|
||||||
ImageTransparency = self.props.transparency,
|
ImageTransparency = self.props.transparency,
|
||||||
|
|
||||||
Size = UDim2.new(0, 18, 0, 18),
|
Size = UDim2.new(0, 18, 0, 18),
|
||||||
|
|||||||
41
plugin/src/App/Components/EditableImage.lua
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local EditableImage = Roact.PureComponent:extend("EditableImage")
|
||||||
|
|
||||||
|
function EditableImage:init()
|
||||||
|
self.ref = Roact.createRef()
|
||||||
|
end
|
||||||
|
|
||||||
|
function EditableImage:writePixels()
|
||||||
|
local image = self.ref.current
|
||||||
|
if not image then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not self.props.pixels then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
image:WritePixels(Vector2.zero, self.props.size, self.props.pixels)
|
||||||
|
end
|
||||||
|
|
||||||
|
function EditableImage:render()
|
||||||
|
return e("EditableImage", {
|
||||||
|
Size = self.props.size,
|
||||||
|
[Roact.Ref] = self.ref,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function EditableImage:didMount()
|
||||||
|
self:writePixels()
|
||||||
|
end
|
||||||
|
|
||||||
|
function EditableImage:didUpdate()
|
||||||
|
self:writePixels()
|
||||||
|
end
|
||||||
|
|
||||||
|
return EditableImage
|
||||||
@@ -14,130 +14,18 @@ local EMPTY_TABLE = {}
|
|||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
local ChangeList = Roact.Component:extend("ChangeList")
|
local function ViewDiffButton(props)
|
||||||
|
|
||||||
function ChangeList:init()
|
|
||||||
self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))
|
|
||||||
end
|
|
||||||
|
|
||||||
function ChangeList:render()
|
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
local props = self.props
|
return e("TextButton", {
|
||||||
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 = "",
|
Text = "",
|
||||||
Size = UDim2.new(0.7, 0, 1, -4),
|
Size = UDim2.new(0.7, 0, 1, -4),
|
||||||
LayoutOrder = 2,
|
LayoutOrder = 2,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
[Roact.Event.Activated] = function()
|
[Roact.Event.Activated] = props.onClick,
|
||||||
if props.showSourceDiff then
|
|
||||||
props.showSourceDiff(tostring(values[2]), tostring(values[3]))
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
}, {
|
}, {
|
||||||
e(BorderedContainer, {
|
e(BorderedContainer, {
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
transparency = self.props.transparency:map(function(t)
|
transparency = props.transparency:map(function(t)
|
||||||
return 0.5 + (0.5 * t)
|
return 0.5 + (0.5 * t)
|
||||||
end),
|
end),
|
||||||
}, {
|
}, {
|
||||||
@@ -163,7 +51,7 @@ function ChangeList:render()
|
|||||||
Icon = e("ImageLabel", {
|
Icon = e("ImageLabel", {
|
||||||
Image = Assets.Images.Icons.Expand,
|
Image = Assets.Images.Icons.Expand,
|
||||||
ImageColor3 = theme.Settings.Setting.DescriptionColor,
|
ImageColor3 = theme.Settings.Setting.DescriptionColor,
|
||||||
ImageTransparency = self.props.transparency,
|
ImageTransparency = props.transparency,
|
||||||
|
|
||||||
Size = UDim2.new(0, 16, 0, 16),
|
Size = UDim2.new(0, 16, 0, 16),
|
||||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||||
@@ -173,13 +61,160 @@ function ChangeList:render()
|
|||||||
LayoutOrder = 2,
|
LayoutOrder = 2,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
continue
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function RowContent(props)
|
||||||
|
local values = props.values
|
||||||
|
local metadata = props.metadata
|
||||||
|
|
||||||
|
if props.showStringDiff and values[1] == "Source" then
|
||||||
|
-- Special case for .Source updates
|
||||||
|
return e(ViewDiffButton, {
|
||||||
|
transparency = props.transparency,
|
||||||
|
onClick = function()
|
||||||
|
if not props.showStringDiff then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
props.showStringDiff(tostring(values[2]), tostring(values[3]))
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
if props.showTableDiff and (type(values[2]) == "table" or type(values[3]) == "table") then
|
||||||
|
-- Special case for table properties (like Attributes/Tags)
|
||||||
|
return e(ViewDiffButton, {
|
||||||
|
transparency = props.transparency,
|
||||||
|
onClick = function()
|
||||||
|
if not props.showTableDiff then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
props.showTableDiff(values[2], values[3])
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return Theme.with(function(theme)
|
||||||
|
return Roact.createFragment({
|
||||||
|
ColumnB = e(
|
||||||
|
"Frame",
|
||||||
|
{
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.new(0.35, 0, 1, 0),
|
||||||
|
LayoutOrder = 2,
|
||||||
|
},
|
||||||
|
e(DisplayValue, {
|
||||||
|
value = values[2],
|
||||||
|
transparency = props.transparency,
|
||||||
|
textColor = if metadata.isWarning
|
||||||
|
then theme.Diff.Warning
|
||||||
|
else theme.Settings.Setting.DescriptionColor,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
ColumnC = e(
|
||||||
|
"Frame",
|
||||||
|
{
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.new(0.35, 0, 1, 0),
|
||||||
|
LayoutOrder = 3,
|
||||||
|
},
|
||||||
|
e(DisplayValue, {
|
||||||
|
value = values[3],
|
||||||
|
transparency = props.transparency,
|
||||||
|
textColor = if metadata.isWarning
|
||||||
|
then theme.Diff.Warning
|
||||||
|
else theme.Settings.Setting.DescriptionColor,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
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 headerRow = changes[1]
|
||||||
|
local headers = e("Frame", {
|
||||||
|
Size = UDim2.new(1, 0, 0, 24),
|
||||||
|
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,
|
||||||
|
}),
|
||||||
|
ColumnA = e("TextLabel", {
|
||||||
|
Text = tostring(headerRow[1]),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Font = Enum.Font.GothamBold,
|
||||||
|
TextSize = 14,
|
||||||
|
TextColor3 = theme.TextColor,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
TextTransparency = props.transparency,
|
||||||
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
|
Size = UDim2.new(0.3, 0, 1, 0),
|
||||||
|
LayoutOrder = 1,
|
||||||
|
}),
|
||||||
|
ColumnB = e("TextLabel", {
|
||||||
|
Text = tostring(headerRow[2]),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Font = Enum.Font.GothamBold,
|
||||||
|
TextSize = 14,
|
||||||
|
TextColor3 = theme.TextColor,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
TextTransparency = props.transparency,
|
||||||
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
|
Size = UDim2.new(0.35, 0, 1, 0),
|
||||||
|
LayoutOrder = 2,
|
||||||
|
}),
|
||||||
|
ColumnC = e("TextLabel", {
|
||||||
|
Text = tostring(headerRow[3]),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Font = Enum.Font.GothamBold,
|
||||||
|
TextSize = 14,
|
||||||
|
TextColor3 = theme.TextColor,
|
||||||
|
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
|
||||||
|
|
||||||
rows[row] = e("Frame", {
|
rows[row] = e("Frame", {
|
||||||
Size = UDim2.new(1, 0, 0, 30),
|
Size = UDim2.new(1, 0, 0, 24),
|
||||||
BackgroundTransparency = row % 2 ~= 0 and rowTransparency or 1,
|
BackgroundTransparency = row % 2 ~= 0 and rowTransparency or 1,
|
||||||
BackgroundColor3 = theme.Diff.Row,
|
BackgroundColor3 = theme.Diff.Row,
|
||||||
BorderSizePixel = 0,
|
BorderSizePixel = 0,
|
||||||
@@ -192,44 +227,25 @@ function ChangeList:render()
|
|||||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||||
}),
|
}),
|
||||||
A = e("TextLabel", {
|
ColumnA = e("TextLabel", {
|
||||||
Text = (if isWarning then "⚠ " else "") .. tostring(values[1]),
|
Text = (if isWarning then "⚠ " else "") .. tostring(values[1]),
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Font = Enum.Font.GothamMedium,
|
Font = Enum.Font.GothamMedium,
|
||||||
TextSize = 14,
|
TextSize = 14,
|
||||||
TextColor3 = if isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
|
TextColor3 = if isWarning then theme.Diff.Warning else theme.TextColor,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextTransparency = props.transparency,
|
TextTransparency = props.transparency,
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
Size = UDim2.new(0.3, 0, 1, 0),
|
Size = UDim2.new(0.3, 0, 1, 0),
|
||||||
LayoutOrder = 1,
|
LayoutOrder = 1,
|
||||||
}),
|
}),
|
||||||
B = e(
|
Content = e(RowContent, {
|
||||||
"Frame",
|
values = values,
|
||||||
{
|
metadata = metadata,
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Size = UDim2.new(0.35, 0, 1, 0),
|
|
||||||
LayoutOrder = 2,
|
|
||||||
},
|
|
||||||
e(DisplayValue, {
|
|
||||||
value = values[2],
|
|
||||||
transparency = props.transparency,
|
transparency = props.transparency,
|
||||||
textColor = if isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
|
showStringDiff = props.showStringDiff,
|
||||||
})
|
showTableDiff = props.showTableDiff,
|
||||||
),
|
}),
|
||||||
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
|
end
|
||||||
|
|
||||||
@@ -253,8 +269,8 @@ function ChangeList:render()
|
|||||||
}, {
|
}, {
|
||||||
Headers = headers,
|
Headers = headers,
|
||||||
Values = e(ScrollingFrame, {
|
Values = e(ScrollingFrame, {
|
||||||
size = UDim2.new(1, 0, 1, -30),
|
size = UDim2.new(1, 0, 1, -24),
|
||||||
position = UDim2.new(0, 0, 0, 30),
|
position = UDim2.new(0, 0, 0, 24),
|
||||||
contentSize = self.contentSize,
|
contentSize = self.contentSize,
|
||||||
transparency = props.transparency,
|
transparency = props.transparency,
|
||||||
}, rows),
|
}, rows),
|
||||||
|
|||||||
@@ -104,8 +104,13 @@ local function DisplayValue(props)
|
|||||||
-- Or special text handling tostring for some?
|
-- Or special text handling tostring for some?
|
||||||
-- Will add as needed, let's see what cases arise.
|
-- Will add as needed, let's see what cases arise.
|
||||||
|
|
||||||
|
local textRepresentation = string.gsub(tostring(props.value), "%s", " ")
|
||||||
|
if t == "string" then
|
||||||
|
textRepresentation = '"' .. textRepresentation .. '"'
|
||||||
|
end
|
||||||
|
|
||||||
return e("TextLabel", {
|
return e("TextLabel", {
|
||||||
Text = string.gsub(tostring(props.value), "%s", " "),
|
Text = textRepresentation,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Font = Enum.Font.GothamMedium,
|
Font = Enum.Font.GothamMedium,
|
||||||
TextSize = 14,
|
TextSize = 14,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
local SelectionService = game:GetService("Selection")
|
local SelectionService = game:GetService("Selection")
|
||||||
local StudioService = game:GetService("StudioService")
|
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
@@ -15,7 +14,8 @@ local bindingUtil = require(Plugin.App.bindingUtil)
|
|||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
local ChangeList = require(script.Parent.ChangeList)
|
local ChangeList = require(script.Parent.ChangeList)
|
||||||
local Tooltip = require(script.Parent.Parent.Tooltip)
|
local Tooltip = require(Plugin.App.Components.Tooltip)
|
||||||
|
local ClassIcon = require(Plugin.App.Components.ClassIcon)
|
||||||
|
|
||||||
local Expansion = Roact.Component:extend("Expansion")
|
local Expansion = Roact.Component:extend("Expansion")
|
||||||
|
|
||||||
@@ -28,13 +28,14 @@ function Expansion:render()
|
|||||||
|
|
||||||
return e("Frame", {
|
return e("Frame", {
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Size = UDim2.new(1, -props.indent, 1, -30),
|
Size = UDim2.new(1, -props.indent, 1, -24),
|
||||||
Position = UDim2.new(0, props.indent, 0, 30),
|
Position = UDim2.new(0, props.indent, 0, 24),
|
||||||
}, {
|
}, {
|
||||||
ChangeList = e(ChangeList, {
|
ChangeList = e(ChangeList, {
|
||||||
changes = props.changeList,
|
changes = props.changeList,
|
||||||
transparency = props.transparency,
|
transparency = props.transparency,
|
||||||
showSourceDiff = props.showSourceDiff,
|
showStringDiff = props.showStringDiff,
|
||||||
|
showTableDiff = props.showTableDiff,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
@@ -43,7 +44,7 @@ local DomLabel = Roact.Component:extend("DomLabel")
|
|||||||
|
|
||||||
function DomLabel:init()
|
function DomLabel:init()
|
||||||
local initHeight = self.props.elementHeight:getValue()
|
local initHeight = self.props.elementHeight:getValue()
|
||||||
self.expanded = initHeight > 30
|
self.expanded = initHeight > 24
|
||||||
|
|
||||||
self.motor = Flipper.SingleMotor.new(initHeight)
|
self.motor = Flipper.SingleMotor.new(initHeight)
|
||||||
self.binding = bindingUtil.fromMotor(self.motor)
|
self.binding = bindingUtil.fromMotor(self.motor)
|
||||||
@@ -52,7 +53,7 @@ function DomLabel:init()
|
|||||||
renderExpansion = self.expanded,
|
renderExpansion = self.expanded,
|
||||||
})
|
})
|
||||||
self.motor:onStep(function(value)
|
self.motor:onStep(function(value)
|
||||||
local renderExpansion = value > 30
|
local renderExpansion = value > 24
|
||||||
|
|
||||||
self.props.setElementHeight(value)
|
self.props.setElementHeight(value)
|
||||||
if self.props.updateEvent then
|
if self.props.updateEvent then
|
||||||
@@ -80,7 +81,7 @@ function DomLabel:didUpdate(prevProps)
|
|||||||
then
|
then
|
||||||
-- Close the expansion when the domlabel is changed to a different thing
|
-- Close the expansion when the domlabel is changed to a different thing
|
||||||
self.expanded = false
|
self.expanded = false
|
||||||
self.motor:setGoal(Flipper.Spring.new(30, {
|
self.motor:setGoal(Flipper.Spring.new(24, {
|
||||||
frequency = 5,
|
frequency = 5,
|
||||||
dampingRatio = 1,
|
dampingRatio = 1,
|
||||||
}))
|
}))
|
||||||
@@ -89,17 +90,49 @@ end
|
|||||||
|
|
||||||
function DomLabel:render()
|
function DomLabel:render()
|
||||||
local props = self.props
|
local props = self.props
|
||||||
|
local depth = props.depth or 1
|
||||||
|
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
local iconProps = StudioService:GetClassIcon(props.className)
|
local color = if props.isWarning
|
||||||
local indent = (props.depth or 0) * 20 + 25
|
then theme.Diff.Warning
|
||||||
|
elseif props.patchType then theme.Diff[props.patchType]
|
||||||
|
else theme.TextColor
|
||||||
|
|
||||||
|
local indent = (depth - 1) * 12 + 15
|
||||||
|
|
||||||
-- Line guides help indent depth remain readable
|
-- Line guides help indent depth remain readable
|
||||||
local lineGuides = {}
|
local lineGuides = {}
|
||||||
for i = 1, props.depth or 0 do
|
for i = 2, depth do
|
||||||
|
if props.depthsComplete[i] then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
if props.isFinalChild and i == depth then
|
||||||
|
-- This line stops halfway down to merge with our connector for the right angle
|
||||||
lineGuides["Line_" .. i] = e("Frame", {
|
lineGuides["Line_" .. i] = e("Frame", {
|
||||||
Size = UDim2.new(0, 2, 1, 2),
|
Size = UDim2.new(0, 2, 0, 15),
|
||||||
Position = UDim2.new(0, (20 * i) + 15, 0, -1),
|
Position = UDim2.new(0, (12 * (i - 1)) + 6, 0, -1),
|
||||||
|
BorderSizePixel = 0,
|
||||||
|
BackgroundTransparency = props.transparency,
|
||||||
|
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||||
|
})
|
||||||
|
else
|
||||||
|
-- All other lines go all the way
|
||||||
|
-- with the exception of the final element, which stops halfway down
|
||||||
|
lineGuides["Line_" .. i] = e("Frame", {
|
||||||
|
Size = UDim2.new(0, 2, 1, if props.isFinalElement then -9 else 2),
|
||||||
|
Position = UDim2.new(0, (12 * (i - 1)) + 6, 0, -1),
|
||||||
|
BorderSizePixel = 0,
|
||||||
|
BackgroundTransparency = props.transparency,
|
||||||
|
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if depth ~= 1 then
|
||||||
|
lineGuides["Connector"] = e("Frame", {
|
||||||
|
Size = UDim2.new(0, 8, 0, 2),
|
||||||
|
Position = UDim2.new(0, 2 + (12 * props.depth), 0, 12),
|
||||||
|
AnchorPoint = Vector2.xAxis,
|
||||||
BorderSizePixel = 0,
|
BorderSizePixel = 0,
|
||||||
BackgroundTransparency = props.transparency,
|
BackgroundTransparency = props.transparency,
|
||||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||||
@@ -108,9 +141,8 @@ function DomLabel:render()
|
|||||||
|
|
||||||
return e("Frame", {
|
return e("Frame", {
|
||||||
ClipsDescendants = true,
|
ClipsDescendants = true,
|
||||||
BackgroundColor3 = if props.patchType then theme.Diff[props.patchType] else nil,
|
BackgroundTransparency = if props.elementIndex % 2 == 0 then 0.985 else 1,
|
||||||
BorderSizePixel = 0,
|
BackgroundColor3 = theme.Diff.Row,
|
||||||
BackgroundTransparency = props.patchType and props.transparency or 1,
|
|
||||||
Size = self.binding:map(function(expand)
|
Size = self.binding:map(function(expand)
|
||||||
return UDim2.new(1, 0, 0, expand)
|
return UDim2.new(1, 0, 0, expand)
|
||||||
end),
|
end),
|
||||||
@@ -140,8 +172,8 @@ function DomLabel:render()
|
|||||||
|
|
||||||
if props.changeList then
|
if props.changeList then
|
||||||
self.expanded = not self.expanded
|
self.expanded = not self.expanded
|
||||||
local goalHeight = 30
|
local goalHeight = 24
|
||||||
+ (if self.expanded then math.clamp(#props.changeList * 30, 30, 30 * 6) else 0)
|
+ (if self.expanded then math.clamp(#props.changeList * 24, 24, 24 * 6) else 0)
|
||||||
self.motor:setGoal(Flipper.Spring.new(goalHeight, {
|
self.motor:setGoal(Flipper.Spring.new(goalHeight, {
|
||||||
frequency = 5,
|
frequency = 5,
|
||||||
dampingRatio = 1,
|
dampingRatio = 1,
|
||||||
@@ -166,46 +198,81 @@ function DomLabel:render()
|
|||||||
indent = indent,
|
indent = indent,
|
||||||
transparency = props.transparency,
|
transparency = props.transparency,
|
||||||
changeList = props.changeList,
|
changeList = props.changeList,
|
||||||
showSourceDiff = props.showSourceDiff,
|
showStringDiff = props.showStringDiff,
|
||||||
|
showTableDiff = props.showTableDiff,
|
||||||
})
|
})
|
||||||
else nil,
|
else nil,
|
||||||
DiffIcon = if props.patchType
|
DiffIcon = if props.patchType
|
||||||
then e("ImageLabel", {
|
then e("ImageLabel", {
|
||||||
Image = Assets.Images.Diff[props.patchType],
|
Image = Assets.Images.Diff[props.patchType],
|
||||||
ImageColor3 = theme.AddressEntry.PlaceholderColor,
|
ImageColor3 = color,
|
||||||
ImageTransparency = props.transparency,
|
ImageTransparency = props.transparency,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Size = UDim2.new(0, 20, 0, 20),
|
Size = UDim2.new(0, 14, 0, 14),
|
||||||
Position = UDim2.new(0, 0, 0, 15),
|
Position = UDim2.new(0, 0, 0, 12),
|
||||||
AnchorPoint = Vector2.new(0, 0.5),
|
AnchorPoint = Vector2.new(0, 0.5),
|
||||||
})
|
})
|
||||||
else nil,
|
else nil,
|
||||||
ClassIcon = e("ImageLabel", {
|
ClassIcon = e(ClassIcon, {
|
||||||
Image = iconProps.Image,
|
className = props.className,
|
||||||
ImageTransparency = props.transparency,
|
color = color,
|
||||||
ImageRectOffset = iconProps.ImageRectOffset,
|
transparency = props.transparency,
|
||||||
ImageRectSize = iconProps.ImageRectSize,
|
size = UDim2.new(0, 16, 0, 16),
|
||||||
BackgroundTransparency = 1,
|
position = UDim2.new(0, indent + 2, 0, 12),
|
||||||
Size = UDim2.new(0, 20, 0, 20),
|
anchorPoint = Vector2.new(0, 0.5),
|
||||||
Position = UDim2.new(0, indent, 0, 15),
|
|
||||||
AnchorPoint = Vector2.new(0, 0.5),
|
|
||||||
}),
|
}),
|
||||||
InstanceName = e("TextLabel", {
|
InstanceName = e("TextLabel", {
|
||||||
Text = (if props.isWarning then "⚠ " else "") .. props.name .. (props.hint and string.format(
|
Text = (if props.isWarning then "⚠ " else "") .. props.name,
|
||||||
' <font color="#%s">%s</font>',
|
|
||||||
theme.AddressEntry.PlaceholderColor:ToHex(),
|
|
||||||
props.hint
|
|
||||||
) or ""),
|
|
||||||
RichText = true,
|
RichText = true,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Font = Enum.Font.GothamMedium,
|
Font = if props.patchType then Enum.Font.GothamBold else Enum.Font.GothamMedium,
|
||||||
TextSize = 14,
|
TextSize = 14,
|
||||||
TextColor3 = if props.isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
|
TextColor3 = color,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextTransparency = props.transparency,
|
TextTransparency = props.transparency,
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
Size = UDim2.new(1, -indent - 50, 0, 30),
|
Size = UDim2.new(1, -indent - 50, 0, 24),
|
||||||
Position = UDim2.new(0, indent + 30, 0, 0),
|
Position = UDim2.new(0, indent + 22, 0, 0),
|
||||||
|
}),
|
||||||
|
ChangeInfo = e("Frame", {
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.new(1, -indent - 80, 0, 24),
|
||||||
|
Position = UDim2.new(1, -2, 0, 0),
|
||||||
|
AnchorPoint = Vector2.new(1, 0),
|
||||||
|
}, {
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
FillDirection = Enum.FillDirection.Horizontal,
|
||||||
|
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
Padding = UDim.new(0, 4),
|
||||||
|
}),
|
||||||
|
Edits = if props.changeInfo and props.changeInfo.edits
|
||||||
|
then e("TextLabel", {
|
||||||
|
Text = props.changeInfo.edits .. if props.changeInfo.failed then "," else "",
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Font = Enum.Font.Gotham,
|
||||||
|
TextSize = 14,
|
||||||
|
TextColor3 = theme.SubTextColor,
|
||||||
|
TextTransparency = props.transparency,
|
||||||
|
Size = UDim2.new(0, 0, 0, 16),
|
||||||
|
AutomaticSize = Enum.AutomaticSize.X,
|
||||||
|
LayoutOrder = 2,
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
|
Failed = if props.changeInfo and props.changeInfo.failed
|
||||||
|
then e("TextLabel", {
|
||||||
|
Text = props.changeInfo.failed,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Font = Enum.Font.Gotham,
|
||||||
|
TextSize = 14,
|
||||||
|
TextColor3 = theme.Diff.Warning,
|
||||||
|
TextTransparency = props.transparency,
|
||||||
|
Size = UDim2.new(0, 0, 0, 16),
|
||||||
|
AutomaticSize = Enum.AutomaticSize.X,
|
||||||
|
LayoutOrder = 6,
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
}),
|
}),
|
||||||
LineGuides = e("Folder", nil, lineGuides),
|
LineGuides = e("Folder", nil, lineGuides),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ local PatchTree = require(Plugin.PatchTree)
|
|||||||
local PatchSet = require(Plugin.PatchSet)
|
local PatchSet = require(Plugin.PatchSet)
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
|
||||||
local VirtualScroller = require(Plugin.App.Components.VirtualScroller)
|
local VirtualScroller = require(Plugin.App.Components.VirtualScroller)
|
||||||
|
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
@@ -55,33 +55,60 @@ function PatchVisualizer:render()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Recusively draw tree
|
-- Recusively draw tree
|
||||||
local scrollElements, elementHeights = {}, {}
|
local scrollElements, elementHeights, elementIndex = {}, {}, 0
|
||||||
|
|
||||||
if patchTree then
|
if patchTree then
|
||||||
|
local elementTotal = patchTree:getCount()
|
||||||
|
local depthsComplete = {}
|
||||||
local function drawNode(node, depth)
|
local function drawNode(node, depth)
|
||||||
local elementHeight, setElementHeight = Roact.createBinding(30)
|
elementIndex += 1
|
||||||
table.insert(elementHeights, elementHeight)
|
|
||||||
table.insert(
|
local parentNode = patchTree:getNode(node.parentId)
|
||||||
scrollElements,
|
local isFinalChild = true
|
||||||
e(DomLabel, {
|
if parentNode then
|
||||||
|
for _id, sibling in parentNode.children do
|
||||||
|
if type(sibling) == "table" and sibling.name and sibling.name > node.name then
|
||||||
|
isFinalChild = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local elementHeight, setElementHeight = Roact.createBinding(24)
|
||||||
|
elementHeights[elementIndex] = elementHeight
|
||||||
|
scrollElements[elementIndex] = e(DomLabel, {
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
showStringDiff = self.props.showStringDiff,
|
||||||
|
showTableDiff = self.props.showTableDiff,
|
||||||
updateEvent = self.updateEvent,
|
updateEvent = self.updateEvent,
|
||||||
elementHeight = elementHeight,
|
elementHeight = elementHeight,
|
||||||
setElementHeight = setElementHeight,
|
setElementHeight = setElementHeight,
|
||||||
|
elementIndex = elementIndex,
|
||||||
|
isFinalElement = elementIndex == elementTotal,
|
||||||
|
depth = depth,
|
||||||
|
depthsComplete = table.clone(depthsComplete),
|
||||||
|
hasChildren = (node.children ~= nil and next(node.children) ~= nil),
|
||||||
|
isFinalChild = isFinalChild,
|
||||||
patchType = node.patchType,
|
patchType = node.patchType,
|
||||||
className = node.className,
|
className = node.className,
|
||||||
isWarning = node.isWarning,
|
isWarning = node.isWarning,
|
||||||
instance = node.instance,
|
instance = node.instance,
|
||||||
name = node.name,
|
name = node.name,
|
||||||
hint = node.hint,
|
changeInfo = node.changeInfo,
|
||||||
changeList = node.changeList,
|
changeList = node.changeList,
|
||||||
depth = depth,
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
showSourceDiff = self.props.showSourceDiff,
|
|
||||||
})
|
})
|
||||||
)
|
|
||||||
|
if isFinalChild then
|
||||||
|
depthsComplete[depth] = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
patchTree:forEach(function(node, depth)
|
patchTree:forEach(function(node, depth)
|
||||||
|
depthsComplete[depth] = false
|
||||||
|
for i = depth + 1, #depthsComplete do
|
||||||
|
depthsComplete[i] = nil
|
||||||
|
end
|
||||||
|
|
||||||
drawNode(node, depth)
|
drawNode(node, depth)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@@ -91,6 +118,7 @@ function PatchVisualizer:render()
|
|||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
size = self.props.size,
|
size = self.props.size,
|
||||||
position = self.props.position,
|
position = self.props.position,
|
||||||
|
anchorPoint = self.props.anchorPoint,
|
||||||
layoutOrder = self.props.layoutOrder,
|
layoutOrder = self.props.layoutOrder,
|
||||||
}, {
|
}, {
|
||||||
CleanMerge = e("TextLabel", {
|
CleanMerge = e("TextLabel", {
|
||||||
@@ -98,14 +126,15 @@ function PatchVisualizer:render()
|
|||||||
Text = "No changes to sync, project is up to date.",
|
Text = "No changes to sync, project is up to date.",
|
||||||
Font = Enum.Font.GothamMedium,
|
Font = Enum.Font.GothamMedium,
|
||||||
TextSize = 15,
|
TextSize = 15,
|
||||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
TextColor3 = theme.TextColor,
|
||||||
TextWrapped = true,
|
TextWrapped = true,
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
Size = UDim2.new(1, 0, 1, 0),
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
VirtualScroller = e(VirtualScroller, {
|
VirtualScroller = e(VirtualScroller, {
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, -2),
|
||||||
|
position = UDim2.new(0, 0, 0, 2),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
count = #scrollElements,
|
count = #scrollElements,
|
||||||
updateEvent = self.updateEvent.Event,
|
updateEvent = self.updateEvent.Event,
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ local bindingUtil = require(Plugin.App.bindingUtil)
|
|||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local scrollDirToAutoSize = {
|
||||||
|
[Enum.ScrollingDirection.X] = Enum.AutomaticSize.X,
|
||||||
|
[Enum.ScrollingDirection.Y] = Enum.AutomaticSize.Y,
|
||||||
|
[Enum.ScrollingDirection.XY] = Enum.AutomaticSize.XY,
|
||||||
|
}
|
||||||
|
|
||||||
local function ScrollingFrame(props)
|
local function ScrollingFrame(props)
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
return e("ScrollingFrame", {
|
return e("ScrollingFrame", {
|
||||||
@@ -28,7 +34,8 @@ local function ScrollingFrame(props)
|
|||||||
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 = if props.contentSize
|
||||||
|
then props.contentSize:map(function(value)
|
||||||
return UDim2.new(
|
return UDim2.new(
|
||||||
0,
|
0,
|
||||||
if (props.scrollingDirection and props.scrollingDirection ~= Enum.ScrollingDirection.Y)
|
if (props.scrollingDirection and props.scrollingDirection ~= Enum.ScrollingDirection.Y)
|
||||||
@@ -37,7 +44,11 @@ local function ScrollingFrame(props)
|
|||||||
0,
|
0,
|
||||||
value.Y
|
value.Y
|
||||||
)
|
)
|
||||||
end),
|
end)
|
||||||
|
else UDim2.new(),
|
||||||
|
AutomaticCanvasSize = if props.contentSize == nil
|
||||||
|
then scrollDirToAutoSize[props.scrollingDirection or Enum.ScrollingDirection.XY]
|
||||||
|
else nil,
|
||||||
|
|
||||||
BorderSizePixel = 0,
|
BorderSizePixel = 0,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ local function SlicedImage(props)
|
|||||||
Size = props.size,
|
Size = props.size,
|
||||||
Position = props.position,
|
Position = props.position,
|
||||||
AnchorPoint = props.anchorPoint,
|
AnchorPoint = props.anchorPoint,
|
||||||
|
AutomaticSize = props.automaticSize,
|
||||||
|
|
||||||
ZIndex = props.zIndex,
|
ZIndex = props.zIndex,
|
||||||
LayoutOrder = props.layoutOrder,
|
LayoutOrder = props.layoutOrder,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ local Log = require(Packages.Log)
|
|||||||
local Highlighter = require(Packages.Highlighter)
|
local Highlighter = require(Packages.Highlighter)
|
||||||
local StringDiff = require(script:FindFirstChild("StringDiff"))
|
local StringDiff = require(script:FindFirstChild("StringDiff"))
|
||||||
|
|
||||||
|
local Timer = require(Plugin.Timer)
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
|
|
||||||
local CodeLabel = require(Plugin.App.Components.CodeLabel)
|
local CodeLabel = require(Plugin.App.Components.CodeLabel)
|
||||||
@@ -52,7 +53,7 @@ function StringDiffVisualizer:updateScriptBackground()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function StringDiffVisualizer:didUpdate(previousProps)
|
function StringDiffVisualizer:didUpdate(previousProps)
|
||||||
if previousProps.oldText ~= self.props.oldText or previousProps.newText ~= self.props.newText then
|
if previousProps.oldString ~= self.props.oldString or previousProps.newString ~= self.props.newString then
|
||||||
self:calculateContentSize()
|
self:calculateContentSize()
|
||||||
local add, remove = self:calculateDiffLines()
|
local add, remove = self:calculateDiffLines()
|
||||||
self:setState({
|
self:setState({
|
||||||
@@ -63,28 +64,29 @@ function StringDiffVisualizer:didUpdate(previousProps)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function StringDiffVisualizer:calculateContentSize()
|
function StringDiffVisualizer:calculateContentSize()
|
||||||
local oldText, newText = self.props.oldText, self.props.newText
|
local oldString, newString = self.props.oldString, self.props.newString
|
||||||
|
|
||||||
local oldTextBounds = TextService:GetTextSize(oldText, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999))
|
local oldStringBounds = TextService:GetTextSize(oldString, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999))
|
||||||
local newTextBounds = TextService:GetTextSize(newText, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999))
|
local newStringBounds = TextService:GetTextSize(newString, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999))
|
||||||
|
|
||||||
self.setContentSize(
|
self.setContentSize(
|
||||||
Vector2.new(math.max(oldTextBounds.X, newTextBounds.X), math.max(oldTextBounds.Y, newTextBounds.Y))
|
Vector2.new(math.max(oldStringBounds.X, newStringBounds.X), math.max(oldStringBounds.Y, newStringBounds.Y))
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
function StringDiffVisualizer:calculateDiffLines()
|
function StringDiffVisualizer:calculateDiffLines()
|
||||||
local oldText, newText = self.props.oldText, self.props.newText
|
Timer.start("StringDiffVisualizer:calculateDiffLines")
|
||||||
|
local oldString, newString = self.props.oldString, self.props.newString
|
||||||
|
|
||||||
-- Diff the two texts
|
-- Diff the two texts
|
||||||
local startClock = os.clock()
|
local startClock = os.clock()
|
||||||
local diffs = StringDiff.findDiffs(oldText, newText)
|
local diffs = StringDiff.findDiffs(oldString, newString)
|
||||||
local stopClock = os.clock()
|
local stopClock = os.clock()
|
||||||
|
|
||||||
Log.trace(
|
Log.trace(
|
||||||
"Diffing {} byte and {} byte strings took {} microseconds and found {} diff sections",
|
"Diffing {} byte and {} byte strings took {} microseconds and found {} diff sections",
|
||||||
#oldText,
|
#oldString,
|
||||||
#newText,
|
#newString,
|
||||||
math.round((stopClock - startClock) * 1000 * 1000),
|
math.round((stopClock - startClock) * 1000 * 1000),
|
||||||
#diffs
|
#diffs
|
||||||
)
|
)
|
||||||
@@ -133,11 +135,12 @@ function StringDiffVisualizer:calculateDiffLines()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Timer.stop()
|
||||||
return add, remove
|
return add, remove
|
||||||
end
|
end
|
||||||
|
|
||||||
function StringDiffVisualizer:render()
|
function StringDiffVisualizer:render()
|
||||||
local oldText, newText = self.props.oldText, self.props.newText
|
local oldString, newString = self.props.oldString, self.props.newString
|
||||||
|
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
return e(BorderedContainer, {
|
return e(BorderedContainer, {
|
||||||
@@ -175,7 +178,7 @@ function StringDiffVisualizer:render()
|
|||||||
Source = e(CodeLabel, {
|
Source = e(CodeLabel, {
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
position = UDim2.new(0, 0, 0, 0),
|
position = UDim2.new(0, 0, 0, 0),
|
||||||
text = oldText,
|
text = oldString,
|
||||||
lineBackground = theme.Diff.Remove,
|
lineBackground = theme.Diff.Remove,
|
||||||
markedLines = self.state.remove,
|
markedLines = self.state.remove,
|
||||||
}),
|
}),
|
||||||
@@ -190,7 +193,7 @@ function StringDiffVisualizer:render()
|
|||||||
Source = e(CodeLabel, {
|
Source = e(CodeLabel, {
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
position = UDim2.new(0, 0, 0, 0),
|
position = UDim2.new(0, 0, 0, 0),
|
||||||
text = newText,
|
text = newString,
|
||||||
lineBackground = theme.Diff.Add,
|
lineBackground = theme.Diff.Add,
|
||||||
markedLines = self.state.add,
|
markedLines = self.state.add,
|
||||||
}),
|
}),
|
||||||
|
|||||||
195
plugin/src/App/Components/TableDiffVisualizer/Array.lua
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
|
local Timer = require(Plugin.Timer)
|
||||||
|
local Assets = require(Plugin.Assets)
|
||||||
|
local Theme = require(Plugin.App.Theme)
|
||||||
|
|
||||||
|
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
||||||
|
local DisplayValue = require(Plugin.App.Components.PatchVisualizer.DisplayValue)
|
||||||
|
|
||||||
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local Array = Roact.Component:extend("Array")
|
||||||
|
|
||||||
|
function Array:init()
|
||||||
|
self:setState({
|
||||||
|
diff = self:calculateDiff(),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function Array:calculateDiff()
|
||||||
|
Timer.start("Array:calculateDiff")
|
||||||
|
--[[
|
||||||
|
Find the indexes that are added or removed from the array,
|
||||||
|
and display them side by side with gaps for the indexes that
|
||||||
|
dont exist in the opposite array.
|
||||||
|
]]
|
||||||
|
local oldTable, newTable = self.props.oldTable or {}, self.props.newTable or {}
|
||||||
|
|
||||||
|
local i, j = 1, 1
|
||||||
|
local diff = {}
|
||||||
|
|
||||||
|
while i <= #oldTable and j <= #newTable do
|
||||||
|
if oldTable[i] == newTable[j] then
|
||||||
|
table.insert(diff, { oldTable[i], newTable[j] }) -- Unchanged
|
||||||
|
i += 1
|
||||||
|
j += 1
|
||||||
|
elseif not table.find(newTable, oldTable[i], j) then
|
||||||
|
table.insert(diff, { oldTable[i], nil }) -- Removal
|
||||||
|
i += 1
|
||||||
|
elseif not table.find(oldTable, newTable[j], i) then
|
||||||
|
table.insert(diff, { nil, newTable[j] }) -- Addition
|
||||||
|
j += 1
|
||||||
|
else
|
||||||
|
if table.find(newTable, oldTable[i], j) then
|
||||||
|
table.insert(diff, { nil, newTable[j] }) -- Addition
|
||||||
|
j += 1
|
||||||
|
else
|
||||||
|
table.insert(diff, { oldTable[i], nil }) -- Removal
|
||||||
|
i += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle remaining elements
|
||||||
|
while i <= #oldTable do
|
||||||
|
table.insert(diff, { oldTable[i], nil }) -- Remaining Removals
|
||||||
|
i += 1
|
||||||
|
end
|
||||||
|
while j <= #newTable do
|
||||||
|
table.insert(diff, { nil, newTable[j] }) -- Remaining Additions
|
||||||
|
j += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
Timer.stop()
|
||||||
|
return diff
|
||||||
|
end
|
||||||
|
|
||||||
|
function Array:didUpdate(previousProps)
|
||||||
|
if previousProps.oldTable ~= self.props.oldTable or previousProps.newTable ~= self.props.newTable then
|
||||||
|
self:setState({
|
||||||
|
diff = self:calculateDiff(),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Array:render()
|
||||||
|
return Theme.with(function(theme)
|
||||||
|
local diff = self.state.diff
|
||||||
|
local lines = table.create(#diff)
|
||||||
|
|
||||||
|
for i, element in diff do
|
||||||
|
local oldValue = element[1]
|
||||||
|
local newValue = element[2]
|
||||||
|
|
||||||
|
local patchType = if oldValue == nil then "Add" elseif newValue == nil then "Remove" else "Remain"
|
||||||
|
|
||||||
|
table.insert(
|
||||||
|
lines,
|
||||||
|
e("Frame", {
|
||||||
|
Size = UDim2.new(1, 0, 0, 25),
|
||||||
|
BackgroundTransparency = if patchType == "Remain" then 1 else self.props.transparency,
|
||||||
|
BackgroundColor3 = if patchType == "Remain" then theme.Diff.Row else theme.Diff[patchType],
|
||||||
|
BorderSizePixel = 0,
|
||||||
|
LayoutOrder = i,
|
||||||
|
}, {
|
||||||
|
DiffIcon = if patchType ~= "Remain"
|
||||||
|
then e("ImageLabel", {
|
||||||
|
Image = Assets.Images.Diff[patchType],
|
||||||
|
ImageColor3 = theme.AddressEntry.PlaceholderColor,
|
||||||
|
ImageTransparency = self.props.transparency,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.new(0, 15, 0, 15),
|
||||||
|
Position = UDim2.new(0, 7, 0.5, 0),
|
||||||
|
AnchorPoint = Vector2.new(0, 0.5),
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
|
Old = e("Frame", {
|
||||||
|
Size = UDim2.new(0.5, -30, 1, 0),
|
||||||
|
Position = UDim2.new(0, 30, 0, 0),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
Display = if oldValue ~= nil
|
||||||
|
then e(DisplayValue, {
|
||||||
|
value = oldValue,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
textColor = theme.Settings.Setting.DescriptionColor,
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
|
}),
|
||||||
|
New = e("Frame", {
|
||||||
|
Size = UDim2.new(0.5, -10, 1, 0),
|
||||||
|
Position = UDim2.new(0.5, 5, 0, 0),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
Display = if newValue ~= nil
|
||||||
|
then e(DisplayValue, {
|
||||||
|
value = newValue,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
textColor = theme.Settings.Setting.DescriptionColor,
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Roact.createFragment({
|
||||||
|
Headers = e("Frame", {
|
||||||
|
Size = UDim2.new(1, 0, 0, 25),
|
||||||
|
BackgroundTransparency = self.props.transparency:map(function(t)
|
||||||
|
return 0.95 + (0.05 * t)
|
||||||
|
end),
|
||||||
|
BackgroundColor3 = theme.Diff.Row,
|
||||||
|
}, {
|
||||||
|
ColumnA = e("TextLabel", {
|
||||||
|
Size = UDim2.new(0.5, -30, 1, 0),
|
||||||
|
Position = UDim2.new(0, 30, 0, 0),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Text = "Old",
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
Font = Enum.Font.GothamBold,
|
||||||
|
TextSize = 14,
|
||||||
|
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
||||||
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
|
}),
|
||||||
|
ColumnB = e("TextLabel", {
|
||||||
|
Size = UDim2.new(0.5, -10, 1, 0),
|
||||||
|
Position = UDim2.new(0.5, 5, 0, 0),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Text = "New",
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
Font = Enum.Font.GothamBold,
|
||||||
|
TextSize = 14,
|
||||||
|
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
||||||
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
|
}),
|
||||||
|
Separator = e("Frame", {
|
||||||
|
Size = UDim2.new(1, 0, 0, 1),
|
||||||
|
Position = UDim2.new(0, 0, 1, 0),
|
||||||
|
BackgroundTransparency = 0,
|
||||||
|
BorderSizePixel = 0,
|
||||||
|
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
KeyValues = e(ScrollingFrame, {
|
||||||
|
position = UDim2.new(0, 1, 0, 25),
|
||||||
|
size = UDim2.new(1, -2, 1, -27),
|
||||||
|
scrollingDirection = Enum.ScrollingDirection.Y,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
}, {
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||||
|
}),
|
||||||
|
Lines = Roact.createFragment(lines),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Array
|
||||||
211
plugin/src/App/Components/TableDiffVisualizer/Dictionary.lua
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
|
local Timer = require(Plugin.Timer)
|
||||||
|
local Assets = require(Plugin.Assets)
|
||||||
|
local Theme = require(Plugin.App.Theme)
|
||||||
|
|
||||||
|
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
||||||
|
local DisplayValue = require(Plugin.App.Components.PatchVisualizer.DisplayValue)
|
||||||
|
|
||||||
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local Dictionary = Roact.Component:extend("Dictionary")
|
||||||
|
|
||||||
|
function Dictionary:init()
|
||||||
|
self:setState({
|
||||||
|
diff = self:calculateDiff(),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dictionary:calculateDiff()
|
||||||
|
Timer.start("Dictionary:calculateDiff")
|
||||||
|
local oldTable, newTable = self.props.oldTable or {}, self.props.newTable or {}
|
||||||
|
|
||||||
|
-- Diff the two tables and find the added keys, removed keys, and changed keys
|
||||||
|
local diff = {}
|
||||||
|
|
||||||
|
for key, oldValue in oldTable do
|
||||||
|
local newValue = newTable[key]
|
||||||
|
if newValue == nil then
|
||||||
|
table.insert(diff, {
|
||||||
|
key = key,
|
||||||
|
patchType = "Remove",
|
||||||
|
})
|
||||||
|
elseif newValue ~= oldValue then
|
||||||
|
-- Note: should this do some sort of deep comparison for various types?
|
||||||
|
table.insert(diff, {
|
||||||
|
key = key,
|
||||||
|
patchType = "Edit",
|
||||||
|
})
|
||||||
|
else
|
||||||
|
table.insert(diff, {
|
||||||
|
key = key,
|
||||||
|
patchType = "Remain",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for key in newTable do
|
||||||
|
if oldTable[key] == nil then
|
||||||
|
table.insert(diff, {
|
||||||
|
key = key,
|
||||||
|
patchType = "Add",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(diff, function(a, b)
|
||||||
|
return a.key < b.key
|
||||||
|
end)
|
||||||
|
|
||||||
|
Timer.stop()
|
||||||
|
return diff
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dictionary:didUpdate(previousProps)
|
||||||
|
if previousProps.oldTable ~= self.props.oldTable or previousProps.newTable ~= self.props.newTable then
|
||||||
|
self:setState({
|
||||||
|
diff = self:calculateDiff(),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Dictionary:render()
|
||||||
|
local oldTable, newTable = self.props.oldTable or {}, self.props.newTable or {}
|
||||||
|
local diff = self.state.diff
|
||||||
|
|
||||||
|
return Theme.with(function(theme)
|
||||||
|
local lines = table.create(#diff)
|
||||||
|
for order, line in diff do
|
||||||
|
local key = line.key
|
||||||
|
local oldValue = oldTable[key]
|
||||||
|
local newValue = newTable[key]
|
||||||
|
|
||||||
|
table.insert(
|
||||||
|
lines,
|
||||||
|
e("Frame", {
|
||||||
|
Size = UDim2.new(1, 0, 0, 25),
|
||||||
|
LayoutOrder = order,
|
||||||
|
BorderSizePixel = 0,
|
||||||
|
BackgroundTransparency = if line.patchType == "Remain" then 1 else self.props.transparency,
|
||||||
|
BackgroundColor3 = if line.patchType == "Remain"
|
||||||
|
then theme.Diff.Row
|
||||||
|
else theme.Diff[line.patchType],
|
||||||
|
}, {
|
||||||
|
DiffIcon = if line.patchType ~= "Remain"
|
||||||
|
then e("ImageLabel", {
|
||||||
|
Image = Assets.Images.Diff[line.patchType],
|
||||||
|
ImageColor3 = theme.AddressEntry.PlaceholderColor,
|
||||||
|
ImageTransparency = self.props.transparency,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.new(0, 15, 0, 15),
|
||||||
|
Position = UDim2.new(0, 7, 0.5, 0),
|
||||||
|
AnchorPoint = Vector2.new(0, 0.5),
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
|
KeyName = e("TextLabel", {
|
||||||
|
Size = UDim2.new(0.3, -15, 1, 0),
|
||||||
|
Position = UDim2.new(0, 30, 0, 0),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Text = key,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
Font = Enum.Font.GothamMedium,
|
||||||
|
TextSize = 14,
|
||||||
|
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
||||||
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
|
}),
|
||||||
|
OldValue = e("Frame", {
|
||||||
|
Size = UDim2.new(0.35, -7, 1, 0),
|
||||||
|
Position = UDim2.new(0.3, 15, 0, 0),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
e(DisplayValue, {
|
||||||
|
value = oldValue,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
textColor = theme.Settings.Setting.DescriptionColor,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
NewValue = e("Frame", {
|
||||||
|
Size = UDim2.new(0.35, -8, 1, 0),
|
||||||
|
Position = UDim2.new(0.65, 8, 0, 0),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
e(DisplayValue, {
|
||||||
|
value = newValue,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
textColor = theme.Settings.Setting.DescriptionColor,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Roact.createFragment({
|
||||||
|
Headers = e("Frame", {
|
||||||
|
Size = UDim2.new(1, 0, 0, 25),
|
||||||
|
BackgroundTransparency = self.props.transparency:map(function(t)
|
||||||
|
return 0.95 + (0.05 * t)
|
||||||
|
end),
|
||||||
|
BackgroundColor3 = theme.Diff.Row,
|
||||||
|
}, {
|
||||||
|
ColumnA = e("TextLabel", {
|
||||||
|
Size = UDim2.new(0.3, -15, 1, 0),
|
||||||
|
Position = UDim2.new(0, 30, 0, 0),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Text = "Key",
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
Font = Enum.Font.GothamBold,
|
||||||
|
TextSize = 14,
|
||||||
|
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
||||||
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
|
}),
|
||||||
|
ColumnB = e("TextLabel", {
|
||||||
|
Size = UDim2.new(0.35, -7, 1, 0),
|
||||||
|
Position = UDim2.new(0.3, 15, 0, 0),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Text = "Old",
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
Font = Enum.Font.GothamBold,
|
||||||
|
TextSize = 14,
|
||||||
|
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
||||||
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
|
}),
|
||||||
|
ColumnC = e("TextLabel", {
|
||||||
|
Size = UDim2.new(0.35, -8, 1, 0),
|
||||||
|
Position = UDim2.new(0.65, 8, 0, 0),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Text = "New",
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
Font = Enum.Font.GothamBold,
|
||||||
|
TextSize = 14,
|
||||||
|
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
||||||
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
|
}),
|
||||||
|
Separator = e("Frame", {
|
||||||
|
Size = UDim2.new(1, 0, 0, 1),
|
||||||
|
Position = UDim2.new(0, 0, 1, 0),
|
||||||
|
BackgroundTransparency = 0,
|
||||||
|
BorderSizePixel = 0,
|
||||||
|
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
KeyValues = e(ScrollingFrame, {
|
||||||
|
position = UDim2.new(0, 1, 0, 25),
|
||||||
|
size = UDim2.new(1, -2, 1, -27),
|
||||||
|
scrollingDirection = Enum.ScrollingDirection.Y,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
}, {
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Top,
|
||||||
|
}),
|
||||||
|
Lines = Roact.createFragment(lines),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Dictionary
|
||||||
48
plugin/src/App/Components/TableDiffVisualizer/init.lua
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
|
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||||
|
local Array = require(script:FindFirstChild("Array"))
|
||||||
|
local Dictionary = require(script:FindFirstChild("Dictionary"))
|
||||||
|
|
||||||
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local TableDiffVisualizer = Roact.Component:extend("TableDiffVisualizer")
|
||||||
|
|
||||||
|
function TableDiffVisualizer:render()
|
||||||
|
local oldTable, newTable = self.props.oldTable or {}, self.props.newTable or {}
|
||||||
|
|
||||||
|
-- Ensure we're diffing tables, not mixing types
|
||||||
|
if type(oldTable) ~= "table" then
|
||||||
|
oldTable = {}
|
||||||
|
end
|
||||||
|
if type(newTable) ~= "table" then
|
||||||
|
newTable = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local isArray = next(newTable) == 1 or next(oldTable) == 1
|
||||||
|
|
||||||
|
return e(BorderedContainer, {
|
||||||
|
size = self.props.size,
|
||||||
|
position = self.props.position,
|
||||||
|
anchorPoint = self.props.anchorPoint,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
}, {
|
||||||
|
Content = if isArray
|
||||||
|
then e(Array, {
|
||||||
|
oldTable = oldTable,
|
||||||
|
newTable = newTable,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
})
|
||||||
|
else e(Dictionary, {
|
||||||
|
oldTable = oldTable,
|
||||||
|
newTable = newTable,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return TableDiffVisualizer
|
||||||
56
plugin/src/App/Components/Tag.lua
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
|
local Assets = require(Plugin.Assets)
|
||||||
|
|
||||||
|
local SlicedImage = require(Plugin.App.Components.SlicedImage)
|
||||||
|
|
||||||
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
return function(props)
|
||||||
|
return e(SlicedImage, {
|
||||||
|
slice = Assets.Slices.RoundedBackground,
|
||||||
|
color = props.color,
|
||||||
|
transparency = props.transparency:map(function(transparency)
|
||||||
|
return 0.9 + (0.1 * transparency)
|
||||||
|
end),
|
||||||
|
layoutOrder = props.layoutOrder,
|
||||||
|
position = props.position,
|
||||||
|
anchorPoint = props.anchorPoint,
|
||||||
|
size = UDim2.new(0, 0, 0, 16),
|
||||||
|
automaticSize = Enum.AutomaticSize.X,
|
||||||
|
}, {
|
||||||
|
Padding = e("UIPadding", {
|
||||||
|
PaddingLeft = UDim.new(0, 4),
|
||||||
|
PaddingRight = UDim.new(0, 4),
|
||||||
|
PaddingTop = UDim.new(0, 2),
|
||||||
|
PaddingBottom = UDim.new(0, 2),
|
||||||
|
}),
|
||||||
|
Icon = if props.icon
|
||||||
|
then e("ImageLabel", {
|
||||||
|
Size = UDim2.new(0, 12, 0, 12),
|
||||||
|
Position = UDim2.new(0, 0, 0.5, 0),
|
||||||
|
AnchorPoint = Vector2.new(0, 0.5),
|
||||||
|
Image = props.icon,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
ImageColor3 = props.color,
|
||||||
|
ImageTransparency = props.transparency,
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
|
Text = e("TextLabel", {
|
||||||
|
Text = props.text,
|
||||||
|
Font = Enum.Font.GothamMedium,
|
||||||
|
TextSize = 12,
|
||||||
|
TextColor3 = props.color,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Center,
|
||||||
|
TextTransparency = props.transparency,
|
||||||
|
Size = UDim2.new(0, 0, 1, 0),
|
||||||
|
Position = UDim2.new(0, if props.icon then 15 else 0, 0, 0),
|
||||||
|
AutomaticSize = Enum.AutomaticSize.X,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
end
|
||||||
@@ -163,7 +163,6 @@ local Trigger = Roact.Component:extend("TooltipTrigger")
|
|||||||
function Trigger:init()
|
function Trigger:init()
|
||||||
self.id = HttpService:GenerateGUID(false)
|
self.id = HttpService:GenerateGUID(false)
|
||||||
self.ref = Roact.createRef()
|
self.ref = Roact.createRef()
|
||||||
self.mousePos = Vector2.zero
|
|
||||||
self.showingPopup = false
|
self.showingPopup = false
|
||||||
|
|
||||||
self.destroy = function()
|
self.destroy = function()
|
||||||
@@ -195,18 +194,22 @@ end
|
|||||||
function Trigger:isHovering()
|
function Trigger:isHovering()
|
||||||
local rbx = self.ref.current
|
local rbx = self.ref.current
|
||||||
if rbx then
|
if rbx then
|
||||||
local pos = rbx.AbsolutePosition
|
return rbx.GuiState == Enum.GuiState.Hover
|
||||||
local size = rbx.AbsoluteSize
|
|
||||||
local mousePos = self.mousePos
|
|
||||||
|
|
||||||
return mousePos.X >= pos.X
|
|
||||||
and mousePos.X <= pos.X + size.X
|
|
||||||
and mousePos.Y >= pos.Y
|
|
||||||
and mousePos.Y <= pos.Y + size.Y
|
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Trigger:getMousePos()
|
||||||
|
local rbx = self.ref.current
|
||||||
|
if rbx then
|
||||||
|
local widget = rbx:FindFirstAncestorOfClass("DockWidgetPluginGui")
|
||||||
|
if widget then
|
||||||
|
return widget:GetRelativeMousePosition()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return Vector2.zero
|
||||||
|
end
|
||||||
|
|
||||||
function Trigger:managePopup()
|
function Trigger:managePopup()
|
||||||
if self:isHovering() then
|
if self:isHovering() then
|
||||||
if self.showingPopup or self.showDelayThread then
|
if self.showingPopup or self.showDelayThread then
|
||||||
@@ -217,7 +220,7 @@ function Trigger:managePopup()
|
|||||||
self.showDelayThread = task.delay(DELAY, function()
|
self.showDelayThread = task.delay(DELAY, function()
|
||||||
self.props.context.addTip(self.id, {
|
self.props.context.addTip(self.id, {
|
||||||
Text = self.props.text,
|
Text = self.props.text,
|
||||||
Position = self.mousePos,
|
Position = self:getMousePos(),
|
||||||
Trigger = self.ref,
|
Trigger = self.ref,
|
||||||
})
|
})
|
||||||
self.showDelayThread = nil
|
self.showDelayThread = nil
|
||||||
@@ -234,13 +237,7 @@ function Trigger:managePopup()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Trigger:render()
|
function Trigger:render()
|
||||||
local function recalculate(rbx)
|
local function recalculate()
|
||||||
local widget = rbx:FindFirstAncestorOfClass("DockWidgetPluginGui")
|
|
||||||
if not widget then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
self.mousePos = widget:GetRelativeMousePosition()
|
|
||||||
|
|
||||||
self:managePopup()
|
self:managePopup()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -250,11 +247,9 @@ function Trigger:render()
|
|||||||
ZIndex = self.props.zIndex or 100,
|
ZIndex = self.props.zIndex or 100,
|
||||||
[Roact.Ref] = self.ref,
|
[Roact.Ref] = self.ref,
|
||||||
|
|
||||||
|
[Roact.Change.GuiState] = recalculate,
|
||||||
[Roact.Change.AbsolutePosition] = recalculate,
|
[Roact.Change.AbsolutePosition] = recalculate,
|
||||||
[Roact.Change.AbsoluteSize] = recalculate,
|
[Roact.Change.AbsoluteSize] = recalculate,
|
||||||
[Roact.Event.MouseMoved] = recalculate,
|
|
||||||
[Roact.Event.MouseLeave] = recalculate,
|
|
||||||
[Roact.Event.MouseEnter] = recalculate,
|
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -131,8 +131,8 @@ function VirtualScroller:render()
|
|||||||
Position = props.position,
|
Position = props.position,
|
||||||
AnchorPoint = props.anchorPoint,
|
AnchorPoint = props.anchorPoint,
|
||||||
BackgroundTransparency = props.backgroundTransparency or 1,
|
BackgroundTransparency = props.backgroundTransparency or 1,
|
||||||
BackgroundColor3 = props.backgroundColor3,
|
BackgroundColor3 = props.backgroundColor3 or theme.BorderedContainer.BackgroundColor,
|
||||||
BorderColor3 = props.borderColor3,
|
BorderColor3 = props.borderColor3 or theme.BorderedContainer.BorderColor,
|
||||||
CanvasSize = self.totalCanvas:map(function(s)
|
CanvasSize = self.totalCanvas:map(function(s)
|
||||||
return UDim2.fromOffset(0, s)
|
return UDim2.fromOffset(0, s)
|
||||||
end),
|
end),
|
||||||
|
|||||||
@@ -4,14 +4,16 @@ local Packages = Rojo.Packages
|
|||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
|
local Timer = require(Plugin.Timer)
|
||||||
|
local PatchTree = require(Plugin.PatchTree)
|
||||||
local Settings = require(Plugin.Settings)
|
local Settings = require(Plugin.Settings)
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local TextButton = require(Plugin.App.Components.TextButton)
|
local TextButton = require(Plugin.App.Components.TextButton)
|
||||||
local Header = require(Plugin.App.Components.Header)
|
|
||||||
local StudioPluginGui = require(Plugin.App.Components.Studio.StudioPluginGui)
|
local StudioPluginGui = require(Plugin.App.Components.Studio.StudioPluginGui)
|
||||||
local Tooltip = require(Plugin.App.Components.Tooltip)
|
local Tooltip = require(Plugin.App.Components.Tooltip)
|
||||||
local PatchVisualizer = require(Plugin.App.Components.PatchVisualizer)
|
local PatchVisualizer = require(Plugin.App.Components.PatchVisualizer)
|
||||||
local StringDiffVisualizer = require(Plugin.App.Components.StringDiffVisualizer)
|
local StringDiffVisualizer = require(Plugin.App.Components.StringDiffVisualizer)
|
||||||
|
local TableDiffVisualizer = require(Plugin.App.Components.TableDiffVisualizer)
|
||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
@@ -22,30 +24,50 @@ function ConfirmingPage:init()
|
|||||||
self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
|
self.containerSize, self.setContainerSize = Roact.createBinding(Vector2.new(0, 0))
|
||||||
|
|
||||||
self:setState({
|
self:setState({
|
||||||
showingSourceDiff = false,
|
patchTree = nil,
|
||||||
oldSource = "",
|
showingStringDiff = false,
|
||||||
newSource = "",
|
oldString = "",
|
||||||
|
newString = "",
|
||||||
|
showingTableDiff = false,
|
||||||
|
oldTable = {},
|
||||||
|
newTable = {},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if self.props.confirmData and self.props.confirmData.patch and self.props.confirmData.instanceMap then
|
||||||
|
self:buildPatchTree()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ConfirmingPage:didUpdate(prevProps)
|
||||||
|
if prevProps.confirmData ~= self.props.confirmData then
|
||||||
|
self:buildPatchTree()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ConfirmingPage:buildPatchTree()
|
||||||
|
Timer.start("ConfirmingPage:buildPatchTree")
|
||||||
|
self:setState({
|
||||||
|
patchTree = PatchTree.build(
|
||||||
|
self.props.confirmData.patch,
|
||||||
|
self.props.confirmData.instanceMap,
|
||||||
|
{ "Property", "Current", "Incoming" }
|
||||||
|
),
|
||||||
|
})
|
||||||
|
Timer.stop()
|
||||||
end
|
end
|
||||||
|
|
||||||
function ConfirmingPage:render()
|
function ConfirmingPage:render()
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
local pageContent = Roact.createFragment({
|
local pageContent = Roact.createFragment({
|
||||||
Header = e(Header, {
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
layoutOrder = 1,
|
|
||||||
}),
|
|
||||||
|
|
||||||
Title = e("TextLabel", {
|
Title = e("TextLabel", {
|
||||||
Text = string.format(
|
Text = string.format(
|
||||||
"Sync changes for project '%s':",
|
"Sync changes for project '%s':",
|
||||||
self.props.confirmData.serverInfo.projectName or "UNKNOWN"
|
self.props.confirmData.serverInfo.projectName or "UNKNOWN"
|
||||||
),
|
),
|
||||||
LayoutOrder = 2,
|
|
||||||
Font = Enum.Font.Gotham,
|
Font = Enum.Font.Gotham,
|
||||||
LineHeight = 1.2,
|
LineHeight = 1.2,
|
||||||
TextSize = 14,
|
TextSize = 14,
|
||||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
TextColor3 = theme.TextColor,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextTransparency = self.props.transparency,
|
TextTransparency = self.props.transparency,
|
||||||
Size = UDim2.new(1, 0, 0, 20),
|
Size = UDim2.new(1, 0, 0, 20),
|
||||||
@@ -53,19 +75,24 @@ function ConfirmingPage:render()
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
PatchVisualizer = e(PatchVisualizer, {
|
PatchVisualizer = e(PatchVisualizer, {
|
||||||
size = UDim2.new(1, 0, 1, -150),
|
size = UDim2.new(1, 0, 1, -100),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 3,
|
layoutOrder = 3,
|
||||||
|
|
||||||
changeListHeaders = { "Property", "Current", "Incoming" },
|
patchTree = self.state.patchTree,
|
||||||
patch = self.props.confirmData.patch,
|
|
||||||
instanceMap = self.props.confirmData.instanceMap,
|
|
||||||
|
|
||||||
showSourceDiff = function(oldSource: string, newSource: string)
|
showStringDiff = function(oldString: string, newString: string)
|
||||||
self:setState({
|
self:setState({
|
||||||
showingSourceDiff = true,
|
showingStringDiff = true,
|
||||||
oldSource = oldSource,
|
oldString = oldString,
|
||||||
newSource = newSource,
|
newString = newString,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
showTableDiff = function(oldTable: { [any]: any? }, newTable: { [any]: any? })
|
||||||
|
self:setState({
|
||||||
|
showingTableDiff = true,
|
||||||
|
oldTable = oldTable,
|
||||||
|
newTable = newTable,
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
}),
|
}),
|
||||||
@@ -121,6 +148,11 @@ function ConfirmingPage:render()
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
Padding = e("UIPadding", {
|
||||||
|
PaddingLeft = UDim.new(0, 8),
|
||||||
|
PaddingRight = UDim.new(0, 8),
|
||||||
|
}),
|
||||||
|
|
||||||
Layout = e("UIListLayout", {
|
Layout = e("UIListLayout", {
|
||||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||||
@@ -129,15 +161,10 @@ function ConfirmingPage:render()
|
|||||||
Padding = UDim.new(0, 10),
|
Padding = UDim.new(0, 10),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Padding = e("UIPadding", {
|
StringDiff = e(StudioPluginGui, {
|
||||||
PaddingLeft = UDim.new(0, 20),
|
id = "Rojo_ConfirmingStringDiff",
|
||||||
PaddingRight = UDim.new(0, 20),
|
title = "String diff",
|
||||||
}),
|
active = self.state.showingStringDiff,
|
||||||
|
|
||||||
SourceDiff = e(StudioPluginGui, {
|
|
||||||
id = "Rojo_ConfirmingSourceDiff",
|
|
||||||
title = "Source diff",
|
|
||||||
active = self.state.showingSourceDiff,
|
|
||||||
isEphemeral = true,
|
isEphemeral = true,
|
||||||
|
|
||||||
initDockState = Enum.InitialDockState.Float,
|
initDockState = Enum.InitialDockState.Float,
|
||||||
@@ -149,7 +176,7 @@ function ConfirmingPage:render()
|
|||||||
|
|
||||||
onClose = function()
|
onClose = function()
|
||||||
self:setState({
|
self:setState({
|
||||||
showingSourceDiff = false,
|
showingStringDiff = false,
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
}, {
|
}, {
|
||||||
@@ -165,8 +192,46 @@ function ConfirmingPage:render()
|
|||||||
anchorPoint = Vector2.new(0, 0),
|
anchorPoint = Vector2.new(0, 0),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
|
|
||||||
oldText = self.state.oldSource,
|
oldString = self.state.oldString,
|
||||||
newText = self.state.newSource,
|
newString = self.state.newString,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
|
TableDiff = e(StudioPluginGui, {
|
||||||
|
id = "Rojo_ConfirmingTableDiff",
|
||||||
|
title = "Table diff",
|
||||||
|
active = self.state.showingTableDiff,
|
||||||
|
isEphemeral = true,
|
||||||
|
|
||||||
|
initDockState = Enum.InitialDockState.Float,
|
||||||
|
overridePreviousState = true,
|
||||||
|
floatingSize = Vector2.new(500, 350),
|
||||||
|
minimumSize = Vector2.new(400, 250),
|
||||||
|
|
||||||
|
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||||
|
|
||||||
|
onClose = function()
|
||||||
|
self:setState({
|
||||||
|
showingTableDiff = false,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
}, {
|
||||||
|
TooltipsProvider = e(Tooltip.Provider, nil, {
|
||||||
|
Tooltips = e(Tooltip.Container, nil),
|
||||||
|
Content = e("Frame", {
|
||||||
|
Size = UDim2.fromScale(1, 1),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
e(TableDiffVisualizer, {
|
||||||
|
size = UDim2.new(1, -10, 1, -10),
|
||||||
|
position = UDim2.new(0, 5, 0, 5),
|
||||||
|
anchorPoint = Vector2.new(0, 0),
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
|
||||||
|
oldTable = self.state.oldTable,
|
||||||
|
newTable = self.state.newTable,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ local Plugin = Rojo.Plugin
|
|||||||
local Packages = Rojo.Packages
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
local Flipper = require(Packages.Flipper)
|
|
||||||
|
|
||||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
local timeUtil = require(Plugin.timeUtil)
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
local PatchSet = require(Plugin.PatchSet)
|
local PatchSet = require(Plugin.PatchSet)
|
||||||
@@ -18,86 +17,188 @@ local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
|||||||
local Tooltip = require(Plugin.App.Components.Tooltip)
|
local Tooltip = require(Plugin.App.Components.Tooltip)
|
||||||
local PatchVisualizer = require(Plugin.App.Components.PatchVisualizer)
|
local PatchVisualizer = require(Plugin.App.Components.PatchVisualizer)
|
||||||
local StringDiffVisualizer = require(Plugin.App.Components.StringDiffVisualizer)
|
local StringDiffVisualizer = require(Plugin.App.Components.StringDiffVisualizer)
|
||||||
|
local TableDiffVisualizer = require(Plugin.App.Components.TableDiffVisualizer)
|
||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
local AGE_UNITS = {
|
local ChangesViewer = Roact.Component:extend("ChangesViewer")
|
||||||
{ 31556909, "year" },
|
|
||||||
{ 2629743, "month" },
|
|
||||||
{ 604800, "week" },
|
|
||||||
{ 86400, "day" },
|
|
||||||
{ 3600, "hour" },
|
|
||||||
{
|
|
||||||
60,
|
|
||||||
"minute",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
function timeSinceText(elapsed: number): string
|
|
||||||
if elapsed < 3 then
|
|
||||||
return "just now"
|
|
||||||
end
|
|
||||||
|
|
||||||
local ageText = string.format("%d seconds ago", elapsed)
|
function ChangesViewer:init()
|
||||||
|
|
||||||
for _, UnitData in ipairs(AGE_UNITS) do
|
|
||||||
local UnitSeconds, UnitName = UnitData[1], UnitData[2]
|
|
||||||
if elapsed > UnitSeconds then
|
|
||||||
local c = math.floor(elapsed / UnitSeconds)
|
|
||||||
ageText = string.format("%d %s%s ago", c, UnitName, c > 1 and "s" or "")
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return ageText
|
|
||||||
end
|
|
||||||
|
|
||||||
local ChangesDrawer = Roact.Component:extend("ChangesDrawer")
|
|
||||||
|
|
||||||
function ChangesDrawer:init()
|
|
||||||
-- Hold onto the serve session during the lifecycle of this component
|
-- Hold onto the serve session during the lifecycle of this component
|
||||||
-- so that it can still render during the fade out after disconnecting
|
-- so that it can still render during the fade out after disconnecting
|
||||||
self.serveSession = self.props.serveSession
|
self.serveSession = self.props.serveSession
|
||||||
end
|
end
|
||||||
|
|
||||||
function ChangesDrawer:render()
|
function ChangesViewer:render()
|
||||||
if self.props.rendered == false or self.serveSession == nil then
|
if self.props.rendered == false or self.serveSession == nil or self.props.patchData == nil then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local unapplied = PatchSet.countChanges(self.props.patchData.unapplied)
|
||||||
|
local applied = PatchSet.countChanges(self.props.patchData.patch) - unapplied
|
||||||
|
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
return e(BorderedContainer, {
|
return Roact.createFragment({
|
||||||
transparency = self.props.transparency,
|
Navbar = e("Frame", {
|
||||||
size = self.props.height:map(function(y)
|
Size = UDim2.new(1, 0, 0, 40),
|
||||||
return UDim2.new(1, 0, y, -220 * y)
|
BackgroundTransparency = 1,
|
||||||
end),
|
|
||||||
position = UDim2.new(0, 0, 1, 0),
|
|
||||||
anchorPoint = Vector2.new(0, 1),
|
|
||||||
layoutOrder = self.props.layoutOrder,
|
|
||||||
}, {
|
}, {
|
||||||
Close = e(IconButton, {
|
Close = e(IconButton, {
|
||||||
icon = Assets.Images.Icons.Close,
|
icon = Assets.Images.Icons.Close,
|
||||||
iconSize = 24,
|
iconSize = 24,
|
||||||
color = theme.ConnectionDetails.DisconnectColor,
|
color = theme.Settings.Navbar.BackButtonColor,
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
|
|
||||||
position = UDim2.new(1, 0, 0, 0),
|
position = UDim2.new(0, 0, 0.5, 0),
|
||||||
anchorPoint = Vector2.new(1, 0),
|
anchorPoint = Vector2.new(0, 0.5),
|
||||||
|
|
||||||
onClick = self.props.onClose,
|
onClick = self.props.onBack,
|
||||||
}, {
|
}, {
|
||||||
Tip = e(Tooltip.Trigger, {
|
Tip = e(Tooltip.Trigger, {
|
||||||
text = "Close the patch visualizer",
|
text = "Close",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
PatchVisualizer = e(PatchVisualizer, {
|
Title = e("TextLabel", {
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
Text = "Sync",
|
||||||
|
Font = Enum.Font.GothamMedium,
|
||||||
|
TextSize = 17,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
TextColor3 = theme.TextColor,
|
||||||
|
TextTransparency = self.props.transparency,
|
||||||
|
Size = UDim2.new(1, -40, 0, 20),
|
||||||
|
Position = UDim2.new(0, 40, 0, 0),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Subtitle = e("TextLabel", {
|
||||||
|
Text = DateTime.fromUnixTimestamp(self.props.patchData.timestamp):FormatLocalTime("LTS", "en-us"),
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
Font = Enum.Font.Gotham,
|
||||||
|
TextSize = 15,
|
||||||
|
TextColor3 = theme.SubTextColor,
|
||||||
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
|
TextTransparency = self.props.transparency,
|
||||||
|
Size = UDim2.new(1, -40, 0, 16),
|
||||||
|
Position = UDim2.new(0, 40, 0, 20),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Info = e("Frame", {
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.new(0, 10, 0, 24),
|
||||||
|
AutomaticSize = Enum.AutomaticSize.X,
|
||||||
|
Position = UDim2.new(1, -5, 0.5, 0),
|
||||||
|
AnchorPoint = Vector2.new(1, 0.5),
|
||||||
|
}, {
|
||||||
|
Tooltip = e(Tooltip.Trigger, {
|
||||||
|
text = `{applied} changes applied`
|
||||||
|
.. (if unapplied > 0 then `, {unapplied} changes failed` else ""),
|
||||||
|
}),
|
||||||
|
Content = e("Frame", {
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.new(0, 0, 1, 0),
|
||||||
|
AutomaticSize = Enum.AutomaticSize.X,
|
||||||
|
}, {
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
FillDirection = Enum.FillDirection.Horizontal,
|
||||||
|
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
Padding = UDim.new(0, 4),
|
||||||
|
}),
|
||||||
|
|
||||||
|
StatusIcon = e("ImageLabel", {
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Image = if unapplied > 0
|
||||||
|
then Assets.Images.Icons.SyncWarning
|
||||||
|
else Assets.Images.Icons.SyncSuccess,
|
||||||
|
ImageColor3 = if unapplied > 0 then theme.Diff.Warning else theme.TextColor,
|
||||||
|
Size = UDim2.new(0, 24, 0, 24),
|
||||||
|
LayoutOrder = 10,
|
||||||
|
}),
|
||||||
|
StatusSpacer = e("Frame", {
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.new(0, 6, 0, 4),
|
||||||
|
LayoutOrder = 9,
|
||||||
|
}),
|
||||||
|
AppliedIcon = e("ImageLabel", {
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Image = Assets.Images.Icons.Checkmark,
|
||||||
|
ImageColor3 = theme.TextColor,
|
||||||
|
Size = UDim2.new(0, 16, 0, 16),
|
||||||
|
LayoutOrder = 1,
|
||||||
|
}),
|
||||||
|
AppliedText = e("TextLabel", {
|
||||||
|
Text = applied,
|
||||||
|
Font = Enum.Font.Gotham,
|
||||||
|
TextSize = 15,
|
||||||
|
TextColor3 = theme.TextColor,
|
||||||
|
TextTransparency = self.props.transparency,
|
||||||
|
Size = UDim2.new(0, 0, 1, 0),
|
||||||
|
AutomaticSize = Enum.AutomaticSize.X,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
LayoutOrder = 2,
|
||||||
|
}),
|
||||||
|
Warnings = if unapplied > 0
|
||||||
|
then Roact.createFragment({
|
||||||
|
WarningsSpacer = e("Frame", {
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.new(0, 4, 0, 4),
|
||||||
|
LayoutOrder = 3,
|
||||||
|
}),
|
||||||
|
UnappliedIcon = e("ImageLabel", {
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Image = Assets.Images.Icons.Exclamation,
|
||||||
|
ImageColor3 = theme.Diff.Warning,
|
||||||
|
Size = UDim2.new(0, 4, 0, 16),
|
||||||
|
LayoutOrder = 4,
|
||||||
|
}),
|
||||||
|
UnappliedText = e("TextLabel", {
|
||||||
|
Text = unapplied,
|
||||||
|
Font = Enum.Font.Gotham,
|
||||||
|
TextSize = 15,
|
||||||
|
TextColor3 = theme.Diff.Warning,
|
||||||
|
TextTransparency = self.props.transparency,
|
||||||
|
Size = UDim2.new(0, 0, 1, 0),
|
||||||
|
AutomaticSize = Enum.AutomaticSize.X,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
LayoutOrder = 5,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
|
Divider = e("Frame", {
|
||||||
|
BackgroundColor3 = theme.Settings.DividerColor,
|
||||||
|
BackgroundTransparency = self.props.transparency,
|
||||||
|
Size = UDim2.new(1, 0, 0, 1),
|
||||||
|
Position = UDim2.new(0, 0, 1, 0),
|
||||||
|
BorderSizePixel = 0,
|
||||||
|
}, {
|
||||||
|
Gradient = e("UIGradient", {
|
||||||
|
Transparency = NumberSequence.new({
|
||||||
|
NumberSequenceKeypoint.new(0, 1),
|
||||||
|
NumberSequenceKeypoint.new(0.1, 0),
|
||||||
|
NumberSequenceKeypoint.new(0.9, 0),
|
||||||
|
NumberSequenceKeypoint.new(1, 1),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
|
Patch = e(PatchVisualizer, {
|
||||||
|
size = UDim2.new(1, -10, 1, -65),
|
||||||
|
position = UDim2.new(0, 5, 1, -5),
|
||||||
|
anchorPoint = Vector2.new(0, 1),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 3,
|
layoutOrder = self.props.layoutOrder,
|
||||||
|
|
||||||
patchTree = self.props.patchTree,
|
patchTree = self.props.patchTree,
|
||||||
|
|
||||||
showSourceDiff = self.props.showSourceDiff,
|
showStringDiff = self.props.showStringDiff,
|
||||||
|
showTableDiff = self.props.showTableDiff,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
@@ -165,20 +266,7 @@ function ConnectedPage:getChangeInfoText()
|
|||||||
if patchData == nil then
|
if patchData == nil then
|
||||||
return ""
|
return ""
|
||||||
end
|
end
|
||||||
|
return timeUtil.elapsedToText(DateTime.now().UnixTimestamp - patchData.timestamp)
|
||||||
local elapsed = os.time() - patchData.timestamp
|
|
||||||
local unapplied = PatchSet.countChanges(patchData.unapplied)
|
|
||||||
|
|
||||||
return "<i>Synced "
|
|
||||||
.. timeSinceText(elapsed)
|
|
||||||
.. (if unapplied > 0
|
|
||||||
then string.format(
|
|
||||||
', <font color="#FF8E3C">but %d change%s failed to apply</font>',
|
|
||||||
unapplied,
|
|
||||||
unapplied == 1 and "" or "s"
|
|
||||||
)
|
|
||||||
else "")
|
|
||||||
.. "</i>"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function ConnectedPage:startChangeInfoTextUpdater()
|
function ConnectedPage:startChangeInfoTextUpdater()
|
||||||
@@ -188,17 +276,13 @@ function ConnectedPage:startChangeInfoTextUpdater()
|
|||||||
-- Start a new updater
|
-- Start a new updater
|
||||||
self.changeInfoTextUpdater = task.defer(function()
|
self.changeInfoTextUpdater = task.defer(function()
|
||||||
while true do
|
while true do
|
||||||
if self.state.hoveringChangeInfo then
|
|
||||||
self.setChangeInfoText("<u>" .. self:getChangeInfoText() .. "</u>")
|
|
||||||
else
|
|
||||||
self.setChangeInfoText(self:getChangeInfoText())
|
self.setChangeInfoText(self:getChangeInfoText())
|
||||||
end
|
|
||||||
|
|
||||||
local elapsed = os.time() - self.props.patchData.timestamp
|
local elapsed = DateTime.now().UnixTimestamp - self.props.patchData.timestamp
|
||||||
local updateInterval = 1
|
local updateInterval = 1
|
||||||
|
|
||||||
-- Update timestamp text as frequently as currently needed
|
-- Update timestamp text as frequently as currently needed
|
||||||
for _, UnitData in ipairs(AGE_UNITS) do
|
for _, UnitData in ipairs(timeUtil.AGE_UNITS) do
|
||||||
local UnitSeconds = UnitData[1]
|
local UnitSeconds = UnitData[1]
|
||||||
if elapsed > UnitSeconds then
|
if elapsed > UnitSeconds then
|
||||||
updateInterval = UnitSeconds
|
updateInterval = UnitSeconds
|
||||||
@@ -219,29 +303,12 @@ function ConnectedPage:stopChangeInfoTextUpdater()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ConnectedPage:init()
|
function ConnectedPage:init()
|
||||||
self.changeDrawerMotor = Flipper.SingleMotor.new(0)
|
|
||||||
self.changeDrawerHeight = bindingUtil.fromMotor(self.changeDrawerMotor)
|
|
||||||
|
|
||||||
self.changeDrawerMotor:onStep(function(value)
|
|
||||||
local renderChanges = value > 0.05
|
|
||||||
|
|
||||||
self:setState(function(state)
|
|
||||||
if state.renderChanges == renderChanges then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
renderChanges = renderChanges,
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
self:setState({
|
self:setState({
|
||||||
renderChanges = false,
|
renderChanges = false,
|
||||||
hoveringChangeInfo = false,
|
hoveringChangeInfo = false,
|
||||||
showingSourceDiff = false,
|
showingStringDiff = false,
|
||||||
oldSource = "",
|
oldString = "",
|
||||||
newSource = "",
|
newString = "",
|
||||||
})
|
})
|
||||||
|
|
||||||
self.changeInfoText, self.setChangeInfoText = Roact.createBinding("")
|
self.changeInfoText, self.setChangeInfoText = Roact.createBinding("")
|
||||||
@@ -258,12 +325,16 @@ function ConnectedPage:didUpdate(previousProps)
|
|||||||
-- New patch recieved
|
-- New patch recieved
|
||||||
self:startChangeInfoTextUpdater()
|
self:startChangeInfoTextUpdater()
|
||||||
self:setState({
|
self:setState({
|
||||||
showingSourceDiff = false,
|
showingStringDiff = false,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function ConnectedPage:render()
|
function ConnectedPage:render()
|
||||||
|
local syncWarning = self.props.patchData
|
||||||
|
and self.props.patchData.unapplied
|
||||||
|
and PatchSet.countChanges(self.props.patchData.unapplied) > 0
|
||||||
|
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
return Roact.createFragment({
|
return Roact.createFragment({
|
||||||
Padding = e("UIPadding", {
|
Padding = e("UIPadding", {
|
||||||
@@ -278,9 +349,88 @@ function ConnectedPage:render()
|
|||||||
Padding = UDim.new(0, 10),
|
Padding = UDim.new(0, 10),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
Heading = e("Frame", {
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.new(1, 0, 0, 32),
|
||||||
|
}, {
|
||||||
Header = e(Header, {
|
Header = e(Header, {
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 1,
|
}),
|
||||||
|
|
||||||
|
ChangeInfo = e("TextButton", {
|
||||||
|
Text = "",
|
||||||
|
Size = UDim2.new(0, 0, 1, 0),
|
||||||
|
AutomaticSize = Enum.AutomaticSize.X,
|
||||||
|
BackgroundColor3 = theme.BorderedContainer.BorderedColor,
|
||||||
|
BackgroundTransparency = if self.state.hoveringChangeInfo then 0.7 else 1,
|
||||||
|
BorderSizePixel = 0,
|
||||||
|
Position = UDim2.new(1, -5, 0.5, 0),
|
||||||
|
AnchorPoint = Vector2.new(1, 0.5),
|
||||||
|
[Roact.Event.MouseEnter] = function()
|
||||||
|
self:setState({
|
||||||
|
hoveringChangeInfo = true,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
[Roact.Event.MouseLeave] = function()
|
||||||
|
self:setState({
|
||||||
|
hoveringChangeInfo = false,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
[Roact.Event.Activated] = function()
|
||||||
|
self:setState(function(prevState)
|
||||||
|
prevState = prevState or {}
|
||||||
|
return {
|
||||||
|
renderChanges = not prevState.renderChanges,
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
}, {
|
||||||
|
Corner = e("UICorner", {
|
||||||
|
CornerRadius = UDim.new(0, 5),
|
||||||
|
}),
|
||||||
|
Tooltip = e(Tooltip.Trigger, {
|
||||||
|
text = if self.state.renderChanges then "Hide changes" else "View changes",
|
||||||
|
}),
|
||||||
|
Content = e("Frame", {
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.new(0, 0, 1, 0),
|
||||||
|
AutomaticSize = Enum.AutomaticSize.X,
|
||||||
|
}, {
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
FillDirection = Enum.FillDirection.Horizontal,
|
||||||
|
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
Padding = UDim.new(0, 5),
|
||||||
|
}),
|
||||||
|
Padding = e("UIPadding", {
|
||||||
|
PaddingLeft = UDim.new(0, 5),
|
||||||
|
PaddingRight = UDim.new(0, 5),
|
||||||
|
}),
|
||||||
|
Text = e("TextLabel", {
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Text = self.changeInfoText,
|
||||||
|
Font = Enum.Font.Gotham,
|
||||||
|
TextSize = 15,
|
||||||
|
TextColor3 = if syncWarning then theme.Diff.Warning else theme.Header.VersionColor,
|
||||||
|
TextTransparency = self.props.transparency,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Right,
|
||||||
|
Size = UDim2.new(0, 0, 1, 0),
|
||||||
|
AutomaticSize = Enum.AutomaticSize.X,
|
||||||
|
LayoutOrder = 1,
|
||||||
|
}),
|
||||||
|
Icon = e("ImageLabel", {
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Image = if syncWarning
|
||||||
|
then Assets.Images.Icons.SyncWarning
|
||||||
|
else Assets.Images.Icons.SyncSuccess,
|
||||||
|
ImageColor3 = if syncWarning then theme.Diff.Warning else theme.Header.VersionColor,
|
||||||
|
ImageTransparency = self.props.transparency,
|
||||||
|
Size = UDim2.new(0, 24, 0, 24),
|
||||||
|
LayoutOrder = 2,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
ConnectionDetails = e(ConnectionDetails, {
|
ConnectionDetails = e(ConnectionDetails, {
|
||||||
@@ -330,83 +480,65 @@ function ConnectedPage:render()
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
ChangeInfo = e("TextButton", {
|
ChangesViewer = e(StudioPluginGui, {
|
||||||
Text = self.changeInfoText,
|
id = "Rojo_ChangesViewer",
|
||||||
Font = Enum.Font.Gotham,
|
title = "View changes",
|
||||||
TextSize = 14,
|
active = self.state.renderChanges,
|
||||||
TextWrapped = true,
|
isEphemeral = true,
|
||||||
RichText = true,
|
|
||||||
TextColor3 = theme.Header.VersionColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextYAlignment = Enum.TextYAlignment.Top,
|
|
||||||
TextTransparency = self.props.transparency,
|
|
||||||
|
|
||||||
Size = UDim2.new(1, 0, 0, 28),
|
initDockState = Enum.InitialDockState.Float,
|
||||||
|
overridePreviousState = true,
|
||||||
|
floatingSize = Vector2.new(400, 500),
|
||||||
|
minimumSize = Vector2.new(300, 300),
|
||||||
|
|
||||||
LayoutOrder = 4,
|
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||||
BackgroundTransparency = 1,
|
|
||||||
|
|
||||||
[Roact.Event.MouseEnter] = function()
|
|
||||||
self:setState({
|
|
||||||
hoveringChangeInfo = true,
|
|
||||||
})
|
|
||||||
self.setChangeInfoText("<u>" .. self:getChangeInfoText() .. "</u>")
|
|
||||||
end,
|
|
||||||
|
|
||||||
[Roact.Event.MouseLeave] = function()
|
|
||||||
self:setState({
|
|
||||||
hoveringChangeInfo = false,
|
|
||||||
})
|
|
||||||
self.setChangeInfoText(self:getChangeInfoText())
|
|
||||||
end,
|
|
||||||
|
|
||||||
[Roact.Event.Activated] = function()
|
|
||||||
if self.state.renderChanges then
|
|
||||||
self.changeDrawerMotor:setGoal(Flipper.Spring.new(0, {
|
|
||||||
frequency = 4,
|
|
||||||
dampingRatio = 1,
|
|
||||||
}))
|
|
||||||
else
|
|
||||||
self.changeDrawerMotor:setGoal(Flipper.Spring.new(1, {
|
|
||||||
frequency = 3,
|
|
||||||
dampingRatio = 1,
|
|
||||||
}))
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
}, {
|
|
||||||
Tooltip = e(Tooltip.Trigger, {
|
|
||||||
text = if self.state.renderChanges then "Hide the changes" else "View the changes",
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
|
|
||||||
ChangesDrawer = e(ChangesDrawer, {
|
|
||||||
rendered = self.state.renderChanges,
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
patchTree = self.props.patchTree,
|
|
||||||
serveSession = self.props.serveSession,
|
|
||||||
height = self.changeDrawerHeight,
|
|
||||||
layoutOrder = 5,
|
|
||||||
|
|
||||||
showSourceDiff = function(oldSource: string, newSource: string)
|
|
||||||
self:setState({
|
|
||||||
showingSourceDiff = true,
|
|
||||||
oldSource = oldSource,
|
|
||||||
newSource = newSource,
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
|
|
||||||
onClose = function()
|
onClose = function()
|
||||||
self.changeDrawerMotor:setGoal(Flipper.Spring.new(0, {
|
self:setState({
|
||||||
frequency = 4,
|
renderChanges = false,
|
||||||
dampingRatio = 1,
|
})
|
||||||
}))
|
end,
|
||||||
|
}, {
|
||||||
|
TooltipsProvider = e(Tooltip.Provider, nil, {
|
||||||
|
Tooltips = e(Tooltip.Container, nil),
|
||||||
|
Content = e("Frame", {
|
||||||
|
Size = UDim2.fromScale(1, 1),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
Changes = e(ChangesViewer, {
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
rendered = self.state.renderChanges,
|
||||||
|
patchData = self.props.patchData,
|
||||||
|
patchTree = self.props.patchTree,
|
||||||
|
serveSession = self.props.serveSession,
|
||||||
|
showStringDiff = function(oldString: string, newString: string)
|
||||||
|
self:setState({
|
||||||
|
showingStringDiff = true,
|
||||||
|
oldString = oldString,
|
||||||
|
newString = newString,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
showTableDiff = function(oldTable: { [any]: any? }, newTable: { [any]: any? })
|
||||||
|
self:setState({
|
||||||
|
showingTableDiff = true,
|
||||||
|
oldTable = oldTable,
|
||||||
|
newTable = newTable,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
onBack = function()
|
||||||
|
self:setState({
|
||||||
|
renderChanges = false,
|
||||||
|
})
|
||||||
end,
|
end,
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
SourceDiff = e(StudioPluginGui, {
|
StringDiff = e(StudioPluginGui, {
|
||||||
id = "Rojo_ConnectedSourceDiff",
|
id = "Rojo_ConnectedStringDiff",
|
||||||
title = "Source diff",
|
title = "String diff",
|
||||||
active = self.state.showingSourceDiff,
|
active = self.state.showingStringDiff,
|
||||||
isEphemeral = true,
|
isEphemeral = true,
|
||||||
|
|
||||||
initDockState = Enum.InitialDockState.Float,
|
initDockState = Enum.InitialDockState.Float,
|
||||||
@@ -418,7 +550,7 @@ function ConnectedPage:render()
|
|||||||
|
|
||||||
onClose = function()
|
onClose = function()
|
||||||
self:setState({
|
self:setState({
|
||||||
showingSourceDiff = false,
|
showingStringDiff = false,
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
}, {
|
}, {
|
||||||
@@ -434,8 +566,46 @@ function ConnectedPage:render()
|
|||||||
anchorPoint = Vector2.new(0, 0),
|
anchorPoint = Vector2.new(0, 0),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
|
|
||||||
oldText = self.state.oldSource,
|
oldString = self.state.oldString,
|
||||||
newText = self.state.newSource,
|
newString = self.state.newString,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
|
TableDiff = e(StudioPluginGui, {
|
||||||
|
id = "Rojo_ConnectedTableDiff",
|
||||||
|
title = "Table diff",
|
||||||
|
active = self.state.showingTableDiff,
|
||||||
|
isEphemeral = true,
|
||||||
|
|
||||||
|
initDockState = Enum.InitialDockState.Float,
|
||||||
|
overridePreviousState = false,
|
||||||
|
floatingSize = Vector2.new(500, 350),
|
||||||
|
minimumSize = Vector2.new(400, 250),
|
||||||
|
|
||||||
|
zIndexBehavior = Enum.ZIndexBehavior.Sibling,
|
||||||
|
|
||||||
|
onClose = function()
|
||||||
|
self:setState({
|
||||||
|
showingTableDiff = false,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
}, {
|
||||||
|
TooltipsProvider = e(Tooltip.Provider, nil, {
|
||||||
|
Tooltips = e(Tooltip.Container, nil),
|
||||||
|
Content = e("Frame", {
|
||||||
|
Size = UDim2.fromScale(1, 1),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
e(TableDiffVisualizer, {
|
||||||
|
size = UDim2.new(1, -10, 1, -10),
|
||||||
|
position = UDim2.new(0, 5, 0, 5),
|
||||||
|
anchorPoint = Vector2.new(0, 0),
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
|
||||||
|
oldTable = self.state.oldTable,
|
||||||
|
newTable = self.state.newTable,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -13,10 +13,23 @@ local Theme = require(Plugin.App.Theme)
|
|||||||
local Checkbox = require(Plugin.App.Components.Checkbox)
|
local Checkbox = require(Plugin.App.Components.Checkbox)
|
||||||
local Dropdown = require(Plugin.App.Components.Dropdown)
|
local Dropdown = require(Plugin.App.Components.Dropdown)
|
||||||
local IconButton = require(Plugin.App.Components.IconButton)
|
local IconButton = require(Plugin.App.Components.IconButton)
|
||||||
|
local Tag = require(Plugin.App.Components.Tag)
|
||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
local DIVIDER_FADE_SIZE = 0.1
|
local DIVIDER_FADE_SIZE = 0.1
|
||||||
|
local TAG_TYPES = {
|
||||||
|
unstable = {
|
||||||
|
text = "UNSTABLE",
|
||||||
|
icon = Assets.Images.Icons.Warning,
|
||||||
|
color = { "Settings", "Setting", "UnstableColor" },
|
||||||
|
},
|
||||||
|
debug = {
|
||||||
|
text = "DEBUG",
|
||||||
|
icon = Assets.Images.Icons.Debug,
|
||||||
|
color = { "Settings", "Setting", "DebugColor" },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
local function getTextBounds(text, textSize, font, lineHeight, bounds)
|
local function getTextBounds(text, textSize, font, lineHeight, bounds)
|
||||||
local textBounds = TextService:GetTextSize(text, textSize, font, bounds)
|
local textBounds = TextService:GetTextSize(text, textSize, font, bounds)
|
||||||
@@ -27,6 +40,17 @@ local function getTextBounds(text, textSize, font, lineHeight, bounds)
|
|||||||
return Vector2.new(textBounds.X, lineHeightAbsolute * lineCount - (lineHeightAbsolute - textSize))
|
return Vector2.new(textBounds.X, lineHeightAbsolute * lineCount - (lineHeightAbsolute - textSize))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function getThemeColorFromPath(theme, path)
|
||||||
|
local color = theme
|
||||||
|
for _, key in path do
|
||||||
|
if color[key] == nil then
|
||||||
|
return theme.BrandColor
|
||||||
|
end
|
||||||
|
color = color[key]
|
||||||
|
end
|
||||||
|
return color
|
||||||
|
end
|
||||||
|
|
||||||
local Setting = Roact.Component:extend("Setting")
|
local Setting = Roact.Component:extend("Setting")
|
||||||
|
|
||||||
function Setting:init()
|
function Setting:init()
|
||||||
@@ -51,11 +75,11 @@ end
|
|||||||
|
|
||||||
function Setting:render()
|
function Setting:render()
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
theme = theme.Settings
|
local settingsTheme = theme.Settings
|
||||||
|
|
||||||
return e("Frame", {
|
return e("Frame", {
|
||||||
Size = self.contentSize:map(function(value)
|
Size = self.contentSize:map(function(value)
|
||||||
return UDim2.new(1, 0, 0, 20 + value.Y + 20)
|
return UDim2.new(1, 0, 0, value.Y + 20)
|
||||||
end),
|
end),
|
||||||
LayoutOrder = self.props.layoutOrder,
|
LayoutOrder = self.props.layoutOrder,
|
||||||
ZIndex = -self.props.layoutOrder,
|
ZIndex = -self.props.layoutOrder,
|
||||||
@@ -106,7 +130,7 @@ function Setting:render()
|
|||||||
then e(IconButton, {
|
then e(IconButton, {
|
||||||
icon = Assets.Images.Icons.Reset,
|
icon = Assets.Images.Icons.Reset,
|
||||||
iconSize = 24,
|
iconSize = 24,
|
||||||
color = theme.BackButtonColor,
|
color = settingsTheme.BackButtonColor,
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
visible = self.props.showReset,
|
visible = self.props.showReset,
|
||||||
layoutOrder = -1,
|
layoutOrder = -1,
|
||||||
@@ -120,29 +144,49 @@ function Setting:render()
|
|||||||
Size = UDim2.new(1, 0, 1, 0),
|
Size = UDim2.new(1, 0, 1, 0),
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}, {
|
}, {
|
||||||
|
Heading = e("Frame", {
|
||||||
|
Size = UDim2.new(1, 0, 0, 16),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||||
|
FillDirection = Enum.FillDirection.Horizontal,
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
Padding = UDim.new(0, 5),
|
||||||
|
}),
|
||||||
|
Tag = if self.props.tag and TAG_TYPES[self.props.tag]
|
||||||
|
then e(Tag, {
|
||||||
|
layoutOrder = 1,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
text = TAG_TYPES[self.props.tag].text,
|
||||||
|
icon = TAG_TYPES[self.props.tag].icon,
|
||||||
|
color = getThemeColorFromPath(theme, TAG_TYPES[self.props.tag].color),
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
Name = e("TextLabel", {
|
Name = e("TextLabel", {
|
||||||
Text = (if self.props.experimental then '<font color="#FF8E3C">⚠ </font>' else "")
|
Text = self.props.name,
|
||||||
.. self.props.name,
|
|
||||||
Font = Enum.Font.GothamBold,
|
Font = Enum.Font.GothamBold,
|
||||||
TextSize = 17,
|
TextSize = 16,
|
||||||
TextColor3 = theme.Setting.NameColor,
|
TextColor3 = if self.props.tag and TAG_TYPES[self.props.tag]
|
||||||
|
then getThemeColorFromPath(theme, TAG_TYPES[self.props.tag].color)
|
||||||
|
else settingsTheme.Setting.NameColor,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextTransparency = self.props.transparency,
|
TextTransparency = self.props.transparency,
|
||||||
RichText = true,
|
RichText = true,
|
||||||
|
|
||||||
Size = UDim2.new(1, 0, 0, 17),
|
Size = UDim2.new(1, 0, 0, 16),
|
||||||
|
|
||||||
LayoutOrder = 1,
|
LayoutOrder = 2,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
Description = e("TextLabel", {
|
Description = e("TextLabel", {
|
||||||
Text = (if self.props.experimental then '<font color="#FF8E3C">[Experimental] </font>' else "")
|
Text = self.props.description,
|
||||||
.. self.props.description,
|
|
||||||
Font = Enum.Font.Gotham,
|
Font = Enum.Font.Gotham,
|
||||||
LineHeight = 1.2,
|
LineHeight = 1.2,
|
||||||
TextSize = 14,
|
TextSize = 14,
|
||||||
TextColor3 = theme.Setting.DescriptionColor,
|
TextColor3 = settingsTheme.Setting.DescriptionColor,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextTransparency = self.props.transparency,
|
TextTransparency = self.props.transparency,
|
||||||
TextWrapped = true,
|
TextWrapped = true,
|
||||||
@@ -152,11 +196,9 @@ function Setting:render()
|
|||||||
containerSize = self.containerSize,
|
containerSize = self.containerSize,
|
||||||
inputSize = self.inputSize,
|
inputSize = self.inputSize,
|
||||||
}):map(function(values)
|
}):map(function(values)
|
||||||
local desc = (if self.props.experimental then "[Experimental] " else "")
|
|
||||||
.. self.props.description
|
|
||||||
local offset = values.inputSize.X + 5
|
local offset = values.inputSize.X + 5
|
||||||
local textBounds = getTextBounds(
|
local textBounds = getTextBounds(
|
||||||
desc,
|
self.props.description,
|
||||||
14,
|
14,
|
||||||
Enum.Font.Gotham,
|
Enum.Font.Gotham,
|
||||||
1.2,
|
1.2,
|
||||||
@@ -165,7 +207,7 @@ function Setting:render()
|
|||||||
return UDim2.new(1, -offset, 0, textBounds.Y)
|
return UDim2.new(1, -offset, 0, textBounds.Y)
|
||||||
end),
|
end),
|
||||||
|
|
||||||
LayoutOrder = 2,
|
LayoutOrder = 3,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -173,21 +215,16 @@ function Setting:render()
|
|||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||||
FillDirection = Enum.FillDirection.Vertical,
|
FillDirection = Enum.FillDirection.Vertical,
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
Padding = UDim.new(0, 6),
|
Padding = UDim.new(0, 5),
|
||||||
|
|
||||||
[Roact.Change.AbsoluteContentSize] = function(object)
|
[Roact.Change.AbsoluteContentSize] = function(object)
|
||||||
self.setContentSize(object.AbsoluteContentSize)
|
self.setContentSize(object.AbsoluteContentSize)
|
||||||
end,
|
end,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Padding = e("UIPadding", {
|
|
||||||
PaddingTop = UDim.new(0, 20),
|
|
||||||
PaddingBottom = UDim.new(0, 20),
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Divider = e("Frame", {
|
Divider = e("Frame", {
|
||||||
BackgroundColor3 = theme.DividerColor,
|
BackgroundColor3 = settingsTheme.DividerColor,
|
||||||
BackgroundTransparency = self.props.transparency,
|
BackgroundTransparency = self.props.transparency,
|
||||||
Size = UDim2.new(1, 0, 0, 1),
|
Size = UDim2.new(1, 0, 0, 1),
|
||||||
BorderSizePixel = 0,
|
BorderSizePixel = 0,
|
||||||
|
|||||||
@@ -75,26 +75,33 @@ function SettingsPage:init()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function SettingsPage:render()
|
function SettingsPage:render()
|
||||||
|
local layoutOrder = 0
|
||||||
|
local function layoutIncrement()
|
||||||
|
layoutOrder += 1
|
||||||
|
return layoutOrder
|
||||||
|
end
|
||||||
|
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
theme = theme.Settings
|
theme = theme.Settings
|
||||||
|
|
||||||
return e(ScrollingFrame, {
|
return Roact.createFragment({
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
|
||||||
contentSize = self.contentSize,
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
}, {
|
|
||||||
Navbar = e(Navbar, {
|
Navbar = e(Navbar, {
|
||||||
onBack = self.props.onBack,
|
onBack = self.props.onBack,
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 0,
|
layoutOrder = layoutIncrement(),
|
||||||
}),
|
}),
|
||||||
|
Content = e(ScrollingFrame, {
|
||||||
|
size = UDim2.new(1, 0, 1, -47),
|
||||||
|
position = UDim2.new(0, 0, 0, 47),
|
||||||
|
contentSize = self.contentSize,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
}, {
|
||||||
ShowNotifications = e(Setting, {
|
ShowNotifications = e(Setting, {
|
||||||
id = "showNotifications",
|
id = "showNotifications",
|
||||||
name = "Show Notifications",
|
name = "Show Notifications",
|
||||||
description = "Popup notifications in viewport",
|
description = "Popup notifications in viewport",
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 1,
|
layoutOrder = layoutIncrement(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
SyncReminder = e(Setting, {
|
SyncReminder = e(Setting, {
|
||||||
@@ -103,7 +110,7 @@ function SettingsPage:render()
|
|||||||
description = "Notify to sync when opening a place that has previously been synced",
|
description = "Notify to sync when opening a place that has previously been synced",
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
visible = Settings:getBinding("showNotifications"),
|
visible = Settings:getBinding("showNotifications"),
|
||||||
layoutOrder = 2,
|
layoutOrder = layoutIncrement(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
ConfirmationBehavior = e(Setting, {
|
ConfirmationBehavior = e(Setting, {
|
||||||
@@ -111,7 +118,7 @@ function SettingsPage:render()
|
|||||||
name = "Confirmation Behavior",
|
name = "Confirmation Behavior",
|
||||||
description = "When to prompt for confirmation before syncing",
|
description = "When to prompt for confirmation before syncing",
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 3,
|
layoutOrder = layoutIncrement(),
|
||||||
|
|
||||||
options = confirmationBehaviors,
|
options = confirmationBehaviors,
|
||||||
}),
|
}),
|
||||||
@@ -121,7 +128,7 @@ function SettingsPage:render()
|
|||||||
name = "Confirmation Threshold",
|
name = "Confirmation Threshold",
|
||||||
description = "How many modified instances to be considered a large change",
|
description = "How many modified instances to be considered a large change",
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 4,
|
layoutOrder = layoutIncrement(),
|
||||||
visible = Settings:getBinding("confirmationBehavior"):map(function(value)
|
visible = Settings:getBinding("confirmationBehavior"):map(function(value)
|
||||||
return value == "Large Changes"
|
return value == "Large Changes"
|
||||||
end),
|
end),
|
||||||
@@ -152,17 +159,44 @@ function SettingsPage:render()
|
|||||||
name = "Play Sounds",
|
name = "Play Sounds",
|
||||||
description = "Toggle sound effects",
|
description = "Toggle sound effects",
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 5,
|
layoutOrder = layoutIncrement(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
CheckForUpdates = e(Setting, {
|
||||||
|
id = "checkForUpdates",
|
||||||
|
name = "Check For Updates",
|
||||||
|
description = "Notify about newer compatible Rojo releases",
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
layoutOrder = layoutIncrement(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
CheckForPreleases = e(Setting, {
|
||||||
|
id = "checkForPrereleases",
|
||||||
|
name = "Include Prerelease Updates",
|
||||||
|
description = "Include prereleases when checking for updates",
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
layoutOrder = layoutIncrement(),
|
||||||
|
visible = if string.find(debug.traceback(), "\n[^\n]-user_.-$") == nil
|
||||||
|
then false -- Must be a local install to allow prerelease checks
|
||||||
|
else Settings:getBinding("checkForUpdates"),
|
||||||
|
}),
|
||||||
|
|
||||||
|
AutoConnectPlaytestServer = e(Setting, {
|
||||||
|
id = "autoConnectPlaytestServer",
|
||||||
|
name = "Auto Connect Playtest Server",
|
||||||
|
description = "Automatically connect game server to Rojo when playtesting while connected in Edit",
|
||||||
|
tag = "unstable",
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
layoutOrder = layoutIncrement(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
OpenScriptsExternally = e(Setting, {
|
OpenScriptsExternally = e(Setting, {
|
||||||
id = "openScriptsExternally",
|
id = "openScriptsExternally",
|
||||||
name = "Open Scripts Externally",
|
name = "Open Scripts Externally",
|
||||||
description = "Attempt to open scripts in an external editor",
|
description = "Attempt to open scripts in an external editor",
|
||||||
locked = self.props.syncActive,
|
tag = "unstable",
|
||||||
experimental = true,
|
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 6,
|
layoutOrder = layoutIncrement(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
TwoWaySync = e(Setting, {
|
TwoWaySync = e(Setting, {
|
||||||
@@ -170,17 +204,18 @@ function SettingsPage:render()
|
|||||||
name = "Two-Way Sync",
|
name = "Two-Way Sync",
|
||||||
description = "Editing files in Studio will sync them into the filesystem",
|
description = "Editing files in Studio will sync them into the filesystem",
|
||||||
locked = self.props.syncActive,
|
locked = self.props.syncActive,
|
||||||
experimental = true,
|
tag = "unstable",
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 7,
|
layoutOrder = layoutIncrement(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
LogLevel = e(Setting, {
|
LogLevel = e(Setting, {
|
||||||
id = "logLevel",
|
id = "logLevel",
|
||||||
name = "Log Level",
|
name = "Log Level",
|
||||||
description = "Plugin output verbosity level",
|
description = "Plugin output verbosity level",
|
||||||
|
tag = "debug",
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 100,
|
layoutOrder = layoutIncrement(),
|
||||||
|
|
||||||
options = invertedLevels,
|
options = invertedLevels,
|
||||||
showReset = Settings:getBinding("logLevel"):map(function(value)
|
showReset = Settings:getBinding("logLevel"):map(function(value)
|
||||||
@@ -195,8 +230,18 @@ function SettingsPage:render()
|
|||||||
id = "typecheckingEnabled",
|
id = "typecheckingEnabled",
|
||||||
name = "Typechecking",
|
name = "Typechecking",
|
||||||
description = "Toggle typechecking on the API surface",
|
description = "Toggle typechecking on the API surface",
|
||||||
|
tag = "debug",
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 101,
|
layoutOrder = layoutIncrement(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
TimingLogsEnabled = e(Setting, {
|
||||||
|
id = "timingLogsEnabled",
|
||||||
|
name = "Timing Logs",
|
||||||
|
description = "Toggle logging timing of internal actions for benchmarking Rojo performance",
|
||||||
|
tag = "debug",
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
layoutOrder = layoutIncrement(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Layout = e("UIListLayout", {
|
Layout = e("UIListLayout", {
|
||||||
@@ -212,6 +257,7 @@ function SettingsPage:render()
|
|||||||
PaddingLeft = UDim.new(0, 20),
|
PaddingLeft = UDim.new(0, 20),
|
||||||
PaddingRight = UDim.new(0, 20),
|
PaddingRight = UDim.new(0, 20),
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -24,227 +24,7 @@ local strict = require(script.Parent.Parent.strict)
|
|||||||
|
|
||||||
local BRAND_COLOR = Color3.fromHex("E13835")
|
local BRAND_COLOR = Color3.fromHex("E13835")
|
||||||
|
|
||||||
local lightTheme = strict("LightTheme", {
|
local Context = Roact.createContext({})
|
||||||
BackgroundColor = Color3.fromHex("FFFFFF"),
|
|
||||||
Button = {
|
|
||||||
Solid = {
|
|
||||||
ActionFillColor = Color3.fromHex("FFFFFF"),
|
|
||||||
ActionFillTransparency = 0.8,
|
|
||||||
Enabled = {
|
|
||||||
TextColor = Color3.fromHex("FFFFFF"),
|
|
||||||
BackgroundColor = BRAND_COLOR,
|
|
||||||
},
|
|
||||||
Disabled = {
|
|
||||||
TextColor = Color3.fromHex("FFFFFF"),
|
|
||||||
BackgroundColor = BRAND_COLOR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Bordered = {
|
|
||||||
ActionFillColor = Color3.fromHex("000000"),
|
|
||||||
ActionFillTransparency = 0.9,
|
|
||||||
Enabled = {
|
|
||||||
TextColor = Color3.fromHex("393939"),
|
|
||||||
BorderColor = Color3.fromHex("ACACAC"),
|
|
||||||
},
|
|
||||||
Disabled = {
|
|
||||||
TextColor = Color3.fromHex("393939"),
|
|
||||||
BorderColor = Color3.fromHex("ACACAC"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Checkbox = {
|
|
||||||
Active = {
|
|
||||||
IconColor = Color3.fromHex("FFFFFF"),
|
|
||||||
BackgroundColor = BRAND_COLOR,
|
|
||||||
},
|
|
||||||
Inactive = {
|
|
||||||
IconColor = Color3.fromHex("EEEEEE"),
|
|
||||||
BorderColor = Color3.fromHex("AFAFAF"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Dropdown = {
|
|
||||||
TextColor = Color3.fromHex("000000"),
|
|
||||||
BorderColor = Color3.fromHex("AFAFAF"),
|
|
||||||
BackgroundColor = Color3.fromHex("EEEEEE"),
|
|
||||||
Open = {
|
|
||||||
IconColor = BRAND_COLOR,
|
|
||||||
},
|
|
||||||
Closed = {
|
|
||||||
IconColor = Color3.fromHex("EEEEEE"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TextInput = {
|
|
||||||
Enabled = {
|
|
||||||
TextColor = Color3.fromHex("000000"),
|
|
||||||
PlaceholderColor = Color3.fromHex("8C8C8C"),
|
|
||||||
BorderColor = Color3.fromHex("ACACAC"),
|
|
||||||
},
|
|
||||||
Disabled = {
|
|
||||||
TextColor = Color3.fromHex("393939"),
|
|
||||||
PlaceholderColor = Color3.fromHex("8C8C8C"),
|
|
||||||
BorderColor = Color3.fromHex("AFAFAF"),
|
|
||||||
},
|
|
||||||
ActionFillColor = Color3.fromHex("000000"),
|
|
||||||
ActionFillTransparency = 0.9,
|
|
||||||
},
|
|
||||||
AddressEntry = {
|
|
||||||
TextColor = Color3.fromHex("000000"),
|
|
||||||
PlaceholderColor = Color3.fromHex("8C8C8C"),
|
|
||||||
},
|
|
||||||
BorderedContainer = {
|
|
||||||
BorderColor = Color3.fromHex("CBCBCB"),
|
|
||||||
BackgroundColor = Color3.fromHex("EEEEEE"),
|
|
||||||
},
|
|
||||||
Spinner = {
|
|
||||||
ForegroundColor = BRAND_COLOR,
|
|
||||||
BackgroundColor = Color3.fromHex("EEEEEE"),
|
|
||||||
},
|
|
||||||
Diff = {
|
|
||||||
Add = Color3.fromHex("baffbd"),
|
|
||||||
Remove = Color3.fromHex("ffbdba"),
|
|
||||||
Edit = Color3.fromHex("bacdff"),
|
|
||||||
Row = Color3.fromHex("000000"),
|
|
||||||
Warning = Color3.fromHex("FF8E3C"),
|
|
||||||
},
|
|
||||||
ConnectionDetails = {
|
|
||||||
ProjectNameColor = Color3.fromHex("000000"),
|
|
||||||
AddressColor = Color3.fromHex("000000"),
|
|
||||||
DisconnectColor = BRAND_COLOR,
|
|
||||||
},
|
|
||||||
Settings = {
|
|
||||||
DividerColor = Color3.fromHex("CBCBCB"),
|
|
||||||
Navbar = {
|
|
||||||
BackButtonColor = Color3.fromHex("000000"),
|
|
||||||
TextColor = Color3.fromHex("000000"),
|
|
||||||
},
|
|
||||||
Setting = {
|
|
||||||
NameColor = Color3.fromHex("000000"),
|
|
||||||
DescriptionColor = Color3.fromHex("5F5F5F"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Header = {
|
|
||||||
LogoColor = BRAND_COLOR,
|
|
||||||
VersionColor = Color3.fromHex("727272"),
|
|
||||||
},
|
|
||||||
Notification = {
|
|
||||||
InfoColor = Color3.fromHex("000000"),
|
|
||||||
CloseColor = BRAND_COLOR,
|
|
||||||
},
|
|
||||||
ErrorColor = Color3.fromHex("000000"),
|
|
||||||
ScrollBarColor = Color3.fromHex("000000"),
|
|
||||||
})
|
|
||||||
|
|
||||||
local darkTheme = strict("DarkTheme", {
|
|
||||||
BackgroundColor = Color3.fromHex("2E2E2E"),
|
|
||||||
Button = {
|
|
||||||
Solid = {
|
|
||||||
ActionFillColor = Color3.fromHex("FFFFFF"),
|
|
||||||
ActionFillTransparency = 0.8,
|
|
||||||
Enabled = {
|
|
||||||
TextColor = Color3.fromHex("FFFFFF"),
|
|
||||||
BackgroundColor = BRAND_COLOR,
|
|
||||||
},
|
|
||||||
Disabled = {
|
|
||||||
TextColor = Color3.fromHex("FFFFFF"),
|
|
||||||
BackgroundColor = BRAND_COLOR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Bordered = {
|
|
||||||
ActionFillColor = Color3.fromHex("FFFFFF"),
|
|
||||||
ActionFillTransparency = 0.9,
|
|
||||||
Enabled = {
|
|
||||||
TextColor = Color3.fromHex("DBDBDB"),
|
|
||||||
BorderColor = Color3.fromHex("535353"),
|
|
||||||
},
|
|
||||||
Disabled = {
|
|
||||||
TextColor = Color3.fromHex("DBDBDB"),
|
|
||||||
BorderColor = Color3.fromHex("535353"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Checkbox = {
|
|
||||||
Active = {
|
|
||||||
IconColor = Color3.fromHex("FFFFFF"),
|
|
||||||
BackgroundColor = BRAND_COLOR,
|
|
||||||
},
|
|
||||||
Inactive = {
|
|
||||||
IconColor = Color3.fromHex("484848"),
|
|
||||||
BorderColor = Color3.fromHex("5A5A5A"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Dropdown = {
|
|
||||||
TextColor = Color3.fromHex("FFFFFF"),
|
|
||||||
BorderColor = Color3.fromHex("5A5A5A"),
|
|
||||||
BackgroundColor = Color3.fromHex("2B2B2B"),
|
|
||||||
Open = {
|
|
||||||
IconColor = BRAND_COLOR,
|
|
||||||
},
|
|
||||||
Closed = {
|
|
||||||
IconColor = Color3.fromHex("484848"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TextInput = {
|
|
||||||
Enabled = {
|
|
||||||
TextColor = Color3.fromHex("FFFFFF"),
|
|
||||||
PlaceholderColor = Color3.fromHex("8B8B8B"),
|
|
||||||
BorderColor = Color3.fromHex("535353"),
|
|
||||||
},
|
|
||||||
Disabled = {
|
|
||||||
TextColor = Color3.fromHex("484848"),
|
|
||||||
PlaceholderColor = Color3.fromHex("8B8B8B"),
|
|
||||||
BorderColor = Color3.fromHex("5A5A5A"),
|
|
||||||
},
|
|
||||||
ActionFillColor = Color3.fromHex("FFFFFF"),
|
|
||||||
ActionFillTransparency = 0.9,
|
|
||||||
},
|
|
||||||
AddressEntry = {
|
|
||||||
TextColor = Color3.fromHex("FFFFFF"),
|
|
||||||
PlaceholderColor = Color3.fromHex("8B8B8B"),
|
|
||||||
},
|
|
||||||
BorderedContainer = {
|
|
||||||
BorderColor = Color3.fromHex("535353"),
|
|
||||||
BackgroundColor = Color3.fromHex("2B2B2B"),
|
|
||||||
},
|
|
||||||
Spinner = {
|
|
||||||
ForegroundColor = BRAND_COLOR,
|
|
||||||
BackgroundColor = Color3.fromHex("2B2B2B"),
|
|
||||||
},
|
|
||||||
Diff = {
|
|
||||||
Add = Color3.fromHex("273732"),
|
|
||||||
Remove = Color3.fromHex("3F2D32"),
|
|
||||||
Edit = Color3.fromHex("193345"),
|
|
||||||
Row = Color3.fromHex("FFFFFF"),
|
|
||||||
Warning = Color3.fromHex("FF8E3C"),
|
|
||||||
},
|
|
||||||
ConnectionDetails = {
|
|
||||||
ProjectNameColor = Color3.fromHex("FFFFFF"),
|
|
||||||
AddressColor = Color3.fromHex("FFFFFF"),
|
|
||||||
DisconnectColor = Color3.fromHex("FFFFFF"),
|
|
||||||
},
|
|
||||||
Settings = {
|
|
||||||
DividerColor = Color3.fromHex("535353"),
|
|
||||||
Navbar = {
|
|
||||||
BackButtonColor = Color3.fromHex("FFFFFF"),
|
|
||||||
TextColor = Color3.fromHex("FFFFFF"),
|
|
||||||
},
|
|
||||||
Setting = {
|
|
||||||
NameColor = Color3.fromHex("FFFFFF"),
|
|
||||||
DescriptionColor = Color3.fromHex("D3D3D3"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Header = {
|
|
||||||
LogoColor = BRAND_COLOR,
|
|
||||||
VersionColor = Color3.fromHex("D3D3D3"),
|
|
||||||
},
|
|
||||||
Notification = {
|
|
||||||
InfoColor = Color3.fromHex("FFFFFF"),
|
|
||||||
CloseColor = Color3.fromHex("FFFFFF"),
|
|
||||||
},
|
|
||||||
ErrorColor = Color3.fromHex("FFFFFF"),
|
|
||||||
ScrollBarColor = Color3.fromHex("FFFFFF"),
|
|
||||||
})
|
|
||||||
|
|
||||||
local Context = Roact.createContext(lightTheme)
|
|
||||||
|
|
||||||
local StudioProvider = Roact.Component:extend("StudioProvider")
|
local StudioProvider = Roact.Component:extend("StudioProvider")
|
||||||
|
|
||||||
@@ -252,22 +32,161 @@ local StudioProvider = Roact.Component:extend("StudioProvider")
|
|||||||
function StudioProvider:updateTheme()
|
function StudioProvider:updateTheme()
|
||||||
local studioTheme = getStudio().Theme
|
local studioTheme = getStudio().Theme
|
||||||
|
|
||||||
if studioTheme.Name == "Light" then
|
local isDark = studioTheme.Name == "Dark"
|
||||||
self:setState({
|
|
||||||
theme = lightTheme,
|
local theme = strict(studioTheme.Name .. "Theme", {
|
||||||
|
BrandColor = BRAND_COLOR,
|
||||||
|
BackgroundColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainBackground),
|
||||||
|
TextColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainText),
|
||||||
|
SubTextColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.SubText),
|
||||||
|
Button = {
|
||||||
|
Solid = {
|
||||||
|
-- Solid uses brand theming, not Studio theming.
|
||||||
|
ActionFillColor = Color3.fromHex("FFFFFF"),
|
||||||
|
ActionFillTransparency = 0.8,
|
||||||
|
Enabled = {
|
||||||
|
TextColor = Color3.fromHex("FFFFFF"),
|
||||||
|
BackgroundColor = BRAND_COLOR,
|
||||||
|
},
|
||||||
|
Disabled = {
|
||||||
|
TextColor = Color3.fromHex("FFFFFF"),
|
||||||
|
BackgroundColor = BRAND_COLOR,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bordered = {
|
||||||
|
ActionFillColor = studioTheme:GetColor(
|
||||||
|
Enum.StudioStyleGuideColor.ButtonText,
|
||||||
|
Enum.StudioStyleGuideModifier.Selected
|
||||||
|
),
|
||||||
|
ActionFillTransparency = 0.9,
|
||||||
|
Enabled = {
|
||||||
|
TextColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.ButtonText),
|
||||||
|
BorderColor = studioTheme:GetColor(
|
||||||
|
Enum.StudioStyleGuideColor.CheckedFieldBorder,
|
||||||
|
Enum.StudioStyleGuideModifier.Disabled
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Disabled = {
|
||||||
|
TextColor = studioTheme:GetColor(
|
||||||
|
Enum.StudioStyleGuideColor.ButtonText,
|
||||||
|
Enum.StudioStyleGuideModifier.Disabled
|
||||||
|
),
|
||||||
|
BorderColor = studioTheme:GetColor(
|
||||||
|
Enum.StudioStyleGuideColor.CheckedFieldBorder,
|
||||||
|
Enum.StudioStyleGuideModifier.Disabled
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Checkbox = {
|
||||||
|
Active = {
|
||||||
|
-- Active checkboxes use brand theming, not Studio theming.
|
||||||
|
IconColor = Color3.fromHex("FFFFFF"),
|
||||||
|
BackgroundColor = BRAND_COLOR,
|
||||||
|
},
|
||||||
|
Inactive = {
|
||||||
|
IconColor = studioTheme:GetColor(
|
||||||
|
Enum.StudioStyleGuideColor.CheckedFieldIndicator,
|
||||||
|
Enum.StudioStyleGuideModifier.Disabled
|
||||||
|
),
|
||||||
|
BorderColor = studioTheme:GetColor(
|
||||||
|
Enum.StudioStyleGuideColor.CheckedFieldBorder,
|
||||||
|
Enum.StudioStyleGuideModifier.Disabled
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Dropdown = {
|
||||||
|
TextColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.ButtonText),
|
||||||
|
BorderColor = studioTheme:GetColor(
|
||||||
|
Enum.StudioStyleGuideColor.CheckedFieldBorder,
|
||||||
|
Enum.StudioStyleGuideModifier.Disabled
|
||||||
|
),
|
||||||
|
BackgroundColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainBackground),
|
||||||
|
IconColor = studioTheme:GetColor(
|
||||||
|
Enum.StudioStyleGuideColor.CheckedFieldIndicator,
|
||||||
|
Enum.StudioStyleGuideModifier.Disabled
|
||||||
|
),
|
||||||
|
},
|
||||||
|
TextInput = {
|
||||||
|
Enabled = {
|
||||||
|
TextColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
|
PlaceholderColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.SubText),
|
||||||
|
BorderColor = studioTheme:GetColor(
|
||||||
|
Enum.StudioStyleGuideColor.CheckedFieldBorder,
|
||||||
|
Enum.StudioStyleGuideModifier.Disabled
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Disabled = {
|
||||||
|
TextColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainText),
|
||||||
|
PlaceholderColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.SubText),
|
||||||
|
BorderColor = studioTheme:GetColor(
|
||||||
|
Enum.StudioStyleGuideColor.CheckedFieldBorder,
|
||||||
|
Enum.StudioStyleGuideModifier.Disabled
|
||||||
|
),
|
||||||
|
},
|
||||||
|
ActionFillColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
|
ActionFillTransparency = 0.9,
|
||||||
|
},
|
||||||
|
AddressEntry = {
|
||||||
|
TextColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
|
PlaceholderColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.SubText),
|
||||||
|
},
|
||||||
|
BorderedContainer = {
|
||||||
|
BorderColor = studioTheme:GetColor(
|
||||||
|
Enum.StudioStyleGuideColor.CheckedFieldBorder,
|
||||||
|
Enum.StudioStyleGuideModifier.Disabled
|
||||||
|
),
|
||||||
|
BackgroundColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.InputFieldBackground),
|
||||||
|
},
|
||||||
|
Spinner = {
|
||||||
|
ForegroundColor = BRAND_COLOR,
|
||||||
|
BackgroundColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.InputFieldBackground),
|
||||||
|
},
|
||||||
|
Diff = {
|
||||||
|
-- Studio doesn't have good colors since their diffs use backgrounds, not text
|
||||||
|
Add = if isDark then Color3.fromRGB(143, 227, 154) else Color3.fromRGB(41, 164, 45),
|
||||||
|
Remove = if isDark then Color3.fromRGB(242, 125, 125) else Color3.fromRGB(150, 29, 29),
|
||||||
|
Edit = if isDark then Color3.fromRGB(120, 154, 248) else Color3.fromRGB(0, 70, 160),
|
||||||
|
Row = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
|
Warning = studioTheme:GetColor(Enum.StudioStyleGuideColor.WarningText),
|
||||||
|
},
|
||||||
|
ConnectionDetails = {
|
||||||
|
ProjectNameColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
|
AddressColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
|
DisconnectColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
|
},
|
||||||
|
Settings = {
|
||||||
|
DividerColor = studioTheme:GetColor(
|
||||||
|
Enum.StudioStyleGuideColor.CheckedFieldBorder,
|
||||||
|
Enum.StudioStyleGuideModifier.Disabled
|
||||||
|
),
|
||||||
|
Navbar = {
|
||||||
|
BackButtonColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
|
TextColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
|
},
|
||||||
|
Setting = {
|
||||||
|
NameColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
|
DescriptionColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainText),
|
||||||
|
UnstableColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.WarningText),
|
||||||
|
DebugColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.InfoText),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Header = {
|
||||||
|
LogoColor = BRAND_COLOR,
|
||||||
|
VersionColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainText),
|
||||||
|
},
|
||||||
|
Notification = {
|
||||||
|
InfoColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
|
CloseColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
|
},
|
||||||
|
ErrorColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
|
ScrollBarColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.BrightText),
|
||||||
})
|
})
|
||||||
elseif studioTheme.Name == "Dark" then
|
|
||||||
self:setState({
|
|
||||||
theme = darkTheme,
|
|
||||||
})
|
|
||||||
else
|
|
||||||
Log.warn("Unexpected theme '{}'' -- falling back to light theme!", studioTheme.Name)
|
|
||||||
|
|
||||||
self:setState({
|
self:setState({
|
||||||
theme = lightTheme,
|
theme = theme,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
function StudioProvider:init()
|
function StudioProvider:init()
|
||||||
self:updateTheme()
|
self:updateTheme()
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ local PatchTree = require(Plugin.PatchTree)
|
|||||||
local preloadAssets = require(Plugin.preloadAssets)
|
local preloadAssets = require(Plugin.preloadAssets)
|
||||||
local soundPlayer = require(Plugin.soundPlayer)
|
local soundPlayer = require(Plugin.soundPlayer)
|
||||||
local ignorePlaceIds = require(Plugin.ignorePlaceIds)
|
local ignorePlaceIds = require(Plugin.ignorePlaceIds)
|
||||||
|
local timeUtil = require(Plugin.timeUtil)
|
||||||
local Theme = require(script.Theme)
|
local Theme = require(script.Theme)
|
||||||
|
|
||||||
local Page = require(script.Page)
|
local Page = require(script.Page)
|
||||||
@@ -118,6 +119,13 @@ function App:init()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
self.disconnectUpdatesCheckChanged = Settings:onChanged("checkForUpdates", function()
|
||||||
|
self:checkForUpdates()
|
||||||
|
end)
|
||||||
|
self.disconnectPrereleasesCheckChanged = Settings:onChanged("checkForPrereleases", function()
|
||||||
|
self:checkForUpdates()
|
||||||
|
end)
|
||||||
|
|
||||||
self:setState({
|
self:setState({
|
||||||
appStatus = AppStatus.NotConnected,
|
appStatus = AppStatus.NotConnected,
|
||||||
guiEnabled = false,
|
guiEnabled = false,
|
||||||
@@ -131,10 +139,12 @@ function App:init()
|
|||||||
toolbarIcon = Assets.Images.PluginButton,
|
toolbarIcon = Assets.Images.PluginButton,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if RunService:IsEdit() then
|
||||||
|
self:checkForUpdates()
|
||||||
|
|
||||||
if
|
if
|
||||||
RunService:IsEdit()
|
Settings:get("syncReminder")
|
||||||
and self.serveSession == nil
|
and self.serveSession == nil
|
||||||
and Settings:get("syncReminder")
|
|
||||||
and self:getLastSyncTimestamp()
|
and self:getLastSyncTimestamp()
|
||||||
and (self:isSyncLockAvailable())
|
and (self:isSyncLockAvailable())
|
||||||
then
|
then
|
||||||
@@ -160,9 +170,32 @@ function App:init()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if self:isAutoConnectPlaytestServerAvailable() then
|
||||||
|
self:useRunningConnectionInfo()
|
||||||
|
self:startSession()
|
||||||
|
end
|
||||||
|
self.autoConnectPlaytestServerListener = Settings:onChanged("autoConnectPlaytestServer", function(enabled)
|
||||||
|
if enabled then
|
||||||
|
if self:isAutoConnectPlaytestServerWriteable() and self.serveSession ~= nil then
|
||||||
|
-- Write the existing session
|
||||||
|
local baseUrl = self.serveSession.__apiContext.__baseUrl
|
||||||
|
self:setRunningConnectionInfo(baseUrl)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self:clearRunningConnectionInfo()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
function App:willUnmount()
|
function App:willUnmount()
|
||||||
self.waypointConnection:Disconnect()
|
self.waypointConnection:Disconnect()
|
||||||
self.confirmationBindable:Destroy()
|
self.confirmationBindable:Destroy()
|
||||||
|
|
||||||
|
self.disconnectUpdatesCheckChanged()
|
||||||
|
self.disconnectPrereleasesCheckChanged()
|
||||||
|
|
||||||
|
self.autoConnectPlaytestServerListener()
|
||||||
|
self:clearRunningConnectionInfo()
|
||||||
end
|
end
|
||||||
|
|
||||||
function App:addNotification(
|
function App:addNotification(
|
||||||
@@ -207,6 +240,40 @@ function App:closeNotification(id: number)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function App:checkForUpdates()
|
||||||
|
if not Settings:get("checkForUpdates") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local isLocalInstall = string.find(debug.traceback(), "\n[^\n]-user_.-$") ~= nil
|
||||||
|
local latestCompatibleVersion = Version.retrieveLatestCompatible({
|
||||||
|
version = Config.version,
|
||||||
|
includePrereleases = isLocalInstall and Settings:get("checkForPrereleases"),
|
||||||
|
})
|
||||||
|
if not latestCompatibleVersion then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
self:addNotification(
|
||||||
|
string.format(
|
||||||
|
"A newer compatible version of Rojo, %s, was published %s! Go to the Rojo releases page to learn more.",
|
||||||
|
Version.display(latestCompatibleVersion.version),
|
||||||
|
timeUtil.elapsedToText(DateTime.now().UnixTimestamp - latestCompatibleVersion.publishedUnixTimestamp)
|
||||||
|
),
|
||||||
|
500,
|
||||||
|
{
|
||||||
|
Dismiss = {
|
||||||
|
text = "Dismiss",
|
||||||
|
style = "Bordered",
|
||||||
|
layoutOrder = 2,
|
||||||
|
onClick = function(notification)
|
||||||
|
notification:dismiss()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
function App:getPriorEndpoint()
|
function App:getPriorEndpoint()
|
||||||
local priorEndpoints = Settings:get("priorEndpoints")
|
local priorEndpoints = Settings:get("priorEndpoints")
|
||||||
if not priorEndpoints then
|
if not priorEndpoints then
|
||||||
@@ -278,10 +345,7 @@ function App:getHostAndPort()
|
|||||||
local host = self.host:getValue()
|
local host = self.host:getValue()
|
||||||
local port = self.port:getValue()
|
local port = self.port:getValue()
|
||||||
|
|
||||||
local host = if #host > 0 then host else Config.defaultHost
|
return if #host > 0 then host else Config.defaultHost, if #port > 0 then port else Config.defaultPort
|
||||||
local port = if #port > 0 then port else Config.defaultPort
|
|
||||||
|
|
||||||
return host, port
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function App:isSyncLockAvailable()
|
function App:isSyncLockAvailable()
|
||||||
@@ -349,6 +413,49 @@ function App:releaseSyncLock()
|
|||||||
Log.trace("Could not relase sync lock because it is owned by {}", lock.Value)
|
Log.trace("Could not relase sync lock because it is owned by {}", lock.Value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function App:isAutoConnectPlaytestServerAvailable()
|
||||||
|
return RunService:IsRunMode()
|
||||||
|
and RunService:IsServer()
|
||||||
|
and Settings:get("autoConnectPlaytestServer")
|
||||||
|
and workspace:GetAttribute("__Rojo_ConnectionUrl")
|
||||||
|
end
|
||||||
|
|
||||||
|
function App:isAutoConnectPlaytestServerWriteable()
|
||||||
|
return RunService:IsEdit() and Settings:get("autoConnectPlaytestServer")
|
||||||
|
end
|
||||||
|
|
||||||
|
function App:setRunningConnectionInfo(baseUrl: string)
|
||||||
|
if not self:isAutoConnectPlaytestServerWriteable() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Log.trace("Setting connection info for play solo auto-connect")
|
||||||
|
workspace:SetAttribute("__Rojo_ConnectionUrl", baseUrl)
|
||||||
|
end
|
||||||
|
|
||||||
|
function App:clearRunningConnectionInfo()
|
||||||
|
if not RunService:IsEdit() then
|
||||||
|
-- Only write connection info from edit mode
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Log.trace("Clearing connection info for play solo auto-connect")
|
||||||
|
workspace:SetAttribute("__Rojo_ConnectionUrl", nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function App:useRunningConnectionInfo()
|
||||||
|
local connectionInfo = workspace:GetAttribute("__Rojo_ConnectionUrl")
|
||||||
|
if not connectionInfo then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Log.trace("Using connection info for play solo auto-connect")
|
||||||
|
local host, port = string.match(connectionInfo, "^(.+):(.-)$")
|
||||||
|
|
||||||
|
self.setHost(host)
|
||||||
|
self.setPort(port)
|
||||||
|
end
|
||||||
|
|
||||||
function App:startSession()
|
function App:startSession()
|
||||||
local claimedLock, priorOwner = self:claimSyncLock()
|
local claimedLock, priorOwner = self:claimSyncLock()
|
||||||
if not claimedLock then
|
if not claimedLock then
|
||||||
@@ -367,11 +474,6 @@ function App:startSession()
|
|||||||
|
|
||||||
local host, port = self:getHostAndPort()
|
local host, port = self:getHostAndPort()
|
||||||
|
|
||||||
local sessionOptions = {
|
|
||||||
openScriptsExternally = Settings:get("openScriptsExternally"),
|
|
||||||
twoWaySync = Settings:get("twoWaySync"),
|
|
||||||
}
|
|
||||||
|
|
||||||
local baseUrl = if string.find(host, "^https?://")
|
local baseUrl = if string.find(host, "^https?://")
|
||||||
then string.format("%s:%s", host, port)
|
then string.format("%s:%s", host, port)
|
||||||
else string.format("http://%s:%s", host, port)
|
else string.format("http://%s:%s", host, port)
|
||||||
@@ -379,8 +481,7 @@ function App:startSession()
|
|||||||
|
|
||||||
local serveSession = ServeSession.new({
|
local serveSession = ServeSession.new({
|
||||||
apiContext = apiContext,
|
apiContext = apiContext,
|
||||||
openScriptsExternally = sessionOptions.openScriptsExternally,
|
twoWaySync = Settings:get("twoWaySync"),
|
||||||
twoWaySync = sessionOptions.twoWaySync,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
self.cleanupPrecommit = serveSession.__reconciler:hookPrecommit(function(patch, instanceMap)
|
self.cleanupPrecommit = serveSession.__reconciler:hookPrecommit(function(patch, instanceMap)
|
||||||
@@ -399,7 +500,7 @@ function App:startSession()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
serveSession:hookPostcommit(function(patch, _instanceMap, unapplied)
|
serveSession:hookPostcommit(function(patch, _instanceMap, unapplied)
|
||||||
local now = os.time()
|
local now = DateTime.now().UnixTimestamp
|
||||||
local old = self.state.patchData
|
local old = self.state.patchData
|
||||||
|
|
||||||
if PatchSet.isEmpty(patch) then
|
if PatchSet.isEmpty(patch) then
|
||||||
@@ -441,6 +542,7 @@ function App:startSession()
|
|||||||
self:addNotification("Connecting to session...")
|
self:addNotification("Connecting to session...")
|
||||||
elseif status == ServeSession.Status.Connected then
|
elseif status == ServeSession.Status.Connected then
|
||||||
self.knownProjects[details] = true
|
self.knownProjects[details] = true
|
||||||
|
self:setRunningConnectionInfo(baseUrl)
|
||||||
|
|
||||||
local address = ("%s:%s"):format(host, port)
|
local address = ("%s:%s"):format(host, port)
|
||||||
self:setState({
|
self:setState({
|
||||||
@@ -453,6 +555,7 @@ function App:startSession()
|
|||||||
elseif status == ServeSession.Status.Disconnected then
|
elseif status == ServeSession.Status.Disconnected then
|
||||||
self.serveSession = nil
|
self.serveSession = nil
|
||||||
self:releaseSyncLock()
|
self:releaseSyncLock()
|
||||||
|
self:clearRunningConnectionInfo()
|
||||||
self:setState({
|
self:setState({
|
||||||
patchData = {
|
patchData = {
|
||||||
patch = PatchSet.newEmpty(),
|
patch = PatchSet.newEmpty(),
|
||||||
@@ -488,6 +591,12 @@ function App:startSession()
|
|||||||
return "Accept"
|
return "Accept"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Play solo auto-connect does not require confirmation
|
||||||
|
if self:isAutoConnectPlaytestServerAvailable() then
|
||||||
|
Log.trace("Accepting patch without confirmation because play solo auto-connect is enabled")
|
||||||
|
return "Accept"
|
||||||
|
end
|
||||||
|
|
||||||
local confirmationBehavior = Settings:get("confirmationBehavior")
|
local confirmationBehavior = Settings:get("confirmationBehavior")
|
||||||
if confirmationBehavior == "Initial" then
|
if confirmationBehavior == "Initial" then
|
||||||
-- Only confirm if we haven't synced this project yet this session
|
-- Only confirm if we haven't synced this project yet this session
|
||||||
@@ -606,7 +715,7 @@ function App:render()
|
|||||||
value = self.props.plugin,
|
value = self.props.plugin,
|
||||||
}, {
|
}, {
|
||||||
e(Theme.StudioProvider, nil, {
|
e(Theme.StudioProvider, nil, {
|
||||||
e(Tooltip.Provider, nil, {
|
tooltip = e(Tooltip.Provider, nil, {
|
||||||
gui = e(StudioPluginGui, {
|
gui = e(StudioPluginGui, {
|
||||||
id = pluginName,
|
id = pluginName,
|
||||||
title = pluginName,
|
title = pluginName,
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ local Assets = {
|
|||||||
Back = "rbxassetid://6017213752",
|
Back = "rbxassetid://6017213752",
|
||||||
Reset = "rbxassetid://10142422327",
|
Reset = "rbxassetid://10142422327",
|
||||||
Expand = "rbxassetid://12045401097",
|
Expand = "rbxassetid://12045401097",
|
||||||
|
Warning = "rbxassetid://16571019891",
|
||||||
|
Debug = "rbxassetid://16588411361",
|
||||||
|
Checkmark = "rbxassetid://16571012729",
|
||||||
|
Exclamation = "rbxassetid://16571172190",
|
||||||
|
SyncSuccess = "rbxassetid://16565035221",
|
||||||
|
SyncWarning = "rbxassetid://16565325171",
|
||||||
},
|
},
|
||||||
Diff = {
|
Diff = {
|
||||||
Add = "rbxassetid://10434145835",
|
Add = "rbxassetid://10434145835",
|
||||||
|
|||||||
@@ -112,9 +112,12 @@ end
|
|||||||
|
|
||||||
function InstanceMap:destroyInstance(instance)
|
function InstanceMap:destroyInstance(instance)
|
||||||
local id = self.fromInstances[instance]
|
local id = self.fromInstances[instance]
|
||||||
|
|
||||||
local descendants = instance:GetDescendants()
|
local descendants = instance:GetDescendants()
|
||||||
instance:Destroy()
|
|
||||||
|
-- Because the user might want to Undo this change, we cannot use Destroy
|
||||||
|
-- since that locks that parent and prevents ChangeHistoryService from
|
||||||
|
-- ever bringing it back. Instead, we parent to nil.
|
||||||
|
instance.Parent = nil
|
||||||
|
|
||||||
-- After the instance is successfully destroyed,
|
-- After the instance is successfully destroyed,
|
||||||
-- we can remove all the id mappings
|
-- we can remove all the id mappings
|
||||||
|
|||||||
@@ -211,10 +211,12 @@ end
|
|||||||
function PatchSet.countChanges(patch)
|
function PatchSet.countChanges(patch)
|
||||||
local count = 0
|
local count = 0
|
||||||
|
|
||||||
for _ in patch.added do
|
for _, add in patch.added do
|
||||||
-- Adding an instance is 1 change
|
-- Adding an instance is 1 change per property
|
||||||
|
for _ in add.Properties do
|
||||||
count += 1
|
count += 1
|
||||||
end
|
end
|
||||||
|
end
|
||||||
for _ in patch.removed do
|
for _ in patch.removed do
|
||||||
-- Removing an instance is 1 change
|
-- Removing an instance is 1 change
|
||||||
count += 1
|
count += 1
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ local Packages = Rojo.Packages
|
|||||||
|
|
||||||
local Log = require(Packages.Log)
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
|
local Timer = require(Plugin.Timer)
|
||||||
local Types = require(Plugin.Types)
|
local Types = require(Plugin.Types)
|
||||||
local decodeValue = require(Plugin.Reconciler.decodeValue)
|
local decodeValue = require(Plugin.Reconciler.decodeValue)
|
||||||
local getProperty = require(Plugin.Reconciler.getProperty)
|
local getProperty = require(Plugin.Reconciler.getProperty)
|
||||||
@@ -78,6 +79,15 @@ function Tree.new()
|
|||||||
return setmetatable(tree, Tree)
|
return setmetatable(tree, Tree)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Iterates over all nodes and counts them up
|
||||||
|
function Tree:getCount()
|
||||||
|
local count = 0
|
||||||
|
self:forEach(function()
|
||||||
|
count += 1
|
||||||
|
end)
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
-- Iterates over all sub-nodes, depth first
|
-- Iterates over all sub-nodes, depth first
|
||||||
-- node is where to start from, defaults to root
|
-- node is where to start from, defaults to root
|
||||||
-- depth is used for recursion but can be used to set the starting depth
|
-- depth is used for recursion but can be used to set the starting depth
|
||||||
@@ -122,6 +132,7 @@ end
|
|||||||
-- props must contain id, and cannot contain children or parentId
|
-- props must contain id, and cannot contain children or parentId
|
||||||
-- other than those three, it can hold anything
|
-- other than those three, it can hold anything
|
||||||
function Tree:addNode(parent, props)
|
function Tree:addNode(parent, props)
|
||||||
|
Timer.start("Tree:addNode")
|
||||||
assert(props.id, "props must contain id")
|
assert(props.id, "props must contain id")
|
||||||
|
|
||||||
parent = parent or "ROOT"
|
parent = parent or "ROOT"
|
||||||
@@ -132,6 +143,7 @@ function Tree:addNode(parent, props)
|
|||||||
for k, v in props do
|
for k, v in props do
|
||||||
node[k] = v
|
node[k] = v
|
||||||
end
|
end
|
||||||
|
Timer.stop()
|
||||||
return node
|
return node
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -142,18 +154,21 @@ function Tree:addNode(parent, props)
|
|||||||
local parentNode = self:getNode(parent)
|
local parentNode = self:getNode(parent)
|
||||||
if not parentNode then
|
if not parentNode then
|
||||||
Log.warn("Failed to create node since parent doesnt exist: {}, {}", parent, props)
|
Log.warn("Failed to create node since parent doesnt exist: {}, {}", parent, props)
|
||||||
|
Timer.stop()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
parentNode.children[node.id] = node
|
parentNode.children[node.id] = node
|
||||||
self.idToNode[node.id] = node
|
self.idToNode[node.id] = node
|
||||||
|
|
||||||
|
Timer.stop()
|
||||||
return node
|
return node
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Given a list of ancestor ids in descending order, builds the nodes for them
|
-- Given a list of ancestor ids in descending order, builds the nodes for them
|
||||||
-- using the patch and instanceMap info
|
-- using the patch and instanceMap info
|
||||||
function Tree:buildAncestryNodes(previousId: string?, ancestryIds: { string }, patch, instanceMap)
|
function Tree:buildAncestryNodes(previousId: string?, ancestryIds: { string }, patch, instanceMap)
|
||||||
|
Timer.start("Tree:buildAncestryNodes")
|
||||||
-- Build nodes for ancestry by going up the tree
|
-- Build nodes for ancestry by going up the tree
|
||||||
previousId = previousId or "ROOT"
|
previousId = previousId or "ROOT"
|
||||||
|
|
||||||
@@ -171,6 +186,8 @@ function Tree:buildAncestryNodes(previousId: string?, ancestryIds: { string }, p
|
|||||||
})
|
})
|
||||||
previousId = ancestorId
|
previousId = ancestorId
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Timer.stop()
|
||||||
end
|
end
|
||||||
|
|
||||||
local PatchTree = {}
|
local PatchTree = {}
|
||||||
@@ -178,10 +195,12 @@ local PatchTree = {}
|
|||||||
-- Builds a new tree from a patch and instanceMap
|
-- Builds a new tree from a patch and instanceMap
|
||||||
-- uses changeListHeaders in node.changeList
|
-- uses changeListHeaders in node.changeList
|
||||||
function PatchTree.build(patch, instanceMap, changeListHeaders)
|
function PatchTree.build(patch, instanceMap, changeListHeaders)
|
||||||
|
Timer.start("PatchTree.build")
|
||||||
local tree = Tree.new()
|
local tree = Tree.new()
|
||||||
|
|
||||||
local knownAncestors = {}
|
local knownAncestors = {}
|
||||||
|
|
||||||
|
Timer.start("patch.updated")
|
||||||
for _, change in patch.updated do
|
for _, change in patch.updated do
|
||||||
local instance = instanceMap.fromIds[change.id]
|
local instance = instanceMap.fromIds[change.id]
|
||||||
if not instance then
|
if not instance then
|
||||||
@@ -209,15 +228,14 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
|
|||||||
tree:buildAncestryNodes(previousId, ancestryIds, patch, instanceMap)
|
tree:buildAncestryNodes(previousId, ancestryIds, patch, instanceMap)
|
||||||
|
|
||||||
-- Gather detail text
|
-- Gather detail text
|
||||||
local changeList, hint = nil, nil
|
local changeList, changeInfo = nil, nil
|
||||||
if next(change.changedProperties) or change.changedName then
|
if next(change.changedProperties) or change.changedName then
|
||||||
changeList = {}
|
changeList = {}
|
||||||
|
|
||||||
local hintBuffer, i = {}, 0
|
local changeIndex = 0
|
||||||
local function addProp(prop: string, current: any?, incoming: any?, metadata: any?)
|
local function addProp(prop: string, current: any?, incoming: any?, metadata: any?)
|
||||||
i += 1
|
changeIndex += 1
|
||||||
hintBuffer[i] = prop
|
changeList[changeIndex] = { prop, current, incoming, metadata }
|
||||||
changeList[i] = { prop, current, incoming, metadata }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Gather the changes
|
-- Gather the changes
|
||||||
@@ -237,19 +255,9 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Finalize detail values
|
changeInfo = {
|
||||||
|
edits = changeIndex,
|
||||||
-- Trim hint to top 3
|
|
||||||
table.sort(hintBuffer)
|
|
||||||
if #hintBuffer > 3 then
|
|
||||||
hintBuffer = {
|
|
||||||
hintBuffer[1],
|
|
||||||
hintBuffer[2],
|
|
||||||
hintBuffer[3],
|
|
||||||
i - 3 .. " more",
|
|
||||||
}
|
}
|
||||||
end
|
|
||||||
hint = table.concat(hintBuffer, ", ")
|
|
||||||
|
|
||||||
-- Sort changes and add header
|
-- Sort changes and add header
|
||||||
table.sort(changeList, function(a, b)
|
table.sort(changeList, function(a, b)
|
||||||
@@ -265,11 +273,13 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
|
|||||||
className = instance.ClassName,
|
className = instance.ClassName,
|
||||||
name = instance.Name,
|
name = instance.Name,
|
||||||
instance = instance,
|
instance = instance,
|
||||||
hint = hint,
|
changeInfo = changeInfo,
|
||||||
changeList = changeList,
|
changeList = changeList,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
Timer.stop()
|
||||||
|
|
||||||
|
Timer.start("patch.removed")
|
||||||
for _, idOrInstance in patch.removed do
|
for _, idOrInstance in patch.removed do
|
||||||
local instance = if Types.RbxId(idOrInstance) then instanceMap.fromIds[idOrInstance] else idOrInstance
|
local instance = if Types.RbxId(idOrInstance) then instanceMap.fromIds[idOrInstance] else idOrInstance
|
||||||
if not instance then
|
if not instance then
|
||||||
@@ -311,7 +321,9 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
|
|||||||
instance = instance,
|
instance = instance,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
Timer.stop()
|
||||||
|
|
||||||
|
Timer.start("patch.added")
|
||||||
for id, change in patch.added do
|
for id, change in patch.added do
|
||||||
-- Gather ancestors from existing DOM or future additions
|
-- Gather ancestors from existing DOM or future additions
|
||||||
local ancestryIds = {}
|
local ancestryIds = {}
|
||||||
@@ -346,36 +358,24 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
|
|||||||
tree:buildAncestryNodes(previousId, ancestryIds, patch, instanceMap)
|
tree:buildAncestryNodes(previousId, ancestryIds, patch, instanceMap)
|
||||||
|
|
||||||
-- Gather detail text
|
-- Gather detail text
|
||||||
local changeList, hint = nil, nil
|
local changeList, changeInfo = nil, nil
|
||||||
if next(change.Properties) then
|
if next(change.Properties) then
|
||||||
changeList = {}
|
changeList = {}
|
||||||
|
|
||||||
local hintBuffer, i = {}, 0
|
local changeIndex = 0
|
||||||
|
local function addProp(prop: string, incoming: any)
|
||||||
|
changeIndex += 1
|
||||||
|
changeList[changeIndex] = { prop, "N/A", incoming }
|
||||||
|
end
|
||||||
|
|
||||||
for prop, incoming in change.Properties do
|
for prop, incoming in change.Properties do
|
||||||
i += 1
|
|
||||||
hintBuffer[i] = prop
|
|
||||||
|
|
||||||
local success, incomingValue = decodeValue(incoming, instanceMap)
|
local success, incomingValue = decodeValue(incoming, instanceMap)
|
||||||
if success then
|
addProp(prop, if success then incomingValue else select(2, next(incoming)))
|
||||||
table.insert(changeList, { prop, "N/A", incomingValue })
|
|
||||||
else
|
|
||||||
table.insert(changeList, { prop, "N/A", select(2, next(incoming)) })
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Finalize detail values
|
changeInfo = {
|
||||||
|
edits = changeIndex,
|
||||||
-- Trim hint to top 3
|
|
||||||
table.sort(hintBuffer)
|
|
||||||
if #hintBuffer > 3 then
|
|
||||||
hintBuffer = {
|
|
||||||
hintBuffer[1],
|
|
||||||
hintBuffer[2],
|
|
||||||
hintBuffer[3],
|
|
||||||
i - 3 .. " more",
|
|
||||||
}
|
}
|
||||||
end
|
|
||||||
hint = table.concat(hintBuffer, ", ")
|
|
||||||
|
|
||||||
-- Sort changes and add header
|
-- Sort changes and add header
|
||||||
table.sort(changeList, function(a, b)
|
table.sort(changeList, function(a, b)
|
||||||
@@ -390,40 +390,32 @@ function PatchTree.build(patch, instanceMap, changeListHeaders)
|
|||||||
patchType = "Add",
|
patchType = "Add",
|
||||||
className = change.ClassName,
|
className = change.ClassName,
|
||||||
name = change.Name,
|
name = change.Name,
|
||||||
hint = hint,
|
changeInfo = changeInfo,
|
||||||
changeList = changeList,
|
changeList = changeList,
|
||||||
instance = instanceMap.fromIds[id],
|
instance = instanceMap.fromIds[id],
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
Timer.stop()
|
||||||
|
|
||||||
|
Timer.stop()
|
||||||
return tree
|
return tree
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Creates a deep copy of a tree for immutability purposes in Roact
|
|
||||||
function PatchTree.clone(tree)
|
|
||||||
if not tree then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local newTree = Tree.new()
|
|
||||||
tree:forEach(function(node)
|
|
||||||
newTree:addNode(node.parentId, table.clone(node))
|
|
||||||
end)
|
|
||||||
|
|
||||||
return newTree
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Updates the metadata of a tree with the unapplied patch and currently existing instances
|
-- Updates the metadata of a tree with the unapplied patch and currently existing instances
|
||||||
-- Builds a new tree from the data if one isn't provided
|
-- Builds a new tree from the data if one isn't provided
|
||||||
-- Always returns a new tree for immutability purposes in Roact
|
-- Always returns a new tree for immutability purposes in Roact
|
||||||
function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
||||||
|
Timer.start("PatchTree.updateMetadata")
|
||||||
if tree then
|
if tree then
|
||||||
tree = PatchTree.clone(tree)
|
-- A shallow copy is enough for our purposes here since we really only need a new top-level object
|
||||||
|
-- for immutable comparison checks in Roact
|
||||||
|
tree = table.clone(tree)
|
||||||
else
|
else
|
||||||
tree = PatchTree.build(patch, instanceMap)
|
tree = PatchTree.build(patch, instanceMap)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Update isWarning metadata
|
-- Update isWarning metadata
|
||||||
|
Timer.start("isWarning")
|
||||||
for _, failedChange in unappliedPatch.updated do
|
for _, failedChange in unappliedPatch.updated do
|
||||||
local node = tree:getNode(failedChange.id)
|
local node = tree:getNode(failedChange.id)
|
||||||
if not node then
|
if not node then
|
||||||
@@ -436,6 +428,8 @@ function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
|||||||
if not node.changeList then
|
if not node.changeList then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local warnings = 0
|
||||||
for _, change in node.changeList do
|
for _, change in node.changeList do
|
||||||
local property = change[1]
|
local property = change[1]
|
||||||
local propertyFailedToApply = if property == "Name"
|
local propertyFailedToApply = if property == "Name"
|
||||||
@@ -446,6 +440,8 @@ function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
|||||||
-- This change didn't fail, no need to mark
|
-- This change didn't fail, no need to mark
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
|
warnings += 1
|
||||||
if change[4] == nil then
|
if change[4] == nil then
|
||||||
change[4] = { isWarning = true }
|
change[4] = { isWarning = true }
|
||||||
else
|
else
|
||||||
@@ -453,6 +449,11 @@ function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
|||||||
end
|
end
|
||||||
Log.trace(" Marked property as warning: {}.{}", node.name, property)
|
Log.trace(" Marked property as warning: {}.{}", node.name, property)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
node.changeInfo = {
|
||||||
|
edits = (node.changeInfo.edits or (#node.changeList - 1)) - warnings,
|
||||||
|
failed = if warnings > 0 then warnings else nil,
|
||||||
|
}
|
||||||
end
|
end
|
||||||
for failedAdditionId in unappliedPatch.added do
|
for failedAdditionId in unappliedPatch.added do
|
||||||
local node = tree:getNode(failedAdditionId)
|
local node = tree:getNode(failedAdditionId)
|
||||||
@@ -466,6 +467,7 @@ function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
|||||||
if not node.changeList then
|
if not node.changeList then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, change in node.changeList do
|
for _, change in node.changeList do
|
||||||
-- Failed addition means that all properties failed to be added
|
-- Failed addition means that all properties failed to be added
|
||||||
if change[4] == nil then
|
if change[4] == nil then
|
||||||
@@ -475,6 +477,10 @@ function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
|||||||
end
|
end
|
||||||
Log.trace(" Marked property as warning: {}.{}", node.name, change[1])
|
Log.trace(" Marked property as warning: {}.{}", node.name, change[1])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
node.changeInfo = {
|
||||||
|
failed = node.changeInfo.edits or (#node.changeList - 1),
|
||||||
|
}
|
||||||
end
|
end
|
||||||
for _, failedRemovalIdOrInstance in unappliedPatch.removed do
|
for _, failedRemovalIdOrInstance in unappliedPatch.removed do
|
||||||
local failedRemovalId = if Types.RbxId(failedRemovalIdOrInstance)
|
local failedRemovalId = if Types.RbxId(failedRemovalIdOrInstance)
|
||||||
@@ -492,8 +498,10 @@ function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
|||||||
node.isWarning = true
|
node.isWarning = true
|
||||||
Log.trace("Marked node as warning: {} {}", node.id, node.name)
|
Log.trace("Marked node as warning: {} {}", node.id, node.name)
|
||||||
end
|
end
|
||||||
|
Timer.stop()
|
||||||
|
|
||||||
-- Update if instances exist
|
-- Update if instances exist
|
||||||
|
Timer.start("instanceAncestry")
|
||||||
tree:forEach(function(node)
|
tree:forEach(function(node)
|
||||||
if node.instance then
|
if node.instance then
|
||||||
if node.instance.Parent == nil and node.instance ~= game then
|
if node.instance.Parent == nil and node.instance ~= game then
|
||||||
@@ -509,7 +517,9 @@ function PatchTree.updateMetadata(tree, patch, instanceMap, unappliedPatch)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
Timer.stop()
|
||||||
|
|
||||||
|
Timer.stop()
|
||||||
return tree
|
return tree
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ local setProperty = require(script.Parent.setProperty)
|
|||||||
|
|
||||||
local function applyPatch(instanceMap, patch)
|
local function applyPatch(instanceMap, patch)
|
||||||
local patchTimestamp = DateTime.now():FormatLocalTime("LTS", "en-us")
|
local patchTimestamp = DateTime.now():FormatLocalTime("LTS", "en-us")
|
||||||
|
local historyRecording = ChangeHistoryService:TryBeginRecording("Rojo: Patch " .. patchTimestamp)
|
||||||
|
if not historyRecording then
|
||||||
|
-- There can only be one recording at a time
|
||||||
|
Log.debug("Failed to begin history recording for " .. patchTimestamp .. ". Another recording is in progress.")
|
||||||
|
end
|
||||||
|
|
||||||
-- Tracks any portions of the patch that could not be applied to the DOM.
|
-- Tracks any portions of the patch that could not be applied to the DOM.
|
||||||
local unappliedPatch = PatchSet.newEmpty()
|
local unappliedPatch = PatchSet.newEmpty()
|
||||||
@@ -62,6 +67,9 @@ local function applyPatch(instanceMap, patch)
|
|||||||
if parentInstance == nil then
|
if parentInstance == nil then
|
||||||
-- This would be peculiar. If you create an instance with no
|
-- This would be peculiar. If you create an instance with no
|
||||||
-- parent, were you supposed to create it at all?
|
-- parent, were you supposed to create it at all?
|
||||||
|
if historyRecording then
|
||||||
|
ChangeHistoryService:FinishRecording(historyRecording, Enum.FinishRecordingOperation.Commit)
|
||||||
|
end
|
||||||
invariant(
|
invariant(
|
||||||
"Cannot add an instance from a patch that has no parent.\nInstance {} with parent {}.\nState: {:#?}",
|
"Cannot add an instance from a patch that has no parent.\nInstance {} with parent {}.\nState: {:#?}",
|
||||||
id,
|
id,
|
||||||
@@ -164,10 +172,14 @@ local function applyPatch(instanceMap, patch)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- See you later, original instance.
|
-- See you later, original instance.
|
||||||
--
|
|
||||||
|
-- Because the user might want to Undo this change, we cannot use Destroy
|
||||||
|
-- since that locks that parent and prevents ChangeHistoryService from
|
||||||
|
-- ever bringing it back. Instead, we parent to nil.
|
||||||
|
|
||||||
-- TODO: Can this fail? Some kinds of instance may not appreciate
|
-- TODO: Can this fail? Some kinds of instance may not appreciate
|
||||||
-- being destroyed, like services.
|
-- being reparented, like services.
|
||||||
instance:Destroy()
|
instance.Parent = nil
|
||||||
|
|
||||||
-- This completes your rebuilding a plane mid-flight safety
|
-- This completes your rebuilding a plane mid-flight safety
|
||||||
-- instruction. Please sit back, relax, and enjoy your flight.
|
-- instruction. Please sit back, relax, and enjoy your flight.
|
||||||
@@ -214,7 +226,9 @@ local function applyPatch(instanceMap, patch)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ChangeHistoryService:SetWaypoint("Rojo: Patch " .. patchTimestamp)
|
if historyRecording then
|
||||||
|
ChangeHistoryService:FinishRecording(historyRecording, Enum.FinishRecordingOperation.Commit)
|
||||||
|
end
|
||||||
|
|
||||||
return unappliedPatch
|
return unappliedPatch
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,25 +4,41 @@ return function()
|
|||||||
local InstanceMap = require(script.Parent.Parent.InstanceMap)
|
local InstanceMap = require(script.Parent.Parent.InstanceMap)
|
||||||
local PatchSet = require(script.Parent.Parent.PatchSet)
|
local PatchSet = require(script.Parent.Parent.PatchSet)
|
||||||
|
|
||||||
local dummy = Instance.new("Folder")
|
local container = Instance.new("Folder")
|
||||||
local function wasDestroyed(instance)
|
|
||||||
|
local tempContainer = Instance.new("Folder")
|
||||||
|
local function wasRemoved(instance)
|
||||||
-- If an instance was destroyed, its parent property is locked.
|
-- If an instance was destroyed, its parent property is locked.
|
||||||
local ok = pcall(function()
|
-- If an instance was removed, its parent property is nil.
|
||||||
|
-- We need to ensure we only remove, so that ChangeHistoryService can still Undo.
|
||||||
|
|
||||||
|
local isParentUnlocked = pcall(function()
|
||||||
local oldParent = instance.Parent
|
local oldParent = instance.Parent
|
||||||
instance.Parent = dummy
|
instance.Parent = tempContainer
|
||||||
instance.Parent = oldParent
|
instance.Parent = oldParent
|
||||||
end)
|
end)
|
||||||
|
|
||||||
return not ok
|
return instance.Parent == nil and isParentUnlocked
|
||||||
end
|
end
|
||||||
|
|
||||||
|
beforeEach(function()
|
||||||
|
container:ClearAllChildren()
|
||||||
|
end)
|
||||||
|
|
||||||
|
afterAll(function()
|
||||||
|
container:Destroy()
|
||||||
|
tempContainer:Destroy()
|
||||||
|
end)
|
||||||
|
|
||||||
it("should return an empty patch if given an empty patch", function()
|
it("should return an empty patch if given an empty patch", function()
|
||||||
local patch = applyPatch(InstanceMap.new(), PatchSet.newEmpty())
|
local patch = applyPatch(InstanceMap.new(), PatchSet.newEmpty())
|
||||||
assert(PatchSet.isEmpty(patch), "expected remaining patch to be empty")
|
assert(PatchSet.isEmpty(patch), "expected remaining patch to be empty")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("should destroy instances listed for remove", function()
|
it("should remove instances listed for remove", function()
|
||||||
local root = Instance.new("Folder")
|
local root = Instance.new("Folder")
|
||||||
|
root.Name = "ROOT"
|
||||||
|
root.Parent = container
|
||||||
|
|
||||||
local child = Instance.new("Folder")
|
local child = Instance.new("Folder")
|
||||||
child.Name = "Child"
|
child.Name = "Child"
|
||||||
@@ -38,14 +54,16 @@ return function()
|
|||||||
local unapplied = applyPatch(instanceMap, patch)
|
local unapplied = applyPatch(instanceMap, patch)
|
||||||
assert(PatchSet.isEmpty(unapplied), "expected remaining patch to be empty")
|
assert(PatchSet.isEmpty(unapplied), "expected remaining patch to be empty")
|
||||||
|
|
||||||
assert(not wasDestroyed(root), "expected root to be left alone")
|
assert(not wasRemoved(root), "expected root to be left alone")
|
||||||
assert(wasDestroyed(child), "expected child to be destroyed")
|
assert(wasRemoved(child), "expected child to be removed")
|
||||||
|
|
||||||
instanceMap:stop()
|
instanceMap:stop()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("should destroy IDs listed for remove", function()
|
it("should remove IDs listed for remove", function()
|
||||||
local root = Instance.new("Folder")
|
local root = Instance.new("Folder")
|
||||||
|
root.Name = "ROOT"
|
||||||
|
root.Parent = container
|
||||||
|
|
||||||
local child = Instance.new("Folder")
|
local child = Instance.new("Folder")
|
||||||
child.Name = "Child"
|
child.Name = "Child"
|
||||||
@@ -62,8 +80,8 @@ return function()
|
|||||||
assert(PatchSet.isEmpty(unapplied), "expected remaining patch to be empty")
|
assert(PatchSet.isEmpty(unapplied), "expected remaining patch to be empty")
|
||||||
expect(instanceMap:size()).to.equal(1)
|
expect(instanceMap:size()).to.equal(1)
|
||||||
|
|
||||||
assert(not wasDestroyed(root), "expected root to be left alone")
|
assert(not wasRemoved(root), "expected root to be left alone")
|
||||||
assert(wasDestroyed(child), "expected child to be destroyed")
|
assert(wasRemoved(child), "expected child to be removed")
|
||||||
|
|
||||||
instanceMap:stop()
|
instanceMap:stop()
|
||||||
end)
|
end)
|
||||||
@@ -73,6 +91,8 @@ return function()
|
|||||||
-- tests on reify, not here.
|
-- tests on reify, not here.
|
||||||
|
|
||||||
local root = Instance.new("Folder")
|
local root = Instance.new("Folder")
|
||||||
|
root.Name = "ROOT"
|
||||||
|
root.Parent = container
|
||||||
|
|
||||||
local instanceMap = InstanceMap.new()
|
local instanceMap = InstanceMap.new()
|
||||||
instanceMap:insert("ROOT", root)
|
instanceMap:insert("ROOT", root)
|
||||||
@@ -113,6 +133,8 @@ return function()
|
|||||||
|
|
||||||
it("should return unapplied additions when instances cannot be created", function()
|
it("should return unapplied additions when instances cannot be created", function()
|
||||||
local root = Instance.new("Folder")
|
local root = Instance.new("Folder")
|
||||||
|
root.Name = "ROOT"
|
||||||
|
root.Parent = container
|
||||||
|
|
||||||
local instanceMap = InstanceMap.new()
|
local instanceMap = InstanceMap.new()
|
||||||
instanceMap:insert("ROOT", root)
|
instanceMap:insert("ROOT", root)
|
||||||
@@ -159,6 +181,7 @@ return function()
|
|||||||
it("should recreate instances when changedClassName is set, preserving children", function()
|
it("should recreate instances when changedClassName is set, preserving children", function()
|
||||||
local root = Instance.new("Folder")
|
local root = Instance.new("Folder")
|
||||||
root.Name = "Initial Root Name"
|
root.Name = "Initial Root Name"
|
||||||
|
root.Parent = container
|
||||||
|
|
||||||
local child = Instance.new("Folder")
|
local child = Instance.new("Folder")
|
||||||
child.Name = "Child"
|
child.Name = "Child"
|
||||||
|
|||||||
@@ -3,9 +3,14 @@
|
|||||||
and mutating the Roblox DOM.
|
and mutating the Roblox DOM.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local Packages = script.Parent.Parent.Packages
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Log = require(Packages.Log)
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
|
local Timer = require(Plugin.Timer)
|
||||||
|
|
||||||
local applyPatch = require(script.applyPatch)
|
local applyPatch = require(script.applyPatch)
|
||||||
local hydrate = require(script.hydrate)
|
local hydrate = require(script.hydrate)
|
||||||
local diff = require(script.diff)
|
local diff = require(script.diff)
|
||||||
@@ -57,31 +62,55 @@ function Reconciler:hookPostcommit(callback: (patch: any, instanceMap: any, unap
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Reconciler:applyPatch(patch)
|
function Reconciler:applyPatch(patch)
|
||||||
|
Timer.start("Reconciler:applyPatch")
|
||||||
|
|
||||||
|
Timer.start("precommitCallbacks")
|
||||||
|
-- Precommit callbacks must be serial in order to obey the contract that
|
||||||
|
-- they execute before commit
|
||||||
for _, callback in self.__precommitCallbacks do
|
for _, callback in self.__precommitCallbacks do
|
||||||
local success, err = pcall(callback, patch, self.__instanceMap)
|
local success, err = pcall(callback, patch, self.__instanceMap)
|
||||||
if not success then
|
if not success then
|
||||||
Log.warn("Precommit hook errored: {}", err)
|
Log.warn("Precommit hook errored: {}", err)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Timer.stop()
|
||||||
|
|
||||||
|
Timer.start("apply")
|
||||||
local unappliedPatch = applyPatch(self.__instanceMap, patch)
|
local unappliedPatch = applyPatch(self.__instanceMap, patch)
|
||||||
|
Timer.stop()
|
||||||
|
|
||||||
|
Timer.start("postcommitCallbacks")
|
||||||
|
-- Postcommit callbacks can be called with spawn since regardless of firing order, they are
|
||||||
|
-- guaranteed to be called after the commit
|
||||||
for _, callback in self.__postcommitCallbacks do
|
for _, callback in self.__postcommitCallbacks do
|
||||||
|
task.spawn(function()
|
||||||
local success, err = pcall(callback, patch, self.__instanceMap, unappliedPatch)
|
local success, err = pcall(callback, patch, self.__instanceMap, unappliedPatch)
|
||||||
if not success then
|
if not success then
|
||||||
Log.warn("Postcommit hook errored: {}", err)
|
Log.warn("Postcommit hook errored: {}", err)
|
||||||
end
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
Timer.stop()
|
||||||
|
|
||||||
|
Timer.stop()
|
||||||
|
|
||||||
return unappliedPatch
|
return unappliedPatch
|
||||||
end
|
end
|
||||||
|
|
||||||
function Reconciler:hydrate(virtualInstances, rootId, rootInstance)
|
function Reconciler:hydrate(virtualInstances, rootId, rootInstance)
|
||||||
return hydrate(self.__instanceMap, virtualInstances, rootId, rootInstance)
|
Timer.start("Reconciler:hydrate")
|
||||||
|
local result = hydrate(self.__instanceMap, virtualInstances, rootId, rootInstance)
|
||||||
|
Timer.stop()
|
||||||
|
|
||||||
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
function Reconciler:diff(virtualInstances, rootId)
|
function Reconciler:diff(virtualInstances, rootId)
|
||||||
return diff(self.__instanceMap, virtualInstances, rootId)
|
Timer.start("Reconciler:diff")
|
||||||
|
local success, result = diff(self.__instanceMap, virtualInstances, rootId)
|
||||||
|
Timer.stop()
|
||||||
|
|
||||||
|
return success, result
|
||||||
end
|
end
|
||||||
|
|
||||||
return Reconciler
|
return Reconciler
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ local Log = require(Packages.Log)
|
|||||||
local RbxDom = require(Packages.RbxDom)
|
local RbxDom = require(Packages.RbxDom)
|
||||||
local Error = require(script.Parent.Error)
|
local Error = require(script.Parent.Error)
|
||||||
|
|
||||||
local function setProperty(instance: Instance, propertyName: string, value: unknown): boolean
|
local function setProperty(instance, propertyName, value)
|
||||||
local descriptor = RbxDom.findCanonicalPropertyDescriptor(instance.ClassName, propertyName)
|
local descriptor = RbxDom.findCanonicalPropertyDescriptor(instance.ClassName, propertyName)
|
||||||
|
|
||||||
-- We can skip unknown properties; they're not likely reflected to Lua.
|
-- We can skip unknown properties; they're not likely reflected to Lua.
|
||||||
@@ -28,13 +28,6 @@ local function setProperty(instance: Instance, propertyName: string, value: unkn
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
if value == nil then
|
|
||||||
if descriptor.dataType == "Float32" or descriptor.dataType == "Float64" then
|
|
||||||
Log.trace("Skipping nil {} property {}.{}", descriptor.dataType, instance.ClassName, propertyName)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local writeSuccess, err = descriptor:write(instance, value)
|
local writeSuccess, err = descriptor:write(instance, value)
|
||||||
|
|
||||||
if not writeSuccess then
|
if not writeSuccess then
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ local InstanceMap = require(script.Parent.InstanceMap)
|
|||||||
local PatchSet = require(script.Parent.PatchSet)
|
local PatchSet = require(script.Parent.PatchSet)
|
||||||
local Reconciler = require(script.Parent.Reconciler)
|
local Reconciler = require(script.Parent.Reconciler)
|
||||||
local strict = require(script.Parent.strict)
|
local strict = require(script.Parent.strict)
|
||||||
|
local Settings = require(script.Parent.Settings)
|
||||||
|
|
||||||
local Status = strict("Session.Status", {
|
local Status = strict("Session.Status", {
|
||||||
NotStarted = "NotStarted",
|
NotStarted = "NotStarted",
|
||||||
@@ -50,7 +51,6 @@ ServeSession.Status = Status
|
|||||||
|
|
||||||
local validateServeOptions = t.strictInterface({
|
local validateServeOptions = t.strictInterface({
|
||||||
apiContext = t.table,
|
apiContext = t.table,
|
||||||
openScriptsExternally = t.boolean,
|
|
||||||
twoWaySync = t.boolean,
|
twoWaySync = t.boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -89,7 +89,6 @@ function ServeSession.new(options)
|
|||||||
self = {
|
self = {
|
||||||
__status = Status.NotStarted,
|
__status = Status.NotStarted,
|
||||||
__apiContext = options.apiContext,
|
__apiContext = options.apiContext,
|
||||||
__openScriptsExternally = options.openScriptsExternally,
|
|
||||||
__twoWaySync = options.twoWaySync,
|
__twoWaySync = options.twoWaySync,
|
||||||
__reconciler = reconciler,
|
__reconciler = reconciler,
|
||||||
__instanceMap = instanceMap,
|
__instanceMap = instanceMap,
|
||||||
@@ -170,7 +169,7 @@ function ServeSession:__applyGameAndPlaceId(serverInfo)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ServeSession:__onActiveScriptChanged(activeScript)
|
function ServeSession:__onActiveScriptChanged(activeScript)
|
||||||
if not self.__openScriptsExternally then
|
if not Settings:get("openScriptsExternally") then
|
||||||
Log.trace("Not opening script {} because feature not enabled.", activeScript)
|
Log.trace("Not opening script {} because feature not enabled.", activeScript)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -14,11 +14,15 @@ local defaultSettings = {
|
|||||||
twoWaySync = false,
|
twoWaySync = false,
|
||||||
showNotifications = true,
|
showNotifications = true,
|
||||||
syncReminder = true,
|
syncReminder = true,
|
||||||
|
checkForUpdates = true,
|
||||||
|
checkForPrereleases = false,
|
||||||
|
autoConnectPlaytestServer = false,
|
||||||
confirmationBehavior = "Initial",
|
confirmationBehavior = "Initial",
|
||||||
largeChangesConfirmationThreshold = 5,
|
largeChangesConfirmationThreshold = 5,
|
||||||
playSounds = true,
|
playSounds = true,
|
||||||
typecheckingEnabled = false,
|
typecheckingEnabled = false,
|
||||||
logLevel = "Info",
|
logLevel = "Info",
|
||||||
|
timingLogsEnabled = false,
|
||||||
priorEndpoints = {},
|
priorEndpoints = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
57
plugin/src/Timer.lua
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
local Settings = require(script.Parent.Settings)
|
||||||
|
|
||||||
|
local clock = os.clock
|
||||||
|
|
||||||
|
local Timer = {
|
||||||
|
_entries = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
function Timer._start(label)
|
||||||
|
local start = clock()
|
||||||
|
if not label then
|
||||||
|
error("[Rojo-Timer] Timer.start: label is required", 2)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(Timer._entries, { label, start })
|
||||||
|
end
|
||||||
|
|
||||||
|
function Timer._stop()
|
||||||
|
local stop = clock()
|
||||||
|
|
||||||
|
local entry = table.remove(Timer._entries)
|
||||||
|
if not entry then
|
||||||
|
error("[Rojo-Timer] Timer.stop: no label to stop", 2)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local label = entry[1]
|
||||||
|
if #Timer._entries > 0 then
|
||||||
|
local priorLabels = {}
|
||||||
|
for _, priorEntry in ipairs(Timer._entries) do
|
||||||
|
table.insert(priorLabels, priorEntry[1])
|
||||||
|
end
|
||||||
|
label = table.concat(priorLabels, "/") .. "/" .. label
|
||||||
|
end
|
||||||
|
|
||||||
|
local start = entry[2]
|
||||||
|
local duration = stop - start
|
||||||
|
print(string.format("[Rojo-Timer] %s took %.3f ms", label, duration * 1000))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Replace functions with no-op if not in debug mode
|
||||||
|
local function no_op() end
|
||||||
|
local function setFunctions(enabled)
|
||||||
|
if enabled then
|
||||||
|
Timer.start = Timer._start
|
||||||
|
Timer.stop = Timer._stop
|
||||||
|
else
|
||||||
|
Timer.start = no_op
|
||||||
|
Timer.stop = no_op
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Settings:onChanged("timingLogsEnabled", setFunctions)
|
||||||
|
setFunctions(Settings:get("timingLogsEnabled"))
|
||||||
|
|
||||||
|
return Timer
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
local Packages = script.Parent.Parent.Packages
|
||||||
|
local Http = require(Packages.Http)
|
||||||
|
local Promise = require(Packages.Promise)
|
||||||
|
|
||||||
local function compare(a, b)
|
local function compare(a, b)
|
||||||
if a > b then
|
if a > b then
|
||||||
return 1
|
return 1
|
||||||
@@ -30,9 +34,50 @@ function Version.compare(a, b)
|
|||||||
return minor
|
return minor
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if revision ~= 0 then
|
||||||
return revision
|
return revision
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local aPrerelease = if a[4] == "" then nil else a[4]
|
||||||
|
local bPrerelease = if b[4] == "" then nil else b[4]
|
||||||
|
|
||||||
|
-- If neither are prerelease, they are the same
|
||||||
|
if aPrerelease == nil and bPrerelease == nil then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If one is prerelease it is older
|
||||||
|
if aPrerelease ~= nil and bPrerelease == nil then
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
if aPrerelease == nil and bPrerelease ~= nil then
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If they are both prereleases, compare those based on number
|
||||||
|
local aPrereleaseNumeric = string.match(aPrerelease, "(%d+).*$")
|
||||||
|
local bPrereleaseNumeric = string.match(bPrerelease, "(%d+).*$")
|
||||||
|
|
||||||
|
if aPrereleaseNumeric == nil or bPrereleaseNumeric == nil then
|
||||||
|
-- If one or both lack a number, comparing isn't meaningful
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
return compare(tonumber(aPrereleaseNumeric) or 0, tonumber(bPrereleaseNumeric) or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Version.parse(versionString: string)
|
||||||
|
local version = { string.match(versionString, "^v?(%d+)%.(%d+)%.(%d+)(.*)$") }
|
||||||
|
for i, v in version do
|
||||||
|
version[i] = tonumber(v) or v
|
||||||
|
end
|
||||||
|
|
||||||
|
if version[4] == "" then
|
||||||
|
version[4] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return version
|
||||||
|
end
|
||||||
|
|
||||||
function Version.display(version)
|
function Version.display(version)
|
||||||
local output = ("%d.%d.%d"):format(version[1], version[2], version[3])
|
local output = ("%d.%d.%d"):format(version[1], version[2], version[3])
|
||||||
|
|
||||||
@@ -43,4 +88,64 @@ function Version.display(version)
|
|||||||
return output
|
return output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Version.retrieveLatestCompatible(options: {
|
||||||
|
version: { number },
|
||||||
|
includePrereleases: boolean?,
|
||||||
|
}): {
|
||||||
|
version: { number },
|
||||||
|
prerelease: boolean,
|
||||||
|
publishedUnixTimestamp: number,
|
||||||
|
}?
|
||||||
|
local success, releases = Http.get("https://api.github.com/repos/rojo-rbx/rojo/releases?per_page=10")
|
||||||
|
:andThen(function(response)
|
||||||
|
if response.code >= 400 then
|
||||||
|
local message = string.format("HTTP %s:\n%s", tostring(response.code), response.body)
|
||||||
|
|
||||||
|
return Promise.reject(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
return response
|
||||||
|
end)
|
||||||
|
:andThen(Http.Response.json)
|
||||||
|
:await()
|
||||||
|
|
||||||
|
if success == false or type(releases) ~= "table" or next(releases) ~= 1 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Iterate through releases, looking for the latest compatible version
|
||||||
|
local latestCompatible = nil
|
||||||
|
for _, release in releases do
|
||||||
|
-- Skip prereleases if they are not requested
|
||||||
|
if (not options.includePrereleases) and release.prerelease then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
local releaseVersion = Version.parse(release.tag_name)
|
||||||
|
|
||||||
|
-- Skip releases that are potentially incompatible
|
||||||
|
if releaseVersion[1] > options.version[1] then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Skip releases that are older than the latest compatible version
|
||||||
|
if latestCompatible ~= nil and Version.compare(releaseVersion, latestCompatible.version) <= 0 then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
latestCompatible = {
|
||||||
|
version = releaseVersion,
|
||||||
|
prerelease = release.prerelease,
|
||||||
|
publishedUnixTimestamp = DateTime.fromIsoDate(release.published_at).UnixTimestamp,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Don't return anything if the latest found is not newer than the current version
|
||||||
|
if latestCompatible == nil or Version.compare(latestCompatible.version, options.version) <= 0 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return latestCompatible
|
||||||
|
end
|
||||||
|
|
||||||
return Version
|
return Version
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ return function()
|
|||||||
|
|
||||||
it("should compare equal versions", function()
|
it("should compare equal versions", function()
|
||||||
expect(Version.compare({ 1, 2, 3 }, { 1, 2, 3 })).to.equal(0)
|
expect(Version.compare({ 1, 2, 3 }, { 1, 2, 3 })).to.equal(0)
|
||||||
|
expect(Version.compare({ 1, 2, 3, "rc1" }, { 1, 2, 3, "rc1" })).to.equal(0)
|
||||||
expect(Version.compare({ 0, 4, 0 }, { 0, 4 })).to.equal(0)
|
expect(Version.compare({ 0, 4, 0 }, { 0, 4 })).to.equal(0)
|
||||||
expect(Version.compare({ 0, 0, 123 }, { 0, 0, 123 })).to.equal(0)
|
expect(Version.compare({ 0, 0, 123 }, { 0, 0, 123 })).to.equal(0)
|
||||||
expect(Version.compare({ 26 }, { 26 })).to.equal(0)
|
expect(Version.compare({ 26 }, { 26 })).to.equal(0)
|
||||||
@@ -13,6 +14,7 @@ return function()
|
|||||||
it("should compare newer, older versions", function()
|
it("should compare newer, older versions", function()
|
||||||
expect(Version.compare({ 1 }, { 0 })).to.equal(1)
|
expect(Version.compare({ 1 }, { 0 })).to.equal(1)
|
||||||
expect(Version.compare({ 1, 1 }, { 1, 0 })).to.equal(1)
|
expect(Version.compare({ 1, 1 }, { 1, 0 })).to.equal(1)
|
||||||
|
expect(Version.compare({ 1, 2, 3 }, { 1, 2, 0 })).to.equal(1)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("should compare different major versions", function()
|
it("should compare different major versions", function()
|
||||||
@@ -25,4 +27,37 @@ return function()
|
|||||||
expect(Version.compare({ 1, 2, 3 }, { 1, 3, 2 })).to.equal(-1)
|
expect(Version.compare({ 1, 2, 3 }, { 1, 3, 2 })).to.equal(-1)
|
||||||
expect(Version.compare({ 50, 1 }, { 50, 2 })).to.equal(-1)
|
expect(Version.compare({ 50, 1 }, { 50, 2 })).to.equal(-1)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("should compare different patch versions", function()
|
||||||
|
expect(Version.compare({ 1, 1, 3 }, { 1, 1, 2 })).to.equal(1)
|
||||||
|
expect(Version.compare({ 1, 1, 2 }, { 1, 1, 3 })).to.equal(-1)
|
||||||
|
expect(Version.compare({ 1, 1, 3, "-rc1" }, { 1, 1, 2, "-rc2" })).to.equal(1)
|
||||||
|
expect(Version.compare({ 1, 1, 2, "-rc5" }, { 1, 1, 3, "-alpha" })).to.equal(-1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should compare prerelease tags", function()
|
||||||
|
expect(Version.compare({ 1, 0, 0, "-alpha" }, { 1, 0, 0 })).to.equal(-1)
|
||||||
|
expect(Version.compare({ 1, 0, 0 }, { 1, 0, 0, "-alpha" })).to.equal(1)
|
||||||
|
expect(Version.compare({ 1, 0, 0, "-rc1" }, { 1, 0, 0, "-rc2" })).to.equal(-1)
|
||||||
|
expect(Version.compare({ 1, 0, 0, "-rc2" }, { 1, 0, 0, "-rc1" })).to.equal(1)
|
||||||
|
|
||||||
|
-- Non number prereleases are not compared since that isn't meaningful
|
||||||
|
expect(Version.compare({ 1, 0, 0, "-alpha" }, { 1, 0, 0, "-beta" })).to.equal(0)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should parse version from strings", function()
|
||||||
|
local a = Version.parse("v1.0.0")
|
||||||
|
expect(a).to.be.ok()
|
||||||
|
expect(a[1]).to.equal(1)
|
||||||
|
expect(a[2]).to.equal(0)
|
||||||
|
expect(a[3]).to.equal(0)
|
||||||
|
expect(a[4]).to.equal(nil)
|
||||||
|
|
||||||
|
local b = Version.parse("7.3.1-rc1")
|
||||||
|
expect(b).to.be.ok()
|
||||||
|
expect(b[1]).to.equal(7)
|
||||||
|
expect(b[2]).to.equal(3)
|
||||||
|
expect(b[3]).to.equal(1)
|
||||||
|
expect(b[4]).to.equal("-rc1")
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
31
plugin/src/timeUtil.lua
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
local timeUtil = {}
|
||||||
|
|
||||||
|
timeUtil.AGE_UNITS = table.freeze({
|
||||||
|
{ 31556909, "year" },
|
||||||
|
{ 2629743, "month" },
|
||||||
|
{ 604800, "week" },
|
||||||
|
{ 86400, "day" },
|
||||||
|
{ 3600, "hour" },
|
||||||
|
{ 60, "minute" },
|
||||||
|
})
|
||||||
|
|
||||||
|
function timeUtil.elapsedToText(elapsed: number): string
|
||||||
|
if elapsed < 3 then
|
||||||
|
return "just now"
|
||||||
|
end
|
||||||
|
|
||||||
|
local ageText = string.format("%d seconds ago", elapsed)
|
||||||
|
|
||||||
|
for _, UnitData in timeUtil.AGE_UNITS do
|
||||||
|
local UnitSeconds, UnitName = UnitData[1], UnitData[2]
|
||||||
|
if elapsed > UnitSeconds then
|
||||||
|
local c = math.floor(elapsed / UnitSeconds)
|
||||||
|
ageText = string.format("%d %s%s ago", c, UnitName, c > 1 and "s" or "")
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ageText
|
||||||
|
end
|
||||||
|
|
||||||
|
return timeUtil
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: tests/tests/build.rs
|
source: tests/tests/build.rs
|
||||||
assertion_line: 104
|
assertion_line: 107
|
||||||
expression: contents
|
expression: contents
|
||||||
---
|
---
|
||||||
<roblox version="4">
|
<roblox version="4">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
source: tests/tests/build.rs
|
source: tests/tests/build.rs
|
||||||
expression: contents
|
expression: contents
|
||||||
|
|
||||||
---
|
---
|
||||||
<roblox version="4">
|
<roblox version="4">
|
||||||
<Item class="Folder" referent="0">
|
<Item class="Folder" referent="0">
|
||||||
@@ -24,7 +25,6 @@ expression: contents
|
|||||||
<R21>0</R21>
|
<R21>0</R21>
|
||||||
<R22>1</R22>
|
<R22>1</R22>
|
||||||
</CoordinateFrame>
|
</CoordinateFrame>
|
||||||
<bool name="NeedsPivotMigration">false</bool>
|
|
||||||
<Ref name="PrimaryPart">null</Ref>
|
<Ref name="PrimaryPart">null</Ref>
|
||||||
<BinaryString name="Tags"></BinaryString>
|
<BinaryString name="Tags"></BinaryString>
|
||||||
</Properties>
|
</Properties>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/build.rs
|
||||||
|
assertion_line: 102
|
||||||
|
expression: contents
|
||||||
|
---
|
||||||
|
<roblox version="4">
|
||||||
|
<Item class="Folder" referent="0">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">sync_rule_alone</string>
|
||||||
|
</Properties>
|
||||||
|
<Item class="StringValue" referent="1">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">foo</string>
|
||||||
|
<string name="Value">Hello, world!</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/build.rs
|
||||||
|
assertion_line: 104
|
||||||
|
expression: contents
|
||||||
|
---
|
||||||
|
<roblox version="4">
|
||||||
|
<Item class="Folder" referent="0">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">sync_rule_complex</string>
|
||||||
|
</Properties>
|
||||||
|
<Item class="Script" referent="1">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">bar</string>
|
||||||
|
<token name="RunContext">0</token>
|
||||||
|
<string name="Source">-- Hello, from bar (a Script)!</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
<Item class="LocalScript" referent="2">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">baz</string>
|
||||||
|
<string name="Source">-- Hello, from baz (a LocalScript)!</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
<Item class="StringValue" referent="3">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">cat</string>
|
||||||
|
<string name="Value">Hello, from cat (a StringValue)!</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
<Item class="ModuleScript" referent="4">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">foo</string>
|
||||||
|
<string name="Source">-- Hello, from foo (a ModuleScript)!</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
<Item class="StringValue" referent="5">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">qux</string>
|
||||||
|
<string name="Value">Hello, from qux (a .rojo file that's turned into a StringValue)!</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/build.rs
|
||||||
|
assertion_line: 104
|
||||||
|
expression: contents
|
||||||
|
---
|
||||||
|
<roblox version="4">
|
||||||
|
<Item class="Folder" referent="0">
|
||||||
|
<Properties>
|
||||||
|
<string name="Name">sync_rule_nested_projects</string>
|
||||||
|
</Properties>
|
||||||
|
</Item>
|
||||||
|
</roblox>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
source: tests/tests/build.rs
|
source: tests/tests/build.rs
|
||||||
expression: contents
|
expression: contents
|
||||||
|
|
||||||
---
|
---
|
||||||
<roblox version="4">
|
<roblox version="4">
|
||||||
<Item class="DataModel" referent="0">
|
<Item class="DataModel" referent="0">
|
||||||
@@ -21,7 +22,6 @@ expression: contents
|
|||||||
<Item class="Workspace" referent="2">
|
<Item class="Workspace" referent="2">
|
||||||
<Properties>
|
<Properties>
|
||||||
<string name="Name">Workspace</string>
|
<string name="Name">Workspace</string>
|
||||||
<bool name="NeedsPivotMigration">false</bool>
|
|
||||||
</Properties>
|
</Properties>
|
||||||
<Item class="BoolValue" referent="3">
|
<Item class="BoolValue" referent="3">
|
||||||
<Properties>
|
<Properties>
|
||||||
|
|||||||
12
rojo-test/build-tests/sync_rule_alone/default.project.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "sync_rule_alone",
|
||||||
|
"tree": {
|
||||||
|
"$path": "src"
|
||||||
|
},
|
||||||
|
"syncRules": [
|
||||||
|
{
|
||||||
|
"pattern": "*.nothing",
|
||||||
|
"use": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1
rojo-test/build-tests/sync_rule_alone/src/foo.nothing
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Hello, world!
|
||||||
30
rojo-test/build-tests/sync_rule_complex/default.project.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "sync_rule_complex",
|
||||||
|
"tree": {
|
||||||
|
"$path": "src"
|
||||||
|
},
|
||||||
|
"syncRules": [
|
||||||
|
{
|
||||||
|
"pattern": "*.module",
|
||||||
|
"use": "moduleScript"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "*.server",
|
||||||
|
"use": "serverScript"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "*.client",
|
||||||
|
"use": "clientScript"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "*.rojo",
|
||||||
|
"exclude": "*.ignore.rojo",
|
||||||
|
"use": "project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "*.dog.rojo2",
|
||||||
|
"use": "text",
|
||||||
|
"suffix": ".dog.rojo2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1
rojo-test/build-tests/sync_rule_complex/src/bar.server
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-- Hello, from bar (a Script)!
|
||||||
1
rojo-test/build-tests/sync_rule_complex/src/baz.client
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-- Hello, from baz (a LocalScript)!
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Hello, from cat (a StringValue)!
|
||||||
1
rojo-test/build-tests/sync_rule_complex/src/foo.module
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-- Hello, from foo (a ModuleScript)!
|
||||||
9
rojo-test/build-tests/sync_rule_complex/src/qux.rojo
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "qux",
|
||||||
|
"tree": {
|
||||||
|
"$className": "StringValue",
|
||||||
|
"$properties": {
|
||||||
|
"Value": "Hello, from qux (a .rojo file that's turned into a StringValue)!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This file should be ignored!
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "sync_rule_nested_projects",
|
||||||
|
"tree": {
|
||||||
|
"$path": "nested.project.json"
|
||||||
|
},
|
||||||
|
"syncRules": [
|
||||||
|
{
|
||||||
|
"pattern": "*.rojo",
|
||||||
|
"use": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "nested",
|
||||||
|
"tree": {
|
||||||
|
"$path": "src"
|
||||||
|
},
|
||||||
|
"syncRules": [
|
||||||
|
{
|
||||||
|
"pattern": "*.txt",
|
||||||
|
"use": "ignore"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This shouldn't be in the built file. If it is, something is wrong.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
This shouldn't be in the built file. If it is, something is wrong.
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
source: tests/tests/serve.rs
|
source: tests/tests/serve.rs
|
||||||
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||||
|
|
||||||
---
|
---
|
||||||
instances:
|
instances:
|
||||||
id-2:
|
id-2:
|
||||||
@@ -21,9 +22,7 @@ instances:
|
|||||||
ignoreUnknownInstances: false
|
ignoreUnknownInstances: false
|
||||||
Name: test
|
Name: test
|
||||||
Parent: id-2
|
Parent: id-2
|
||||||
Properties:
|
Properties: {}
|
||||||
NeedsPivotMigration:
|
|
||||||
Bool: false
|
|
||||||
messageCursor: 1
|
messageCursor: 1
|
||||||
sessionId: id-1
|
sessionId: id-1
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
source: tests/tests/serve.rs
|
source: tests/tests/serve.rs
|
||||||
expression: "subscribe_response.intern_and_redact(&mut redactions, ())"
|
expression: "subscribe_response.intern_and_redact(&mut redactions, ())"
|
||||||
|
|
||||||
---
|
---
|
||||||
messageCursor: 1
|
messageCursor: 1
|
||||||
messages:
|
messages:
|
||||||
@@ -13,9 +14,7 @@ messages:
|
|||||||
ignoreUnknownInstances: false
|
ignoreUnknownInstances: false
|
||||||
Name: test
|
Name: test
|
||||||
Parent: id-2
|
Parent: id-2
|
||||||
Properties:
|
Properties: {}
|
||||||
NeedsPivotMigration:
|
|
||||||
Bool: false
|
|
||||||
removed: []
|
removed: []
|
||||||
updated: []
|
updated: []
|
||||||
sessionId: id-1
|
sessionId: id-1
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: tests/tests/serve.rs
|
source: tests/tests/serve.rs
|
||||||
|
assertion_line: 370
|
||||||
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||||
---
|
---
|
||||||
instances:
|
instances:
|
||||||
@@ -16,4 +17,3 @@ instances:
|
|||||||
String: "If this isn't named `no_name_top_level_project`, something went wrong!"
|
String: "If this isn't named `no_name_top_level_project`, something went wrong!"
|
||||||
messageCursor: 0
|
messageCursor: 0
|
||||||
sessionId: id-1
|
sessionId: id-1
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: tests/tests/serve.rs
|
source: tests/tests/serve.rs
|
||||||
assertion_line: 306
|
assertion_line: 357
|
||||||
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||||
---
|
---
|
||||||
instances:
|
instances:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
source: tests/tests/serve.rs
|
source: tests/tests/serve.rs
|
||||||
assertion_line: 300
|
assertion_line: 351
|
||||||
expression: redactions.redacted_yaml(info)
|
expression: redactions.redacted_yaml(info)
|
||||||
---
|
---
|
||||||
expectedPlaceIds: ~
|
expectedPlaceIds: ~
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||||
|
---
|
||||||
|
instances:
|
||||||
|
id-10:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-10
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: ProjectPointer
|
||||||
|
Parent: id-9
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: project target
|
||||||
|
Value:
|
||||||
|
Ref: id-9
|
||||||
|
id-11:
|
||||||
|
Children: []
|
||||||
|
ClassName: Model
|
||||||
|
Id: id-11
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: ProjectPointer
|
||||||
|
Parent: id-7
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_PrimaryPart:
|
||||||
|
String: project target
|
||||||
|
PrimaryPart:
|
||||||
|
Ref: id-9
|
||||||
|
id-2:
|
||||||
|
Children:
|
||||||
|
- id-3
|
||||||
|
ClassName: DataModel
|
||||||
|
Id: id-2
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: ref_properties
|
||||||
|
Parent: "00000000000000000000000000000000"
|
||||||
|
Properties: {}
|
||||||
|
id-3:
|
||||||
|
Children:
|
||||||
|
- id-4
|
||||||
|
- id-5
|
||||||
|
- id-7
|
||||||
|
- id-9
|
||||||
|
ClassName: Workspace
|
||||||
|
Id: id-3
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: Workspace
|
||||||
|
Parent: id-2
|
||||||
|
Properties: {}
|
||||||
|
id-4:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-4
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: CrossFormatPointer
|
||||||
|
Parent: id-3
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: folder target
|
||||||
|
Value:
|
||||||
|
Ref: id-5
|
||||||
|
id-5:
|
||||||
|
Children:
|
||||||
|
- id-6
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-5
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: FolderTarget
|
||||||
|
Parent: id-3
|
||||||
|
Properties: {}
|
||||||
|
id-6:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-6
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: FolderPointer
|
||||||
|
Parent: id-5
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: folder target
|
||||||
|
Value:
|
||||||
|
Ref: id-5
|
||||||
|
id-7:
|
||||||
|
Children:
|
||||||
|
- id-8
|
||||||
|
- id-11
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-7
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: ModelTarget
|
||||||
|
Parent: id-3
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Id:
|
||||||
|
String: model target 2
|
||||||
|
id-8:
|
||||||
|
Children: []
|
||||||
|
ClassName: Model
|
||||||
|
Id: id-8
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: ModelPointer
|
||||||
|
Parent: id-7
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_PrimaryPart:
|
||||||
|
String: model target 2
|
||||||
|
PrimaryPart:
|
||||||
|
Ref: id-7
|
||||||
|
id-9:
|
||||||
|
Children:
|
||||||
|
- id-10
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-9
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: ProjectTarget
|
||||||
|
Parent: id-3
|
||||||
|
Properties: {}
|
||||||
|
messageCursor: 1
|
||||||
|
sessionId: id-1
|
||||||
|
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||||
|
---
|
||||||
|
instances:
|
||||||
|
id-10:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-10
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: ProjectPointer
|
||||||
|
Parent: id-9
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: project target
|
||||||
|
Value:
|
||||||
|
Ref: id-9
|
||||||
|
id-2:
|
||||||
|
Children:
|
||||||
|
- id-3
|
||||||
|
ClassName: DataModel
|
||||||
|
Id: id-2
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: ref_properties
|
||||||
|
Parent: "00000000000000000000000000000000"
|
||||||
|
Properties: {}
|
||||||
|
id-3:
|
||||||
|
Children:
|
||||||
|
- id-4
|
||||||
|
- id-5
|
||||||
|
- id-7
|
||||||
|
- id-9
|
||||||
|
ClassName: Workspace
|
||||||
|
Id: id-3
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: Workspace
|
||||||
|
Parent: id-2
|
||||||
|
Properties: {}
|
||||||
|
id-4:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-4
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: CrossFormatPointer
|
||||||
|
Parent: id-3
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: folder target
|
||||||
|
Value:
|
||||||
|
Ref: id-5
|
||||||
|
id-5:
|
||||||
|
Children:
|
||||||
|
- id-6
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-5
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: FolderTarget
|
||||||
|
Parent: id-3
|
||||||
|
Properties: {}
|
||||||
|
id-6:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-6
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: FolderPointer
|
||||||
|
Parent: id-5
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: folder target
|
||||||
|
Value:
|
||||||
|
Ref: id-5
|
||||||
|
id-7:
|
||||||
|
Children:
|
||||||
|
- id-8
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-7
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: ModelTarget
|
||||||
|
Parent: id-3
|
||||||
|
Properties: {}
|
||||||
|
id-8:
|
||||||
|
Children: []
|
||||||
|
ClassName: Model
|
||||||
|
Id: id-8
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: ModelPointer
|
||||||
|
Parent: id-7
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_PrimaryPart:
|
||||||
|
String: model target
|
||||||
|
PrimaryPart:
|
||||||
|
Ref: id-7
|
||||||
|
id-9:
|
||||||
|
Children:
|
||||||
|
- id-10
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-9
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: ProjectTarget
|
||||||
|
Parent: id-3
|
||||||
|
Properties: {}
|
||||||
|
messageCursor: 0
|
||||||
|
sessionId: id-1
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
expression: redactions.redacted_yaml(info)
|
||||||
|
---
|
||||||
|
expectedPlaceIds: ~
|
||||||
|
gameId: ~
|
||||||
|
placeId: ~
|
||||||
|
projectName: ref_properties
|
||||||
|
protocolVersion: 4
|
||||||
|
rootInstanceId: id-2
|
||||||
|
serverVersion: "[server-version]"
|
||||||
|
sessionId: id-1
|
||||||
|
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||||
|
---
|
||||||
|
instances:
|
||||||
|
id-10:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-10
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: ProjectPointer
|
||||||
|
Parent: id-9
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: project target
|
||||||
|
Value:
|
||||||
|
Ref: id-9
|
||||||
|
id-2:
|
||||||
|
Children:
|
||||||
|
- id-3
|
||||||
|
ClassName: DataModel
|
||||||
|
Id: id-2
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: ref_properties
|
||||||
|
Parent: "00000000000000000000000000000000"
|
||||||
|
Properties: {}
|
||||||
|
id-3:
|
||||||
|
Children:
|
||||||
|
- id-4
|
||||||
|
- id-5
|
||||||
|
- id-7
|
||||||
|
- id-9
|
||||||
|
ClassName: Workspace
|
||||||
|
Id: id-3
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: Workspace
|
||||||
|
Parent: id-2
|
||||||
|
Properties: {}
|
||||||
|
id-4:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-4
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: CrossFormatPointer
|
||||||
|
Parent: id-3
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: folder target
|
||||||
|
Value:
|
||||||
|
Ref: id-5
|
||||||
|
id-5:
|
||||||
|
Children:
|
||||||
|
- id-6
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-5
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: FolderTarget
|
||||||
|
Parent: id-3
|
||||||
|
Properties: {}
|
||||||
|
id-6:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-6
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: FolderPointer
|
||||||
|
Parent: id-5
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: folder target
|
||||||
|
Value:
|
||||||
|
Ref: id-5
|
||||||
|
id-7:
|
||||||
|
Children:
|
||||||
|
- id-8
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-7
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: ModelTarget
|
||||||
|
Parent: id-3
|
||||||
|
Properties: {}
|
||||||
|
id-8:
|
||||||
|
Children: []
|
||||||
|
ClassName: Model
|
||||||
|
Id: id-8
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: ModelPointer
|
||||||
|
Parent: id-7
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_PrimaryPart:
|
||||||
|
String: model target
|
||||||
|
PrimaryPart:
|
||||||
|
Ref: id-7
|
||||||
|
id-9:
|
||||||
|
Children:
|
||||||
|
- id-10
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-9
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: ProjectTarget
|
||||||
|
Parent: id-3
|
||||||
|
Properties: {}
|
||||||
|
messageCursor: 0
|
||||||
|
sessionId: id-1
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||||
|
---
|
||||||
|
instances:
|
||||||
|
id-10:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-10
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: ProjectPointer
|
||||||
|
Parent: id-9
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: project target
|
||||||
|
Value:
|
||||||
|
Ref: id-9
|
||||||
|
id-2:
|
||||||
|
Children:
|
||||||
|
- id-3
|
||||||
|
ClassName: DataModel
|
||||||
|
Id: id-2
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: ref_properties
|
||||||
|
Parent: "00000000000000000000000000000000"
|
||||||
|
Properties: {}
|
||||||
|
id-3:
|
||||||
|
Children:
|
||||||
|
- id-4
|
||||||
|
- id-5
|
||||||
|
- id-7
|
||||||
|
- id-9
|
||||||
|
ClassName: Workspace
|
||||||
|
Id: id-3
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: Workspace
|
||||||
|
Parent: id-2
|
||||||
|
Properties: {}
|
||||||
|
id-4:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-4
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: CrossFormatPointer
|
||||||
|
Parent: id-3
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: folder target
|
||||||
|
Value:
|
||||||
|
Ref: id-5
|
||||||
|
id-5:
|
||||||
|
Children:
|
||||||
|
- id-6
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-5
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: FolderTarget
|
||||||
|
Parent: id-3
|
||||||
|
Properties: {}
|
||||||
|
id-6:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-6
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: FolderPointer
|
||||||
|
Parent: id-5
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: folder target
|
||||||
|
Value:
|
||||||
|
Ref: id-5
|
||||||
|
id-7:
|
||||||
|
Children:
|
||||||
|
- id-8
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-7
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: ModelTarget
|
||||||
|
Parent: id-3
|
||||||
|
Properties: {}
|
||||||
|
id-8:
|
||||||
|
Children: []
|
||||||
|
ClassName: Model
|
||||||
|
Id: id-8
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: ModelPointer
|
||||||
|
Parent: id-7
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_PrimaryPart:
|
||||||
|
String: model target
|
||||||
|
PrimaryPart:
|
||||||
|
Ref: id-7
|
||||||
|
id-9:
|
||||||
|
Children:
|
||||||
|
- id-10
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-9
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: ProjectTarget
|
||||||
|
Parent: id-3
|
||||||
|
Properties: {}
|
||||||
|
messageCursor: 0
|
||||||
|
sessionId: id-1
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
expression: redactions.redacted_yaml(info)
|
||||||
|
---
|
||||||
|
expectedPlaceIds: ~
|
||||||
|
gameId: ~
|
||||||
|
placeId: ~
|
||||||
|
projectName: ref_properties
|
||||||
|
protocolVersion: 4
|
||||||
|
rootInstanceId: id-2
|
||||||
|
serverVersion: "[server-version]"
|
||||||
|
sessionId: id-1
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||||
|
---
|
||||||
|
instances:
|
||||||
|
id-2:
|
||||||
|
Children:
|
||||||
|
- id-3
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-2
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: ref_properties_remove
|
||||||
|
Parent: "00000000000000000000000000000000"
|
||||||
|
Properties: {}
|
||||||
|
id-3:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-3
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: pointer
|
||||||
|
Parent: id-2
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: test pointer
|
||||||
|
Value:
|
||||||
|
Ref: id-4
|
||||||
|
messageCursor: 1
|
||||||
|
sessionId: id-1
|
||||||
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||||
|
---
|
||||||
|
instances:
|
||||||
|
id-2:
|
||||||
|
Children:
|
||||||
|
- id-3
|
||||||
|
- id-4
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-2
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: ref_properties_remove
|
||||||
|
Parent: "00000000000000000000000000000000"
|
||||||
|
Properties: {}
|
||||||
|
id-3:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-3
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: pointer
|
||||||
|
Parent: id-2
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_Value:
|
||||||
|
String: test pointer
|
||||||
|
Value:
|
||||||
|
Ref: id-4
|
||||||
|
id-4:
|
||||||
|
Children: []
|
||||||
|
ClassName: ObjectValue
|
||||||
|
Id: id-4
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: target
|
||||||
|
Parent: id-2
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Id:
|
||||||
|
String: test pointer
|
||||||
|
messageCursor: 0
|
||||||
|
sessionId: id-1
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
expression: redactions.redacted_yaml(info)
|
||||||
|
---
|
||||||
|
expectedPlaceIds: ~
|
||||||
|
gameId: ~
|
||||||
|
placeId: ~
|
||||||
|
projectName: ref_properties_remove
|
||||||
|
protocolVersion: 4
|
||||||
|
rootInstanceId: id-2
|
||||||
|
serverVersion: "[server-version]"
|
||||||
|
sessionId: id-1
|
||||||
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
expression: "subscribe_response.intern_and_redact(&mut redactions, ())"
|
||||||
|
---
|
||||||
|
messageCursor: 1
|
||||||
|
messages:
|
||||||
|
- added: {}
|
||||||
|
removed:
|
||||||
|
- id-4
|
||||||
|
updated: []
|
||||||
|
sessionId: id-1
|
||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
expression: "subscribe_response.intern_and_redact(&mut redactions, ())"
|
||||||
|
---
|
||||||
|
messageCursor: 1
|
||||||
|
messages:
|
||||||
|
- added:
|
||||||
|
id-11:
|
||||||
|
Children: []
|
||||||
|
ClassName: Model
|
||||||
|
Id: id-11
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: ProjectPointer
|
||||||
|
Parent: id-7
|
||||||
|
Properties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_PrimaryPart:
|
||||||
|
String: project target
|
||||||
|
PrimaryPart:
|
||||||
|
Ref: id-9
|
||||||
|
removed: []
|
||||||
|
updated:
|
||||||
|
- changedClassName: ~
|
||||||
|
changedMetadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
changedName: ~
|
||||||
|
changedProperties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Id:
|
||||||
|
String: model target 2
|
||||||
|
id: id-7
|
||||||
|
- changedClassName: ~
|
||||||
|
changedMetadata: ~
|
||||||
|
changedName: ~
|
||||||
|
changedProperties:
|
||||||
|
Attributes:
|
||||||
|
Attributes:
|
||||||
|
Rojo_Target_PrimaryPart:
|
||||||
|
String: model target 2
|
||||||
|
PrimaryPart: ~
|
||||||
|
id: id-8
|
||||||
|
sessionId: id-1
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
assertion_line: 268
|
||||||
|
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||||
|
---
|
||||||
|
instances:
|
||||||
|
id-2:
|
||||||
|
Children:
|
||||||
|
- id-3
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-2
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: sync_rule_alone
|
||||||
|
Parent: "00000000000000000000000000000000"
|
||||||
|
Properties: {}
|
||||||
|
id-3:
|
||||||
|
Children: []
|
||||||
|
ClassName: StringValue
|
||||||
|
Id: id-3
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: foo
|
||||||
|
Parent: id-2
|
||||||
|
Properties:
|
||||||
|
Value:
|
||||||
|
String: "Hello, world!"
|
||||||
|
messageCursor: 0
|
||||||
|
sessionId: id-1
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
assertion_line: 265
|
||||||
|
expression: redactions.redacted_yaml(info)
|
||||||
|
---
|
||||||
|
expectedPlaceIds: ~
|
||||||
|
gameId: ~
|
||||||
|
placeId: ~
|
||||||
|
projectName: sync_rule_alone
|
||||||
|
protocolVersion: 4
|
||||||
|
rootInstanceId: id-2
|
||||||
|
serverVersion: "[server-version]"
|
||||||
|
sessionId: id-1
|
||||||
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
source: tests/tests/serve.rs
|
||||||
|
assertion_line: 284
|
||||||
|
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
|
||||||
|
---
|
||||||
|
instances:
|
||||||
|
id-2:
|
||||||
|
Children:
|
||||||
|
- id-3
|
||||||
|
- id-4
|
||||||
|
- id-5
|
||||||
|
- id-6
|
||||||
|
- id-7
|
||||||
|
ClassName: Folder
|
||||||
|
Id: id-2
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: sync_rule_complex
|
||||||
|
Parent: "00000000000000000000000000000000"
|
||||||
|
Properties: {}
|
||||||
|
id-3:
|
||||||
|
Children: []
|
||||||
|
ClassName: Script
|
||||||
|
Id: id-3
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: bar
|
||||||
|
Parent: id-2
|
||||||
|
Properties:
|
||||||
|
RunContext:
|
||||||
|
Enum: 0
|
||||||
|
Source:
|
||||||
|
String: "-- Hello, from bar (a Script)!"
|
||||||
|
id-4:
|
||||||
|
Children: []
|
||||||
|
ClassName: LocalScript
|
||||||
|
Id: id-4
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: baz
|
||||||
|
Parent: id-2
|
||||||
|
Properties:
|
||||||
|
Source:
|
||||||
|
String: "-- Hello, from baz (a LocalScript)!"
|
||||||
|
id-5:
|
||||||
|
Children: []
|
||||||
|
ClassName: StringValue
|
||||||
|
Id: id-5
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: cat
|
||||||
|
Parent: id-2
|
||||||
|
Properties:
|
||||||
|
Value:
|
||||||
|
String: "Hello, from cat (a StringValue)!"
|
||||||
|
id-6:
|
||||||
|
Children: []
|
||||||
|
ClassName: ModuleScript
|
||||||
|
Id: id-6
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: false
|
||||||
|
Name: foo
|
||||||
|
Parent: id-2
|
||||||
|
Properties:
|
||||||
|
Source:
|
||||||
|
String: "-- Hello, from foo (a ModuleScript)!"
|
||||||
|
id-7:
|
||||||
|
Children: []
|
||||||
|
ClassName: StringValue
|
||||||
|
Id: id-7
|
||||||
|
Metadata:
|
||||||
|
ignoreUnknownInstances: true
|
||||||
|
Name: qux
|
||||||
|
Parent: id-2
|
||||||
|
Properties:
|
||||||
|
Value:
|
||||||
|
String: "Hello, from qux (a .rojo file that's turned into a StringValue)!"
|
||||||
|
messageCursor: 0
|
||||||
|
sessionId: id-1
|
||||||
|
|
||||||