Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
441c469966 | ||
|
|
f3c423d77d | ||
|
|
beb497878b | ||
|
|
6ea95d487c | ||
|
|
80a381dbb1 | ||
|
|
59e36491a5 | ||
|
|
c1326ba06e | ||
|
|
e2633126ee | ||
|
|
5f33435f3c | ||
|
|
54e0ff230b | ||
|
|
4e9e6233ff | ||
|
|
0056849b51 | ||
|
|
2ddb21ec5f | ||
|
|
a4eb65ca3f | ||
|
|
3002d250a1 | ||
|
|
9598553e5d | ||
|
|
7f68d9887b | ||
|
|
e092a7301f | ||
|
|
6dfdfbe514 | ||
|
|
7860f2717f | ||
|
|
60f19df9a0 | ||
|
|
951f0cda0b | ||
|
|
227042d6b1 | ||
|
|
b2c4f550ee | ||
|
|
4ddbefa88f | ||
|
|
d935115591 | ||
|
|
bd2ea42732 | ||
|
|
3bac38ee34 | ||
|
|
a7a4f6d8f2 | ||
|
|
80b6facbd3 | ||
|
|
7dee898400 | ||
|
|
4c4b2dbe17 | ||
|
|
73ed5ae697 | ||
|
|
833320de64 | ||
|
|
0d6ff8ef8a | ||
|
|
55a207a275 | ||
|
|
f33d1f1cc4 | ||
|
|
19ca2b12fc | ||
|
|
b7d3394464 | ||
|
|
8c33100d7a | ||
|
|
80c406f196 | ||
|
|
bc2c76e5e2 | ||
|
|
4a7bddbc09 | ||
|
|
e316fdbaef | ||
|
|
34106f470f | ||
|
|
d9ab0e7de8 | ||
|
|
5ca1573e2e | ||
|
|
c9ce996626 | ||
|
|
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 |
2
.dir-locals.el
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
((nil . ((eglot-luau-rojo-project-path . "plugin.project.json")
|
||||||
|
(eglot-luau-rojo-sourcemap-enabled . 't))))
|
||||||
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.lua linguist-language=Luau
|
||||||
6
.github/workflows/changelog.yml
vendored
@@ -11,12 +11,12 @@ jobs:
|
|||||||
name: Check Actions
|
name: Check Actions
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Changelog check
|
- name: Changelog check
|
||||||
uses: Zomzog/changelog-checker@v1.3.0
|
uses: Zomzog/changelog-checker@v1.3.0
|
||||||
with:
|
with:
|
||||||
fileName: CHANGELOG.md
|
fileName: CHANGELOG.md
|
||||||
noChangelogLabel: skip changelog
|
noChangelogLabel: skip changelog
|
||||||
checkNotification: Simple
|
checkNotification: Simple
|
||||||
env:
|
env:
|
||||||
|
|||||||
99
.github/workflows/ci.yml
vendored
@@ -16,27 +16,24 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-22.04, windows-latest, macos-latest, windows-11-arm, ubuntu-22.04-arm]
|
||||||
|
|
||||||
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: Restore Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: actions/cache/restore@v4
|
||||||
|
|
||||||
- name: Setup Aftman
|
|
||||||
uses: ok-nick/setup-aftman@v0.3.0
|
|
||||||
with:
|
with:
|
||||||
version: 'v0.2.7'
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --locked --verbose
|
run: cargo build --locked --verbose
|
||||||
@@ -44,63 +41,93 @@ jobs:
|
|||||||
- name: Test
|
- name: Test
|
||||||
run: cargo test --locked --verbose
|
run: cargo test --locked --verbose
|
||||||
|
|
||||||
|
- name: Save Rust Cache
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
msrv:
|
msrv:
|
||||||
name: Check MSRV
|
name: Check MSRV
|
||||||
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.83.0
|
||||||
with:
|
|
||||||
toolchain: 1.70.0
|
|
||||||
override: true
|
|
||||||
profile: minimal
|
|
||||||
|
|
||||||
- name: Rust cache
|
- name: Restore Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: actions/cache/restore@v4
|
||||||
|
|
||||||
- name: Setup Aftman
|
|
||||||
uses: ok-nick/setup-aftman@v0.3.0
|
|
||||||
with:
|
with:
|
||||||
version: 'v0.2.7'
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --locked --verbose
|
run: cargo build --locked --verbose
|
||||||
|
|
||||||
|
- name: Save Rust Cache
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Rustfmt, Clippy, & Stylua
|
name: Rustfmt, Clippy, Stylua, & Selene
|
||||||
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: Restore Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: actions/cache/restore@v4
|
||||||
|
|
||||||
- name: Setup Aftman
|
|
||||||
uses: ok-nick/setup-aftman@v0.3.0
|
|
||||||
with:
|
with:
|
||||||
version: 'v0.2.7'
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
|
- name: Setup Rokit
|
||||||
|
uses: CompeyDev/setup-rokit@v0.1.2
|
||||||
|
with:
|
||||||
|
version: 'v1.1.0'
|
||||||
|
|
||||||
- name: Stylua
|
- name: Stylua
|
||||||
run: stylua --check plugin/src
|
run: stylua --check plugin/src
|
||||||
|
|
||||||
|
- name: Selene
|
||||||
|
run: selene plugin/src
|
||||||
|
|
||||||
- name: Rustfmt
|
- name: Rustfmt
|
||||||
run: cargo fmt -- --check
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy
|
run: cargo clippy
|
||||||
|
|
||||||
|
- name: Save Rust Cache
|
||||||
|
uses: actions/cache/save@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|||||||
124
.github/workflows/release.yml
vendored
@@ -8,51 +8,39 @@ 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
|
||||||
|
|
||||||
- name: Setup Aftman
|
- name: Setup Rokit
|
||||||
uses: ok-nick/setup-aftman@v0.1.0
|
uses: CompeyDev/setup-rokit@v0.1.2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
version: 'v1.1.0'
|
||||||
trust-check: false
|
|
||||||
version: 'v0.2.6'
|
|
||||||
|
|
||||||
- name: Build Plugin
|
- name: Build Plugin
|
||||||
run: rojo build plugin --output Rojo.rbxm
|
run: rojo build plugin.project.json --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
|
||||||
@@ -65,15 +53,25 @@ jobs:
|
|||||||
# https://doc.rust-lang.org/rustc/platform-support.html
|
# https://doc.rust-lang.org/rustc/platform-support.html
|
||||||
include:
|
include:
|
||||||
- host: linux
|
- host: linux
|
||||||
os: ubuntu-20.04
|
os: ubuntu-22.04
|
||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu
|
||||||
label: linux-x86_64
|
label: linux-x86_64
|
||||||
|
|
||||||
|
- host: linux
|
||||||
|
os: ubuntu-22.04-arm
|
||||||
|
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-11-arm
|
||||||
|
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,70 +87,64 @@ 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: Restore Rust Cache
|
||||||
uses: ok-nick/setup-aftman@v0.1.0
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
path: |
|
||||||
trust-check: false
|
~/.cargo/registry
|
||||||
version: 'v0.2.6'
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Build Release
|
- name: Build Release
|
||||||
run: cargo build --release --locked --verbose --target ${{ matrix.target }}
|
run: cargo build --release --locked --verbose --target ${{ matrix.target }}
|
||||||
env:
|
|
||||||
# Build into a known directory so we can find our build artifact more
|
|
||||||
# easily.
|
|
||||||
CARGO_TARGET_DIR: output
|
|
||||||
|
|
||||||
# On platforms that use OpenSSL, ensure it is statically linked to
|
- name: Save Rust Cache
|
||||||
# make binaries more portable.
|
uses: actions/cache/save@v4
|
||||||
OPENSSL_STATIC: 1
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
- name: Create Release Archive
|
- name: Generate Artifact Name
|
||||||
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 "target/${{ 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 "target/${{ 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 }}
|
||||||
|
|||||||
8
.gitignore
vendored
@@ -10,8 +10,8 @@
|
|||||||
/*.rbxl
|
/*.rbxl
|
||||||
/*.rbxlx
|
/*.rbxlx
|
||||||
|
|
||||||
# Test places for the Roblox Studio Plugin
|
# Sourcemap for the Rojo plugin (for better intellisense)
|
||||||
/plugin/*.rbxlx
|
/sourcemap.json
|
||||||
|
|
||||||
# Roblox Studio holds 'lock' files on places
|
# Roblox Studio holds 'lock' files on places
|
||||||
*.rbxl.lock
|
*.rbxl.lock
|
||||||
@@ -19,3 +19,7 @@
|
|||||||
|
|
||||||
# Snapshot files from the 'insta' Rust crate
|
# Snapshot files from the 'insta' Rust crate
|
||||||
**/*.snap.new
|
**/*.snap.new
|
||||||
|
|
||||||
|
# Macos file system junk
|
||||||
|
._*
|
||||||
|
.DS_STORE
|
||||||
|
|||||||
8
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"JohnnyMorganz.luau-lsp",
|
||||||
|
"JohnnyMorganz.stylua",
|
||||||
|
"Kampfkarren.selene-vscode",
|
||||||
|
"rust-lang.rust-analyzer"
|
||||||
|
]
|
||||||
|
}
|
||||||
4
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"luau-lsp.sourcemap.rojoProjectFile": "plugin.project.json",
|
||||||
|
"luau-lsp.sourcemap.autogenerate": true
|
||||||
|
}
|
||||||
194
CHANGELOG.md
@@ -1,6 +1,196 @@
|
|||||||
# Rojo Changelog
|
# Rojo Changelog
|
||||||
|
|
||||||
## Unreleased Changes
|
## 7.6.0 - October 10th, 2025
|
||||||
|
* Added flag to `rojo init` to skip initializing a git repository ([#1122])
|
||||||
|
* Added fallback method for when an Instance can't be synced through normal means ([#1030])
|
||||||
|
This should make it possible to sync `MeshParts` and `Unions`!
|
||||||
|
|
||||||
|
The fallback involves deleting and recreating Instances. This will break
|
||||||
|
properties that reference them that Rojo does not know about, so be weary.
|
||||||
|
|
||||||
|
* Add auto-reconnect and improve UX for sync reminders ([#1096])
|
||||||
|
* Add support for syncing `yml` and `yaml` files (behaves similar to JSON and TOML) ([#1093])
|
||||||
|
* Fixed colors of Table diff ([#1084])
|
||||||
|
* Fixed `sourcemap` command outputting paths with OS-specific path separators ([#1085])
|
||||||
|
* Fixed nil -> nil properties showing up as failing to sync in plugin's patch visualizer ([#1081])
|
||||||
|
* Changed the background of the server's in-browser UI to be gray instead of white ([#1080])
|
||||||
|
* Fixed `Auto Connect Playtest Server` no longer functioning due to Roblox change ([#1066])
|
||||||
|
* Added an update indicator to the version header when a new version of the plugin is available. ([#1069])
|
||||||
|
* Added `--absolute` flag to the sourcemap subcommand, which will emit absolute paths instead of relative paths. ([#1092])
|
||||||
|
* Fixed applying `gameId` and `placeId` before initial sync was accepted ([#1104])
|
||||||
|
|
||||||
|
[#1122]: https://github.com/rojo-rbx/rojo/pull/1122
|
||||||
|
[#1030]: https://github.com/rojo-rbx/rojo/pull/1030
|
||||||
|
[#1096]: https://github.com/rojo-rbx/rojo/pull/1096
|
||||||
|
[#1093]: https://github.com/rojo-rbx/rojo/pull/1093
|
||||||
|
[#1084]: https://github.com/rojo-rbx/rojo/pull/1084
|
||||||
|
[#1085]: https://github.com/rojo-rbx/rojo/pull/1085
|
||||||
|
[#1081]: https://github.com/rojo-rbx/rojo/pull/1081
|
||||||
|
[#1080]: https://github.com/rojo-rbx/rojo/pull/1080
|
||||||
|
[#1066]: https://github.com/rojo-rbx/rojo/pull/1066
|
||||||
|
[#1069]: https://github.com/rojo-rbx/rojo/pull/1069
|
||||||
|
[#1092]: https://github.com/rojo-rbx/rojo/pull/1092
|
||||||
|
[#1104]: https://github.com/rojo-rbx/rojo/pull/1104
|
||||||
|
|
||||||
|
## 7.5.1 - April 25th, 2025
|
||||||
|
* Fixed output spam related to `Instance.Capabilities` in the plugin
|
||||||
|
|
||||||
|
## 7.5.0 - April 25th, 2025
|
||||||
|
* Fixed an edge case that caused model pivots to not be built correctly in some cases ([#1027])
|
||||||
|
* Add `blockedPlaceIds` project config field to allow blocking place ids from being live synced ([#1021])
|
||||||
|
* Adds support for `.plugin.lua(u)` files - this applies the `Plugin` RunContext. ([#1008])
|
||||||
|
* Added support for Roblox's `Content` type. This replaces the old `Content` type with `ContentId` to reflect Roblox's change.
|
||||||
|
If you were previously using the fully-qualified syntax for `Content` you will need to switch it to `ContentId`.
|
||||||
|
* Added support for `Enum` attributes
|
||||||
|
* Significantly improved performance of `.rbxm` parsing
|
||||||
|
* Support for a `$schema` field in all special JSON files (`.project.json`, `.model.json`, and `.meta.json`) ([#974])
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
As an example, here is a `model.json` for an ObjectValue that refers to itself:
|
||||||
|
|
||||||
|
```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])
|
||||||
|
* The sync reminder notification will now tell you what was last synced and when ([#987])
|
||||||
|
* Fixed notification and tooltip text sometimes getting cut off ([#988])
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
| `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! |
|
||||||
|
|
||||||
|
Additionally, there are `use` values for specific script types ([#909]):
|
||||||
|
|
||||||
|
| `use` value | script type |
|
||||||
|
|:-------------------------|:---------------------------------------|
|
||||||
|
| `legacyServerScript` | `Script` with `Enum.RunContext.Legacy` |
|
||||||
|
| `legacyClientScript` | `LocalScript` |
|
||||||
|
| `runContextServerScript` | `Script` with `Enum.RunContext.Server` |
|
||||||
|
| `runContextClientScript` | `Script` with `Enum.RunContext.Client` |
|
||||||
|
| `pluginScript` | `Script` with `Enum.RunContext.Plugin` |
|
||||||
|
|
||||||
|
**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
|
||||||
|
[#909]: https://github.com/rojo-rbx/rojo/pull/909
|
||||||
|
[#911]: https://github.com/rojo-rbx/rojo/pull/911
|
||||||
|
[#915]: https://github.com/rojo-rbx/rojo/pull/915
|
||||||
|
[#974]: https://github.com/rojo-rbx/rojo/pull/974
|
||||||
|
[#987]: https://github.com/rojo-rbx/rojo/pull/987
|
||||||
|
[#988]: https://github.com/rojo-rbx/rojo/pull/988
|
||||||
|
[#1008]: https://github.com/rojo-rbx/rojo/pull/1008
|
||||||
|
[#1021]: https://github.com/rojo-rbx/rojo/pull/1021
|
||||||
|
[#1027]: https://github.com/rojo-rbx/rojo/pull/1027
|
||||||
|
|
||||||
|
## [7.4.4] - August 22nd, 2024
|
||||||
|
* 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`.
|
||||||
|
|
||||||
|
## [7.4.3] - August 6th, 2024
|
||||||
|
* 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]
|
||||||
|
|
||||||
|
[#955]: https://github.com/rojo-rbx/rojo/pull/955
|
||||||
|
|
||||||
|
## [7.4.2] - July 23, 2024
|
||||||
|
* Added Never option to Confirmation ([#893])
|
||||||
|
* Fixed removing trailing newlines ([#903])
|
||||||
|
* Updated the internal property database, correcting an issue with `SurfaceAppearance.Color` that was reported [here][Surface_Appearance_Color_1] and [here][Surface_Appearance_Color_2] ([#948])
|
||||||
|
|
||||||
|
[#893]: https://github.com/rojo-rbx/rojo/pull/893
|
||||||
|
[#903]: https://github.com/rojo-rbx/rojo/pull/903
|
||||||
|
[#948]: https://github.com/rojo-rbx/rojo/pull/948
|
||||||
|
[Surface_Appearance_Color_1]: https://devforum.roblox.com/t/jailbreak-custom-character-turned-shiny-black-no-texture/3075563
|
||||||
|
[Surface_Appearance_Color_2]: https://devforum.roblox.com/t/surfaceappearance-not-displaying-correctly/3075588
|
||||||
|
|
||||||
|
## [7.4.1] - February 20, 2024
|
||||||
|
* 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
|
||||||
|
are named as expect (e.g. `foo.project.json` becomes an Instance named `foo`)
|
||||||
|
|
||||||
|
There is no change in behavior if `name` is set.
|
||||||
|
* Fixed incorrect results when building model pivots ([#865])
|
||||||
|
* Fixed incorrect results when serving model pivots ([#868])
|
||||||
|
* Rojo now converts any line endings to LF, preventing spurious diffs when syncing Lua files on Windows ([#854])
|
||||||
|
* Fixed Rojo plugin failing to connect when project contains certain unreadable properties ([#848])
|
||||||
|
* Fixed various cases where patch visualizer would not display sync failures ([#845], [#844])
|
||||||
|
* Fixed http error handling so Rojo can be used in Github Codespaces ([#847])
|
||||||
|
|
||||||
|
[#848]: https://github.com/rojo-rbx/rojo/pull/848
|
||||||
|
[#845]: https://github.com/rojo-rbx/rojo/pull/845
|
||||||
|
[#844]: https://github.com/rojo-rbx/rojo/pull/844
|
||||||
|
[#847]: https://github.com/rojo-rbx/rojo/pull/847
|
||||||
|
[#854]: https://github.com/rojo-rbx/rojo/pull/854
|
||||||
|
[#865]: https://github.com/rojo-rbx/rojo/pull/865
|
||||||
|
[#868]: https://github.com/rojo-rbx/rojo/pull/868
|
||||||
|
[#870]: https://github.com/rojo-rbx/rojo/pull/870
|
||||||
|
|
||||||
## [7.4.0] - January 16, 2024
|
## [7.4.0] - January 16, 2024
|
||||||
* Improved the visualization for array properties like Tags ([#829])
|
* Improved the visualization for array properties like Tags ([#829])
|
||||||
@@ -538,7 +728,7 @@ This is a general maintenance release for the Rojo 0.5.x release series.
|
|||||||
|
|
||||||
## [0.5.0 Alpha 11](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.11) (May 29, 2019)
|
## [0.5.0 Alpha 11](https://github.com/rojo-rbx/rojo/releases/tag/v0.5.0-alpha.11) (May 29, 2019)
|
||||||
* Added support for implicit property values in JSON model files ([#154](https://github.com/rojo-rbx/rojo/pull/154))
|
* Added support for implicit property values in JSON model files ([#154](https://github.com/rojo-rbx/rojo/pull/154))
|
||||||
* `Content` propertyes can now be specified in projects and model files as regular string literals.
|
* `Content` properties can now be specified in projects and model files as regular string literals.
|
||||||
* Added support for `BrickColor` properties.
|
* Added support for `BrickColor` properties.
|
||||||
* Added support for properties added in client release 384, like `Lighting.Technology` being set to `"ShadowMap"`.
|
* Added support for properties added in client release 384, like `Lighting.Technology` being set to `"ShadowMap"`.
|
||||||
* Improved performance when working with XML models and places
|
* Improved performance when working with XML models and places
|
||||||
|
|||||||
@@ -15,12 +15,29 @@ You'll want these tools to work on Rojo:
|
|||||||
|
|
||||||
* Latest stable Rust compiler
|
* Latest stable Rust compiler
|
||||||
* Latest stable [Rojo](https://github.com/rojo-rbx/rojo)
|
* Latest stable [Rojo](https://github.com/rojo-rbx/rojo)
|
||||||
* [Foreman](https://github.com/Roblox/foreman)
|
* [Rokit](https://github.com/rojo-rbx/rokit)
|
||||||
|
* [Luau Language Server](https://github.com/JohnnyMorganz/luau-lsp) (Only needed if working on the Studio plugin.)
|
||||||
|
|
||||||
|
When working on the Studio plugin, we recommend using this command to automatically rebuild the plugin when you save a change:
|
||||||
|
|
||||||
|
*(Make sure you've enabled the Studio setting to reload plugins on file change!)*
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/watch-build-plugin.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run the plugin's unit tests with the following:
|
||||||
|
|
||||||
|
*(Make sure you have `run-in-roblox` installed first!)*
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash scripts/unit-test-plugin.sh
|
||||||
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
Documentation impacts way more people than the individual lines of code we write.
|
Documentation impacts way more people than the individual lines of code we write.
|
||||||
|
|
||||||
If you find any problems in documentation, including typos, bad grammar, misleading phrasing, or missing content, feel free to file issues and pull requests to fix them.
|
If you find any problems in the documentation, including typos, bad grammar, misleading phrasing, or missing content, feel free to file issues and pull requests to fix them.
|
||||||
|
|
||||||
## Bug Reports and Feature Requests
|
## Bug Reports and Feature Requests
|
||||||
Most of the tools around Rojo try to be clear when an issue is a bug. Even if they aren't, sometimes things don't work quite right.
|
Most of the tools around Rojo try to be clear when an issue is a bug. Even if they aren't, sometimes things don't work quite right.
|
||||||
|
|||||||
1233
Cargo.lock
generated
102
Cargo.toml
@@ -1,8 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rojo"
|
name = "rojo"
|
||||||
version = "7.4.0"
|
version = "7.6.0"
|
||||||
rust-version = "1.70.0"
|
rust-version = "1.83"
|
||||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
authors = [
|
||||||
|
"Lucien Greathouse <me@lpghatguy.com>",
|
||||||
|
"Micah Reid <git@dekkonot.com>",
|
||||||
|
"Ken Loeffler <kenloef@gmail.com>",
|
||||||
|
]
|
||||||
description = "Enables professional-grade development tools for Roblox developers"
|
description = "Enables professional-grade development tools for Roblox developers"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
homepage = "https://rojo.space"
|
homepage = "https://rojo.space"
|
||||||
@@ -26,7 +30,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/*"]
|
||||||
@@ -40,7 +46,7 @@ name = "build"
|
|||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
memofs = { version = "0.2.0", path = "crates/memofs" }
|
memofs = { version = "0.3.0", path = "crates/memofs" }
|
||||||
|
|
||||||
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
# These dependencies can be uncommented when working on rbx-dom simultaneously
|
||||||
# rbx_binary = { path = "../rbx-dom/rbx_binary" }
|
# rbx_binary = { path = "../rbx-dom/rbx_binary" }
|
||||||
@@ -49,67 +55,67 @@ memofs = { version = "0.2.0", path = "crates/memofs" }
|
|||||||
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
# rbx_reflection_database = { path = "../rbx-dom/rbx_reflection_database" }
|
||||||
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
# rbx_xml = { path = "../rbx-dom/rbx_xml" }
|
||||||
|
|
||||||
rbx_binary = "0.7.4"
|
rbx_binary = "2.0.0"
|
||||||
rbx_dom_weak = "2.7.0"
|
rbx_dom_weak = "4.0.0"
|
||||||
rbx_reflection = "4.5.0"
|
rbx_reflection = "6.0.0"
|
||||||
rbx_reflection_database = "0.2.10"
|
rbx_reflection_database = "2.0.0"
|
||||||
rbx_xml = "0.13.3"
|
rbx_xml = "2.0.0"
|
||||||
|
|
||||||
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"
|
num_cpus = "1.16.0"
|
||||||
num_cpus = "1.15.0"
|
opener = "0.5.2"
|
||||||
opener = "0.5.0"
|
rayon = "1.9.0"
|
||||||
rayon = "1.7.0"
|
reqwest = { version = "0.11.24", default-features = false, features = [
|
||||||
reqwest = { version = "0.11.10", 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 }
|
yaml-rust2 = "0.10.3"
|
||||||
|
data-encoding = "2.8.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.10.1"
|
winreg = "0.10.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
memofs = { version = "0.2.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>
|
||||||
@@ -40,7 +40,7 @@ Check out our [contribution guide](CONTRIBUTING.md) for detailed instructions fo
|
|||||||
|
|
||||||
Pull requests are welcome!
|
Pull requests are welcome!
|
||||||
|
|
||||||
Rojo supports Rust 1.70.0 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
Rojo supports Rust 1.83 and newer. The minimum supported version of Rust is based on the latest versions of the dependencies that Rojo has.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
Rojo is available under the terms of the Mozilla Public License, Version 2.0. See [LICENSE.txt](LICENSE.txt) for details.
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
[tools]
|
|
||||||
rojo = "rojo-rbx/rojo@7.3.0"
|
|
||||||
selene = "Kampfkarren/selene@0.25.0"
|
|
||||||
stylua = "JohnnyMorganz/stylua@0.18.2"
|
|
||||||
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"
|
|
||||||
|
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 |
@@ -17,6 +17,10 @@ html {
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #e7e7e7
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width:100%;
|
max-width:100%;
|
||||||
max-height:100%;
|
max-height:100%;
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ return {
|
|||||||
hello = function()
|
hello = function()
|
||||||
print("Hello world, from {project_name}!")
|
print("Hello world, from {project_name}!")
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
print("Hello world, from client!")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
print("Hello world, from server!")
|
||||||
3
assets/project-templates/place/src/shared/Hello.luau
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
return function()
|
||||||
|
print("Hello, world!")
|
||||||
|
end
|
||||||
1
assets/project-templates/plugin/src/init.server.luau
Normal file
@@ -0,0 +1 @@
|
|||||||
|
print("Hello world, from plugin!")
|
||||||
40
build.rs
@@ -20,6 +20,10 @@ fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
|||||||
|
|
||||||
let file_name = entry.file_name().to_str().unwrap().to_owned();
|
let file_name = entry.file_name().to_str().unwrap().to_owned();
|
||||||
|
|
||||||
|
if file_name.starts_with(".git") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// We can skip any TestEZ test files since they aren't necessary for
|
// We can skip any TestEZ test files since they aren't necessary for
|
||||||
// the plugin to run.
|
// the plugin to run.
|
||||||
if file_name.ends_with(".spec.lua") || file_name.ends_with(".spec.luau") {
|
if file_name.ends_with(".spec.lua") || file_name.ends_with(".spec.luau") {
|
||||||
@@ -41,33 +45,39 @@ fn snapshot_from_fs_path(path: &Path) -> io::Result<VfsSnapshot> {
|
|||||||
fn main() -> Result<(), anyhow::Error> {
|
fn main() -> Result<(), anyhow::Error> {
|
||||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||||
|
|
||||||
let root_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
|
let root_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
|
||||||
let plugin_root = PathBuf::from(root_dir).join("plugin");
|
let plugin_dir = root_dir.join("plugin");
|
||||||
|
let templates_dir = root_dir.join("assets").join("project-templates");
|
||||||
|
|
||||||
let our_version = Version::parse(env::var_os("CARGO_PKG_VERSION").unwrap().to_str().unwrap())?;
|
let our_version = Version::parse(env::var_os("CARGO_PKG_VERSION").unwrap().to_str().unwrap())?;
|
||||||
let plugin_version =
|
let plugin_version =
|
||||||
Version::parse(fs::read_to_string(plugin_root.join("Version.txt"))?.trim())?;
|
Version::parse(fs::read_to_string(plugin_dir.join("Version.txt"))?.trim())?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
our_version, plugin_version,
|
our_version, plugin_version,
|
||||||
"plugin version does not match Cargo version"
|
"plugin version does not match Cargo version"
|
||||||
);
|
);
|
||||||
|
|
||||||
let snapshot = VfsSnapshot::dir(hashmap! {
|
let template_snapshot = snapshot_from_fs_path(&templates_dir)?;
|
||||||
"default.project.json" => snapshot_from_fs_path(&plugin_root.join("default.project.json"))?,
|
|
||||||
"fmt" => snapshot_from_fs_path(&plugin_root.join("fmt"))?,
|
let plugin_snapshot = VfsSnapshot::dir(hashmap! {
|
||||||
"http" => snapshot_from_fs_path(&plugin_root.join("http"))?,
|
"default.project.json" => snapshot_from_fs_path(&root_dir.join("plugin.project.json"))?,
|
||||||
"log" => snapshot_from_fs_path(&plugin_root.join("log"))?,
|
"plugin" => VfsSnapshot::dir(hashmap! {
|
||||||
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_root.join("rbx_dom_lua"))?,
|
"fmt" => snapshot_from_fs_path(&plugin_dir.join("fmt"))?,
|
||||||
"src" => snapshot_from_fs_path(&plugin_root.join("src"))?,
|
"http" => snapshot_from_fs_path(&plugin_dir.join("http"))?,
|
||||||
"Packages" => snapshot_from_fs_path(&plugin_root.join("Packages"))?,
|
"log" => snapshot_from_fs_path(&plugin_dir.join("log"))?,
|
||||||
"Version.txt" => snapshot_from_fs_path(&plugin_root.join("Version.txt"))?,
|
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_dir.join("rbx_dom_lua"))?,
|
||||||
|
"src" => snapshot_from_fs_path(&plugin_dir.join("src"))?,
|
||||||
|
"Packages" => snapshot_from_fs_path(&plugin_dir.join("Packages"))?,
|
||||||
|
"Version.txt" => snapshot_from_fs_path(&plugin_dir.join("Version.txt"))?,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
let out_path = Path::new(&out_dir).join("plugin.bincode");
|
let template_file = File::create(Path::new(&out_dir).join("templates.bincode"))?;
|
||||||
let out_file = File::create(out_path)?;
|
let plugin_file = File::create(Path::new(&out_dir).join("plugin.bincode"))?;
|
||||||
|
|
||||||
bincode::serialize_into(out_file, &snapshot)?;
|
bincode::serialize_into(plugin_file, &plugin_snapshot)?;
|
||||||
|
bincode::serialize_into(template_file, &template_snapshot)?;
|
||||||
|
|
||||||
println!("cargo:rerun-if-changed=build/windows/rojo-manifest.rc");
|
println!("cargo:rerun-if-changed=build/windows/rojo-manifest.rc");
|
||||||
println!("cargo:rerun-if-changed=build/windows/rojo.manifest");
|
println!("cargo:rerun-if-changed=build/windows/rojo.manifest");
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
# memofs Changelog
|
# memofs Changelog
|
||||||
|
|
||||||
## Unreleased Changes
|
## Unreleased Changes
|
||||||
|
|
||||||
|
## 0.3.0 (2024-03-15)
|
||||||
* Changed `StdBackend` file watching component to use minimal recursive watches. [#830]
|
* Changed `StdBackend` file watching component to use minimal recursive watches. [#830]
|
||||||
|
* Added `Vfs::read_to_string` and `Vfs::read_to_string_lf_normalized` [#854]
|
||||||
|
|
||||||
[#830]: https://github.com/rojo-rbx/rojo/pull/830
|
[#830]: https://github.com/rojo-rbx/rojo/pull/830
|
||||||
|
[#854]: https://github.com/rojo-rbx/rojo/pull/854
|
||||||
|
|
||||||
## 0.2.0 (2021-08-23)
|
## 0.2.0 (2021-08-23)
|
||||||
* Updated to `crossbeam-channel` 0.5.1.
|
* Updated to `crossbeam-channel` 0.5.1.
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "memofs"
|
name = "memofs"
|
||||||
description = "Virtual filesystem with configurable backends."
|
description = "Virtual filesystem with configurable backends."
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
|
authors = [
|
||||||
|
"Lucien Greathouse <me@lpghatguy.com>",
|
||||||
|
"Micah Reid <git@dekkonot.com>",
|
||||||
|
"Ken Loeffler <kenloef@gmail.com>",
|
||||||
|
]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -11,7 +15,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"] }
|
||||||
|
|||||||
@@ -228,23 +228,17 @@ impl VfsBackend for InMemoryFs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn must_be_file<T>(path: &Path) -> io::Result<T> {
|
fn must_be_file<T>(path: &Path) -> io::Result<T> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other(format!(
|
||||||
io::ErrorKind::Other,
|
"path {} was a directory, but must be a file",
|
||||||
format!(
|
path.display()
|
||||||
"path {} was a directory, but must be a file",
|
)))
|
||||||
path.display()
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn must_be_dir<T>(path: &Path) -> io::Result<T> {
|
fn must_be_dir<T>(path: &Path) -> io::Result<T> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other(format!(
|
||||||
io::ErrorKind::Other,
|
"path {} was a file, but must be a directory",
|
||||||
format!(
|
path.display()
|
||||||
"path {} was a file, but must be a directory",
|
)))
|
||||||
path.display()
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn not_found<T>(path: &Path) -> io::Result<T> {
|
fn not_found<T>(path: &Path) -> io::Result<T> {
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ mod noop_backend;
|
|||||||
mod snapshot;
|
mod snapshot;
|
||||||
mod std_backend;
|
mod std_backend;
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
use std::{io, str};
|
||||||
|
|
||||||
pub use in_memory_fs::InMemoryFs;
|
pub use in_memory_fs::InMemoryFs;
|
||||||
pub use noop_backend::NoopBackend;
|
pub use noop_backend::NoopBackend;
|
||||||
@@ -155,6 +155,24 @@ impl VfsInner {
|
|||||||
Ok(Arc::new(contents))
|
Ok(Arc::new(contents))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_to_string<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Arc<String>> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let contents = self.backend.read(path)?;
|
||||||
|
|
||||||
|
if self.watch_enabled {
|
||||||
|
self.backend.watch(path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contents_str = str::from_utf8(&contents).map_err(|_| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
format!("File was not valid UTF-8: {}", path.display()),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Arc::new(contents_str.into()))
|
||||||
|
}
|
||||||
|
|
||||||
fn write<P: AsRef<Path>, C: AsRef<[u8]>>(&mut self, path: P, contents: C) -> io::Result<()> {
|
fn write<P: AsRef<Path>, C: AsRef<[u8]>>(&mut self, path: P, contents: C) -> io::Result<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let contents = contents.as_ref();
|
let contents = contents.as_ref();
|
||||||
@@ -258,6 +276,33 @@ impl Vfs {
|
|||||||
self.inner.lock().unwrap().read(path)
|
self.inner.lock().unwrap().read(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a file from the VFS (or from the underlying backend if it isn't
|
||||||
|
/// resident) into a string.
|
||||||
|
///
|
||||||
|
/// Roughly equivalent to [`std::fs::read_to_string`][std::fs::read_to_string].
|
||||||
|
///
|
||||||
|
/// [std::fs::read_to_string]: https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html
|
||||||
|
#[inline]
|
||||||
|
pub fn read_to_string<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<String>> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
self.inner.lock().unwrap().read_to_string(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a file from the VFS (or the underlying backend if it isn't
|
||||||
|
/// resident) into a string, and normalize its line endings to LF.
|
||||||
|
///
|
||||||
|
/// Roughly equivalent to [`std::fs::read_to_string`][std::fs::read_to_string], but also performs
|
||||||
|
/// line ending normalization.
|
||||||
|
///
|
||||||
|
/// [std::fs::read_to_string]: https://doc.rust-lang.org/stable/std/fs/fn.read_to_string.html
|
||||||
|
#[inline]
|
||||||
|
pub fn read_to_string_lf_normalized<P: AsRef<Path>>(&self, path: P) -> io::Result<Arc<String>> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let contents = self.inner.lock().unwrap().read_to_string(path)?;
|
||||||
|
|
||||||
|
Ok(contents.replace("\r\n", "\n").into())
|
||||||
|
}
|
||||||
|
|
||||||
/// Write a file to the VFS and the underlying backend.
|
/// Write a file to the VFS and the underlying backend.
|
||||||
///
|
///
|
||||||
/// Roughly equivalent to [`std::fs::write`][std::fs::write].
|
/// Roughly equivalent to [`std::fs::write`][std::fs::write].
|
||||||
@@ -428,3 +473,23 @@ impl VfsLock<'_> {
|
|||||||
self.inner.commit_event(event)
|
self.inner.commit_event(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{InMemoryFs, Vfs, VfsSnapshot};
|
||||||
|
|
||||||
|
/// https://github.com/rojo-rbx/rojo/issues/899
|
||||||
|
#[test]
|
||||||
|
fn read_to_string_lf_normalized_keeps_trailing_newline() {
|
||||||
|
let mut imfs = InMemoryFs::new();
|
||||||
|
imfs.load_snapshot("test", VfsSnapshot::file("bar\r\nfoo\r\n\r\n"))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let vfs = Vfs::new(imfs);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
vfs.read_to_string_lf_normalized("test").unwrap().as_str(),
|
||||||
|
"bar\nfoo\n\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,45 +15,27 @@ impl NoopBackend {
|
|||||||
|
|
||||||
impl VfsBackend for NoopBackend {
|
impl VfsBackend for NoopBackend {
|
||||||
fn read(&mut self, _path: &Path) -> io::Result<Vec<u8>> {
|
fn read(&mut self, _path: &Path) -> io::Result<Vec<u8>> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, _path: &Path, _data: &[u8]) -> io::Result<()> {
|
fn write(&mut self, _path: &Path, _data: &[u8]) -> io::Result<()> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_dir(&mut self, _path: &Path) -> io::Result<ReadDir> {
|
fn read_dir(&mut self, _path: &Path) -> io::Result<ReadDir> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_file(&mut self, _path: &Path) -> io::Result<()> {
|
fn remove_file(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_dir_all(&mut self, _path: &Path) -> io::Result<()> {
|
fn remove_dir_all(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn metadata(&mut self, _path: &Path) -> io::Result<Metadata> {
|
fn metadata(&mut self, _path: &Path) -> io::Result<Metadata> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
fn event_receiver(&self) -> crossbeam_channel::Receiver<VfsEvent> {
|
||||||
@@ -61,17 +43,11 @@ impl VfsBackend for NoopBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn watch(&mut self, _path: &Path) -> io::Result<()> {
|
fn watch(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unwatch(&mut self, _path: &Path) -> io::Result<()> {
|
fn unwatch(&mut self, _path: &Path) -> io::Result<()> {
|
||||||
Err(io::Error::new(
|
Err(io::Error::other("NoopBackend doesn't do anything"))
|
||||||
io::ErrorKind::Other,
|
|
||||||
"NoopBackend doesn't do anything",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,15 +109,13 @@ impl VfsBackend for StdBackend {
|
|||||||
self.watches.insert(path.to_path_buf());
|
self.watches.insert(path.to_path_buf());
|
||||||
self.watcher
|
self.watcher
|
||||||
.watch(path, RecursiveMode::Recursive)
|
.watch(path, RecursiveMode::Recursive)
|
||||||
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner))
|
.map_err(io::Error::other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unwatch(&mut self, path: &Path) -> io::Result<()> {
|
fn unwatch(&mut self, path: &Path) -> io::Result<()> {
|
||||||
self.watches.remove(path);
|
self.watches.remove(path);
|
||||||
self.watcher
|
self.watcher.unwatch(path).map_err(io::Error::other)
|
||||||
.unwatch(path)
|
|
||||||
.map_err(|inner| io::Error::new(io::ErrorKind::Other, inner))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -5,19 +5,13 @@ use serde::Serialize;
|
|||||||
/// Enables redacting any value that serializes as a string.
|
/// Enables redacting any value that serializes as a string.
|
||||||
///
|
///
|
||||||
/// Used for transforming Rojo instance IDs into something deterministic.
|
/// Used for transforming Rojo instance IDs into something deterministic.
|
||||||
|
#[derive(Default)]
|
||||||
pub struct RedactionMap {
|
pub struct RedactionMap {
|
||||||
ids: HashMap<String, usize>,
|
ids: HashMap<String, usize>,
|
||||||
last_id: usize,
|
last_id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RedactionMap {
|
impl RedactionMap {
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
ids: HashMap::new(),
|
|
||||||
last_id: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_redacted_value(&self, id: impl ToString) -> Option<String> {
|
pub fn get_redacted_value(&self, id: impl ToString) -> Option<String> {
|
||||||
let id = id.to_string();
|
let id = id.to_string();
|
||||||
|
|
||||||
@@ -28,6 +22,12 @@ impl RedactionMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the numeric ID that was assigned to the provided value,
|
||||||
|
/// if one exists.
|
||||||
|
pub fn get_id_for_value(&self, value: impl ToString) -> Option<usize> {
|
||||||
|
self.ids.get(&value.to_string()).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn intern(&mut self, id: impl ToString) {
|
pub fn intern(&mut self, id: impl ToString) {
|
||||||
let last_id = &mut self.last_id;
|
let last_id = &mut self.last_id;
|
||||||
|
|
||||||
|
|||||||
@@ -3,25 +3,25 @@
|
|||||||
"tree": {
|
"tree": {
|
||||||
"$className": "Folder",
|
"$className": "Folder",
|
||||||
"Plugin": {
|
"Plugin": {
|
||||||
"$path": "src"
|
"$path": "plugin/src"
|
||||||
},
|
},
|
||||||
"Packages": {
|
"Packages": {
|
||||||
"$path": "Packages",
|
"$path": "plugin/Packages",
|
||||||
"Log": {
|
"Log": {
|
||||||
"$path": "log"
|
"$path": "plugin/log"
|
||||||
},
|
},
|
||||||
"Http": {
|
"Http": {
|
||||||
"$path": "http"
|
"$path": "plugin/http"
|
||||||
},
|
},
|
||||||
"Fmt": {
|
"Fmt": {
|
||||||
"$path": "fmt"
|
"$path": "plugin/fmt"
|
||||||
},
|
},
|
||||||
"RbxDom": {
|
"RbxDom": {
|
||||||
"$path": "rbx_dom_lua"
|
"$path": "plugin/rbx_dom_lua"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Version": {
|
"Version": {
|
||||||
"$path": "Version.txt"
|
"$path": "plugin/Version.txt"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
7.4.0
|
7.6.0
|
||||||
@@ -70,7 +70,7 @@ local function debugImpl(buffer, value, extendedForm)
|
|||||||
elseif valueType == "table" then
|
elseif valueType == "table" then
|
||||||
local valueMeta = getmetatable(value)
|
local valueMeta = getmetatable(value)
|
||||||
|
|
||||||
if valueMeta ~= nil and valueMeta.__fmtDebug ~= nil then
|
if valueMeta ~= nil and valueMeta.__fmtDebug ~= nil then
|
||||||
-- This type implement's the metamethod we made up to line up with
|
-- This type implement's the metamethod we made up to line up with
|
||||||
-- Rust's 'Debug' trait.
|
-- Rust's 'Debug' trait.
|
||||||
|
|
||||||
@@ -242,4 +242,4 @@ return {
|
|||||||
debugOutputBuffer = debugOutputBuffer,
|
debugOutputBuffer = debugOutputBuffer,
|
||||||
fmt = fmt,
|
fmt = fmt,
|
||||||
debugify = debugify,
|
debugify = debugify,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ Error.__index = Error
|
|||||||
|
|
||||||
Error.Kind = {
|
Error.Kind = {
|
||||||
HttpNotEnabled = {
|
HttpNotEnabled = {
|
||||||
message = "Rojo requires HTTP access, which is not enabled.\n" ..
|
message = "Rojo requires HTTP access, which is not enabled.\n"
|
||||||
"Check your game settings, located in the 'Home' tab of Studio.",
|
.. "Check your game settings, located in the 'Home' tab of Studio.",
|
||||||
},
|
},
|
||||||
ConnectFailed = {
|
ConnectFailed = {
|
||||||
message = "Couldn't connect to the Rojo server.\n" ..
|
message = "Couldn't connect to the Rojo server.\n"
|
||||||
"Make sure the server is running — use 'rojo serve' to run it!",
|
.. "Make sure the server is running — use 'rojo serve' to run it!",
|
||||||
},
|
},
|
||||||
Timeout = {
|
Timeout = {
|
||||||
message = "HTTP request timed out.",
|
message = "HTTP request timed out.",
|
||||||
@@ -63,4 +63,13 @@ function Error.fromRobloxErrorString(message)
|
|||||||
return Error.new(Error.Kind.Unknown, message)
|
return Error.new(Error.Kind.Unknown, message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Error.fromResponse(response)
|
||||||
|
local lower = (response.body or ""):lower()
|
||||||
|
if response.code == 408 or response.code == 504 or lower:find("timed? ?out") then
|
||||||
|
return Error.new(Error.Kind.Timeout)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Error.new(Error.Kind.Unknown, string.format("%s: %s", tostring(response.code), tostring(response.body)))
|
||||||
|
end
|
||||||
|
|
||||||
return Error
|
return Error
|
||||||
|
|||||||
@@ -31,4 +31,4 @@ function Response:json()
|
|||||||
return HttpService:JSONDecode(self.body)
|
return HttpService:JSONDecode(self.body)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Response
|
return Response
|
||||||
|
|||||||
@@ -30,8 +30,13 @@ local function performRequest(requestParams)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
if success then
|
if success then
|
||||||
Log.trace("Request {} success, status code {}", requestId, response.StatusCode)
|
Log.trace("Request {} success, response {:#?}", requestId, response)
|
||||||
resolve(HttpResponse.fromRobloxResponse(response))
|
local httpResponse = HttpResponse.fromRobloxResponse(response)
|
||||||
|
if httpResponse:isSuccess() then
|
||||||
|
resolve(httpResponse)
|
||||||
|
else
|
||||||
|
reject(HttpError.fromResponse(httpResponse))
|
||||||
|
end
|
||||||
else
|
else
|
||||||
Log.trace("Request {} failure: {:?}", requestId, response)
|
Log.trace("Request {} failure: {:?}", requestId, response)
|
||||||
reject(HttpError.fromRobloxErrorString(response))
|
reject(HttpError.fromRobloxErrorString(response))
|
||||||
@@ -63,4 +68,4 @@ function Http.jsonDecode(source)
|
|||||||
return HttpService:JSONDecode(source)
|
return HttpService:JSONDecode(source)
|
||||||
end
|
end
|
||||||
|
|
||||||
return Http
|
return Http
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ return function()
|
|||||||
it("should load", function()
|
it("should load", function()
|
||||||
require(script.Parent)
|
require(script.Parent)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -57,4 +57,4 @@ function Log.error(template, ...)
|
|||||||
error(Fmt.fmt(template, ...))
|
error(Fmt.fmt(template, ...))
|
||||||
end
|
end
|
||||||
|
|
||||||
return Log
|
return Log
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ return function()
|
|||||||
it("should load", function()
|
it("should load", function()
|
||||||
require(script.Parent)
|
require(script.Parent)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -188,6 +188,38 @@ types = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Content = {
|
Content = {
|
||||||
|
fromPod = function(pod): Content
|
||||||
|
if type(pod) == "string" then
|
||||||
|
if pod == "None" then
|
||||||
|
return Content.none
|
||||||
|
else
|
||||||
|
error(`unexpected Content value '{pod}'`)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local ty, value = next(pod)
|
||||||
|
if ty == "Uri" then
|
||||||
|
return Content.fromUri(value)
|
||||||
|
elseif ty == "Object" then
|
||||||
|
error("Object deserializing is not currently implemented")
|
||||||
|
else
|
||||||
|
error(`Unknown Content type '{ty}' (could not deserialize)`)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
toPod = function(roblox: Content)
|
||||||
|
if roblox.SourceType == Enum.ContentSourceType.None then
|
||||||
|
return "None"
|
||||||
|
elseif roblox.SourceType == Enum.ContentSourceType.Uri then
|
||||||
|
return { Uri = roblox.Uri }
|
||||||
|
elseif roblox.SourceType == Enum.ContentSourceType.Object then
|
||||||
|
error("Object serializing is not currently implemented")
|
||||||
|
else
|
||||||
|
error(`Unknown Content type '{roblox.SourceType} (could not serialize)`)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
ContentId = {
|
||||||
fromPod = identity,
|
fromPod = identity,
|
||||||
toPod = identity,
|
toPod = identity,
|
||||||
},
|
},
|
||||||
@@ -205,6 +237,19 @@ types = {
|
|||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
EnumItem = {
|
||||||
|
fromPod = function(pod)
|
||||||
|
return Enum[pod.type]:FromValue(pod.value)
|
||||||
|
end,
|
||||||
|
|
||||||
|
toPod = function(roblox)
|
||||||
|
return {
|
||||||
|
type = tostring(roblox.EnumType),
|
||||||
|
value = roblox.Value,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
Faces = {
|
Faces = {
|
||||||
fromPod = function(pod)
|
fromPod = function(pod)
|
||||||
local faces = {}
|
local faces = {}
|
||||||
@@ -300,7 +345,12 @@ types = {
|
|||||||
local keypoints = {}
|
local keypoints = {}
|
||||||
|
|
||||||
for index, keypoint in ipairs(pod.keypoints) do
|
for index, keypoint in ipairs(pod.keypoints) do
|
||||||
keypoints[index] = NumberSequenceKeypoint.new(keypoint.time, keypoint.value, keypoint.envelope)
|
-- TODO: Add a test for NaN or Infinity values and envelopes
|
||||||
|
-- Right now it isn't possible because it'd fail the roundtrip.
|
||||||
|
-- It's more important that it works right now, though.
|
||||||
|
local value = keypoint.value or 0
|
||||||
|
local envelope = keypoint.envelope or 0
|
||||||
|
keypoints[index] = NumberSequenceKeypoint.new(keypoint.time, value, envelope)
|
||||||
end
|
end
|
||||||
|
|
||||||
return NumberSequence.new(keypoints)
|
return NumberSequence.new(keypoints)
|
||||||
@@ -328,13 +378,26 @@ types = {
|
|||||||
if pod == "Default" then
|
if pod == "Default" then
|
||||||
return nil
|
return nil
|
||||||
else
|
else
|
||||||
return PhysicalProperties.new(
|
-- Passing `nil` instead of not passing anything gives
|
||||||
pod.density,
|
-- different results, so we have to branch here.
|
||||||
pod.friction,
|
if pod.acousticAbsorption then
|
||||||
pod.elasticity,
|
return (PhysicalProperties.new :: any)(
|
||||||
pod.frictionWeight,
|
pod.density,
|
||||||
pod.elasticityWeight
|
pod.friction,
|
||||||
)
|
pod.elasticity,
|
||||||
|
pod.frictionWeight,
|
||||||
|
pod.elasticityWeight,
|
||||||
|
pod.acousticAbsorption
|
||||||
|
)
|
||||||
|
else
|
||||||
|
return PhysicalProperties.new(
|
||||||
|
pod.density,
|
||||||
|
pod.friction,
|
||||||
|
pod.elasticity,
|
||||||
|
pod.frictionWeight,
|
||||||
|
pod.elasticityWeight
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
@@ -348,6 +411,7 @@ types = {
|
|||||||
elasticity = roblox.Elasticity,
|
elasticity = roblox.Elasticity,
|
||||||
frictionWeight = roblox.FrictionWeight,
|
frictionWeight = roblox.FrictionWeight,
|
||||||
elasticityWeight = roblox.ElasticityWeight,
|
elasticityWeight = roblox.ElasticityWeight,
|
||||||
|
acousticAbsorption = roblox.AcousticAbsorption,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
@@ -493,9 +557,32 @@ types = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
types.OptionalCFrame = {
|
||||||
|
fromPod = function(pod)
|
||||||
|
if pod == nil then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return types.CFrame.fromPod(pod)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
toPod = function(roblox)
|
||||||
|
if roblox == nil then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return types.CFrame.toPod(roblox)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
function EncodedValue.decode(encodedValue)
|
function EncodedValue.decode(encodedValue)
|
||||||
local ty, value = next(encodedValue)
|
local ty, value = next(encodedValue)
|
||||||
|
|
||||||
|
if ty == nil then
|
||||||
|
-- If the encoded pair is empty, assume it is an unoccupied optional value
|
||||||
|
return true, nil
|
||||||
|
end
|
||||||
|
|
||||||
local typeImpl = types[ty]
|
local typeImpl = types[ty]
|
||||||
if typeImpl == nil then
|
if typeImpl == nil then
|
||||||
return false, "Couldn't decode value " .. tostring(ty)
|
return false, "Couldn't decode value " .. tostring(ty)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Error.Kind = {
|
|||||||
UnknownProperty = "UnknownProperty",
|
UnknownProperty = "UnknownProperty",
|
||||||
PropertyNotReadable = "PropertyNotReadable",
|
PropertyNotReadable = "PropertyNotReadable",
|
||||||
PropertyNotWritable = "PropertyNotWritable",
|
PropertyNotWritable = "PropertyNotWritable",
|
||||||
|
CannotParseBinaryString = "CannotParseBinaryString",
|
||||||
Roblox = "Roblox",
|
Roblox = "Roblox",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,12 @@
|
|||||||
0.0
|
0.0
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"TestEnumItem": {
|
||||||
|
"EnumItem": {
|
||||||
|
"type": "Material",
|
||||||
|
"value": 256
|
||||||
|
}
|
||||||
|
},
|
||||||
"TestNumber": {
|
"TestNumber": {
|
||||||
"Float64": 1337.0
|
"Float64": 1337.0
|
||||||
},
|
},
|
||||||
@@ -170,9 +176,23 @@
|
|||||||
},
|
},
|
||||||
"ty": "ColorSequence"
|
"ty": "ColorSequence"
|
||||||
},
|
},
|
||||||
"Content": {
|
"ContentId": {
|
||||||
"value": {
|
"value": {
|
||||||
"Content": "rbxassetid://12345"
|
"ContentId": "rbxassetid://12345"
|
||||||
|
},
|
||||||
|
"ty": "ContentId"
|
||||||
|
},
|
||||||
|
"Content_None": {
|
||||||
|
"value": {
|
||||||
|
"Content": "None"
|
||||||
|
},
|
||||||
|
"ty": "Content"
|
||||||
|
},
|
||||||
|
"Content_Uri": {
|
||||||
|
"value": {
|
||||||
|
"Content": {
|
||||||
|
"Uri": "rbxasset://abc/123.rojo"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ty": "Content"
|
"ty": "Content"
|
||||||
},
|
},
|
||||||
@@ -182,6 +202,15 @@
|
|||||||
},
|
},
|
||||||
"ty": "Enum"
|
"ty": "Enum"
|
||||||
},
|
},
|
||||||
|
"EnumItem": {
|
||||||
|
"value": {
|
||||||
|
"EnumItem": {
|
||||||
|
"type": "Material",
|
||||||
|
"value": 256
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ty": "EnumItem"
|
||||||
|
},
|
||||||
"Faces": {
|
"Faces": {
|
||||||
"value": {
|
"value": {
|
||||||
"Faces": [
|
"Faces": [
|
||||||
@@ -370,6 +399,41 @@
|
|||||||
},
|
},
|
||||||
"ty": "NumberSequence"
|
"ty": "NumberSequence"
|
||||||
},
|
},
|
||||||
|
"OptionalCFrame-None": {
|
||||||
|
"value": {
|
||||||
|
"OptionalCFrame": null
|
||||||
|
},
|
||||||
|
"ty": "OptionalCFrame"
|
||||||
|
},
|
||||||
|
"OptionalCFrame-Some": {
|
||||||
|
"value": {
|
||||||
|
"OptionalCFrame": {
|
||||||
|
"position": [
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"orientation": [
|
||||||
|
[
|
||||||
|
1.0,
|
||||||
|
0.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ty": "OptionalCFrame"
|
||||||
|
},
|
||||||
"PhysicalProperties-Custom": {
|
"PhysicalProperties-Custom": {
|
||||||
"value": {
|
"value": {
|
||||||
"PhysicalProperties": {
|
"PhysicalProperties": {
|
||||||
@@ -377,7 +441,8 @@
|
|||||||
"friction": 1.0,
|
"friction": 1.0,
|
||||||
"elasticity": 0.0,
|
"elasticity": 0.0,
|
||||||
"frictionWeight": 50.0,
|
"frictionWeight": 50.0,
|
||||||
"elasticityWeight": 25.0
|
"elasticityWeight": 25.0,
|
||||||
|
"acousticAbsorption": 0.15625
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ty": "PhysicalProperties"
|
"ty": "PhysicalProperties"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
local CollectionService = game:GetService("CollectionService")
|
local CollectionService = game:GetService("CollectionService")
|
||||||
local ScriptEditorService = game:GetService("ScriptEditorService")
|
local ScriptEditorService = game:GetService("ScriptEditorService")
|
||||||
|
|
||||||
|
local Error = require(script.Parent.Error)
|
||||||
|
|
||||||
--- A list of `Enum.Material` values that are used for Terrain.MaterialColors
|
--- A list of `Enum.Material` values that are used for Terrain.MaterialColors
|
||||||
local TERRAIN_MATERIAL_COLORS = {
|
local TERRAIN_MATERIAL_COLORS = {
|
||||||
Enum.Material.Grass,
|
Enum.Material.Grass,
|
||||||
@@ -26,6 +28,21 @@ local TERRAIN_MATERIAL_COLORS = {
|
|||||||
Enum.Material.Pavement,
|
Enum.Material.Pavement,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local function isAttributeNameValid(attributeName)
|
||||||
|
-- For SetAttribute to succeed, the attribute name must be less than or
|
||||||
|
-- equal to 100 characters...
|
||||||
|
return #attributeName <= 100
|
||||||
|
-- ...and must only contain alphanumeric characters, periods, hyphens,
|
||||||
|
-- underscores, or forward slashes.
|
||||||
|
and attributeName:match("[^%w%.%-_/]") == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isAttributeNameReserved(attributeName)
|
||||||
|
-- For SetAttribute to succeed, attribute names must not use the RBX
|
||||||
|
-- prefix, which is reserved by Roblox.
|
||||||
|
return attributeName:sub(1, 3) == "RBX"
|
||||||
|
end
|
||||||
|
|
||||||
-- Defines how to read and write properties that aren't directly scriptable.
|
-- Defines how to read and write properties that aren't directly scriptable.
|
||||||
--
|
--
|
||||||
-- The reflection database refers to these as having scriptability = "Custom"
|
-- The reflection database refers to these as having scriptability = "Custom"
|
||||||
@@ -36,30 +53,41 @@ return {
|
|||||||
return true, instance:GetAttributes()
|
return true, instance:GetAttributes()
|
||||||
end,
|
end,
|
||||||
write = function(instance, _, value)
|
write = function(instance, _, value)
|
||||||
|
if typeof(value) ~= "table" then
|
||||||
|
return false, Error.new(Error.Kind.CannotParseBinaryString)
|
||||||
|
end
|
||||||
|
|
||||||
local existing = instance:GetAttributes()
|
local existing = instance:GetAttributes()
|
||||||
local didAllWritesSucceed = true
|
local didAllWritesSucceed = true
|
||||||
|
|
||||||
for attributeName, attributeValue in pairs(value) do
|
for attributeName, attributeValue in pairs(value) do
|
||||||
local isNameValid =
|
if isAttributeNameReserved(attributeName) then
|
||||||
-- For our SetAttribute to succeed, the attribute name must be
|
-- If the attribute name is reserved, then we don't
|
||||||
-- less than or equal to 100 characters...
|
-- really care about reporting any failures about
|
||||||
#attributeName <= 100
|
-- it.
|
||||||
-- ...must only contain alphanumeric characters, periods, hyphens,
|
continue
|
||||||
-- underscores, or forward slashes...
|
|
||||||
and attributeName:match("[^%w%.%-_/]") == nil
|
|
||||||
-- ... and must not use the RBX prefix, which is reserved by Roblox.
|
|
||||||
and attributeName:sub(1, 3) ~= "RBX"
|
|
||||||
|
|
||||||
if isNameValid then
|
|
||||||
instance:SetAttribute(attributeName, attributeValue)
|
|
||||||
else
|
|
||||||
didAllWritesSucceed = false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not isAttributeNameValid(attributeName) then
|
||||||
|
didAllWritesSucceed = false
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
instance:SetAttribute(attributeName, attributeValue)
|
||||||
end
|
end
|
||||||
|
|
||||||
for key in pairs(existing) do
|
for existingAttributeName in pairs(existing) do
|
||||||
if value[key] == nil then
|
if isAttributeNameReserved(existingAttributeName) then
|
||||||
instance:SetAttribute(key, nil)
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
if not isAttributeNameValid(existingAttributeName) then
|
||||||
|
didAllWritesSucceed = false
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
if value[existingAttributeName] == nil then
|
||||||
|
instance:SetAttribute(existingAttributeName, nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -111,6 +139,19 @@ return {
|
|||||||
return true, instance:ScaleTo(value)
|
return true, instance:ScaleTo(value)
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
WorldPivotData = {
|
||||||
|
read = function(instance)
|
||||||
|
return true, instance.WorldPivot
|
||||||
|
end,
|
||||||
|
write = function(instance, _, value)
|
||||||
|
if value == nil then
|
||||||
|
return true, nil
|
||||||
|
else
|
||||||
|
instance.WorldPivot = value
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Terrain = {
|
Terrain = {
|
||||||
MaterialColors = {
|
MaterialColors = {
|
||||||
@@ -125,9 +166,14 @@ return {
|
|||||||
return true, colors
|
return true, colors
|
||||||
end,
|
end,
|
||||||
write = function(instance: Terrain, _, value: { [Enum.Material]: Color3 })
|
write = function(instance: Terrain, _, value: { [Enum.Material]: Color3 })
|
||||||
|
if typeof(value) ~= "table" then
|
||||||
|
return false, Error.new(Error.Kind.CannotParseBinaryString)
|
||||||
|
end
|
||||||
|
|
||||||
for material, color in value do
|
for material, color in value do
|
||||||
instance:SetMaterialColor(material, color)
|
instance:SetMaterialColor(material, color)
|
||||||
end
|
end
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
|
||||||
local TestEZ = require(ReplicatedStorage.Packages.TestEZ)
|
local TestEZ = require(ReplicatedStorage.Packages:WaitForChild("TestEZ", 10))
|
||||||
|
|
||||||
local Rojo = ReplicatedStorage.Rojo
|
local Rojo = ReplicatedStorage.Rojo
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ local Version = require(script.Parent.Version)
|
|||||||
local validateApiInfo = Types.ifEnabled(Types.ApiInfoResponse)
|
local validateApiInfo = Types.ifEnabled(Types.ApiInfoResponse)
|
||||||
local validateApiRead = Types.ifEnabled(Types.ApiReadResponse)
|
local validateApiRead = Types.ifEnabled(Types.ApiReadResponse)
|
||||||
local validateApiSubscribe = Types.ifEnabled(Types.ApiSubscribeResponse)
|
local validateApiSubscribe = Types.ifEnabled(Types.ApiSubscribeResponse)
|
||||||
|
local validateApiSerialize = Types.ifEnabled(Types.ApiSerializeResponse)
|
||||||
|
local validateApiRefPatch = Types.ifEnabled(Types.ApiRefPatchResponse)
|
||||||
|
|
||||||
local function rejectFailedRequests(response)
|
local function rejectFailedRequests(response)
|
||||||
if response.code >= 400 then
|
if response.code >= 400 then
|
||||||
@@ -45,14 +47,7 @@ end
|
|||||||
|
|
||||||
local function rejectWrongPlaceId(infoResponseBody)
|
local function rejectWrongPlaceId(infoResponseBody)
|
||||||
if infoResponseBody.expectedPlaceIds ~= nil then
|
if infoResponseBody.expectedPlaceIds ~= nil then
|
||||||
local foundId = false
|
local foundId = table.find(infoResponseBody.expectedPlaceIds, game.PlaceId)
|
||||||
|
|
||||||
for _, id in ipairs(infoResponseBody.expectedPlaceIds) do
|
|
||||||
if id == game.PlaceId then
|
|
||||||
foundId = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not foundId then
|
if not foundId then
|
||||||
local idList = {}
|
local idList = {}
|
||||||
@@ -62,10 +57,30 @@ local function rejectWrongPlaceId(infoResponseBody)
|
|||||||
|
|
||||||
local message = (
|
local message = (
|
||||||
"Found a Rojo server, but its project is set to only be used with a specific list of places."
|
"Found a Rojo server, but its project is set to only be used with a specific list of places."
|
||||||
.. "\nYour place ID is %s, but needs to be one of these:"
|
.. "\nYour place ID is %u, but needs to be one of these:"
|
||||||
.. "\n%s"
|
.. "\n%s"
|
||||||
.. "\n\nTo change this list, edit 'servePlaceIds' in your .project.json file."
|
.. "\n\nTo change this list, edit 'servePlaceIds' in your .project.json file."
|
||||||
):format(tostring(game.PlaceId), table.concat(idList, "\n"))
|
):format(game.PlaceId, table.concat(idList, "\n"))
|
||||||
|
|
||||||
|
return Promise.reject(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if infoResponseBody.unexpectedPlaceIds ~= nil then
|
||||||
|
local foundId = table.find(infoResponseBody.unexpectedPlaceIds, game.PlaceId)
|
||||||
|
|
||||||
|
if foundId then
|
||||||
|
local idList = {}
|
||||||
|
for _, id in ipairs(infoResponseBody.unexpectedPlaceIds) do
|
||||||
|
table.insert(idList, "- " .. tostring(id))
|
||||||
|
end
|
||||||
|
|
||||||
|
local message = (
|
||||||
|
"Found a Rojo server, but its project is set to not be used with a specific list of places."
|
||||||
|
.. "\nYour place ID is %u, but needs to not be one of these:"
|
||||||
|
.. "\n%s"
|
||||||
|
.. "\n\nTo change this list, edit 'blockedPlaceIds' in your .project.json file."
|
||||||
|
):format(game.PlaceId, table.concat(idList, "\n"))
|
||||||
|
|
||||||
return Promise.reject(message)
|
return Promise.reject(message)
|
||||||
end
|
end
|
||||||
@@ -185,10 +200,10 @@ function ApiContext:write(patch)
|
|||||||
|
|
||||||
body = Http.jsonEncode(body)
|
body = Http.jsonEncode(body)
|
||||||
|
|
||||||
return Http.post(url, body):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
return Http.post(url, body):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(responseBody)
|
||||||
Log.info("Write response: {:?}", body)
|
Log.info("Write response: {:?}", responseBody)
|
||||||
|
|
||||||
return body
|
return responseBody
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -239,4 +254,32 @@ function ApiContext:open(id)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function ApiContext:serialize(ids: { string })
|
||||||
|
local url = ("%s/api/serialize/%s"):format(self.__baseUrl, table.concat(ids, ","))
|
||||||
|
|
||||||
|
return Http.get(url):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
||||||
|
if body.sessionId ~= self.__sessionId then
|
||||||
|
return Promise.reject("Server changed ID")
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(validateApiSerialize(body))
|
||||||
|
|
||||||
|
return body
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ApiContext:refPatch(ids: { string })
|
||||||
|
local url = ("%s/api/ref-patch/%s"):format(self.__baseUrl, table.concat(ids, ","))
|
||||||
|
|
||||||
|
return Http.get(url):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
||||||
|
if body.sessionId ~= self.__sessionId then
|
||||||
|
return Promise.reject("Server changed ID")
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(validateApiRefPatch(body))
|
||||||
|
|
||||||
|
return body
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
return ApiContext
|
return ApiContext
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ end
|
|||||||
|
|
||||||
function Checkbox:render()
|
function Checkbox:render()
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
theme = theme.Checkbox
|
local checkboxTheme = theme.Checkbox
|
||||||
|
|
||||||
local activeTransparency = Roact.joinBindings({
|
local activeTransparency = Roact.joinBindings({
|
||||||
self.binding:map(function(value)
|
self.binding:map(function(value)
|
||||||
@@ -57,20 +57,21 @@ function Checkbox:render()
|
|||||||
end,
|
end,
|
||||||
}, {
|
}, {
|
||||||
StateTip = e(Tooltip.Trigger, {
|
StateTip = e(Tooltip.Trigger, {
|
||||||
text = (if self.props.locked then "[LOCKED] " else "")
|
text = (if self.props.locked
|
||||||
.. (if self.props.active then "Enabled" else "Disabled"),
|
then (self.props.lockedTooltip or "(Cannot be changed right now)") .. "\n"
|
||||||
|
else "") .. (if self.props.active then "Enabled" else "Disabled"),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Active = e(SlicedImage, {
|
Active = e(SlicedImage, {
|
||||||
slice = Assets.Slices.RoundedBackground,
|
slice = Assets.Slices.RoundedBackground,
|
||||||
color = theme.Active.BackgroundColor,
|
color = checkboxTheme.Active.BackgroundColor,
|
||||||
transparency = activeTransparency,
|
transparency = activeTransparency,
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
zIndex = 2,
|
zIndex = 2,
|
||||||
}, {
|
}, {
|
||||||
Icon = e("ImageLabel", {
|
Icon = e("ImageLabel", {
|
||||||
Image = if self.props.locked then Assets.Images.Checkbox.Locked else Assets.Images.Checkbox.Active,
|
Image = if self.props.locked then Assets.Images.Checkbox.Locked else Assets.Images.Checkbox.Active,
|
||||||
ImageColor3 = theme.Active.IconColor,
|
ImageColor3 = checkboxTheme.Active.IconColor,
|
||||||
ImageTransparency = activeTransparency,
|
ImageTransparency = activeTransparency,
|
||||||
|
|
||||||
Size = UDim2.new(0, 16, 0, 16),
|
Size = UDim2.new(0, 16, 0, 16),
|
||||||
@@ -83,7 +84,7 @@ function Checkbox:render()
|
|||||||
|
|
||||||
Inactive = e(SlicedImage, {
|
Inactive = e(SlicedImage, {
|
||||||
slice = Assets.Slices.RoundedBorder,
|
slice = Assets.Slices.RoundedBorder,
|
||||||
color = theme.Inactive.BorderColor,
|
color = checkboxTheme.Inactive.BorderColor,
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
}, {
|
}, {
|
||||||
@@ -91,7 +92,7 @@ function Checkbox:render()
|
|||||||
Image = if self.props.locked
|
Image = if self.props.locked
|
||||||
then Assets.Images.Checkbox.Locked
|
then Assets.Images.Checkbox.Locked
|
||||||
else Assets.Images.Checkbox.Inactive,
|
else Assets.Images.Checkbox.Inactive,
|
||||||
ImageColor3 = theme.Inactive.IconColor,
|
ImageColor3 = checkboxTheme.Inactive.IconColor,
|
||||||
ImageTransparency = self.props.transparency,
|
ImageTransparency = self.props.transparency,
|
||||||
|
|
||||||
Size = UDim2.new(0, 16, 0, 16),
|
Size = UDim2.new(0, 16, 0, 16),
|
||||||
|
|||||||
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
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
@@ -7,6 +8,8 @@ Highlighter.matchStudioSettings()
|
|||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local Theme = require(Plugin.App.Theme)
|
||||||
|
|
||||||
local CodeLabel = Roact.PureComponent:extend("CodeLabel")
|
local CodeLabel = Roact.PureComponent:extend("CodeLabel")
|
||||||
|
|
||||||
function CodeLabel:init()
|
function CodeLabel:init()
|
||||||
@@ -40,22 +43,24 @@ function CodeLabel:updateHighlights()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function CodeLabel:render()
|
function CodeLabel:render()
|
||||||
return e("TextLabel", {
|
return Theme.with(function(theme)
|
||||||
Size = self.props.size,
|
return e("TextLabel", {
|
||||||
Position = self.props.position,
|
Size = self.props.size,
|
||||||
Text = self.props.text,
|
Position = self.props.position,
|
||||||
BackgroundTransparency = 1,
|
Text = self.props.text,
|
||||||
Font = Enum.Font.RobotoMono,
|
BackgroundTransparency = 1,
|
||||||
TextSize = 16,
|
FontFace = theme.Font.Code,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextSize = theme.TextSize.Code,
|
||||||
TextYAlignment = Enum.TextYAlignment.Top,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextColor3 = Color3.fromRGB(255, 255, 255),
|
TextYAlignment = Enum.TextYAlignment.Top,
|
||||||
[Roact.Ref] = self.labelRef,
|
TextColor3 = Color3.fromRGB(255, 255, 255),
|
||||||
}, {
|
[Roact.Ref] = self.labelRef,
|
||||||
SyntaxHighlights = e("Folder", {
|
}, {
|
||||||
[Roact.Ref] = self.highlightsRef,
|
SyntaxHighlights = e("Folder", {
|
||||||
}),
|
[Roact.Ref] = self.highlightsRef,
|
||||||
})
|
}),
|
||||||
|
})
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return CodeLabel
|
return CodeLabel
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
local TextService = game:GetService("TextService")
|
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
local Packages = Rojo.Packages
|
||||||
@@ -10,9 +8,11 @@ local Flipper = require(Packages.Flipper)
|
|||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||||
|
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
|
||||||
|
|
||||||
local SlicedImage = require(script.Parent.SlicedImage)
|
local SlicedImage = require(script.Parent.SlicedImage)
|
||||||
local ScrollingFrame = require(script.Parent.ScrollingFrame)
|
local ScrollingFrame = require(script.Parent.ScrollingFrame)
|
||||||
|
local Tooltip = require(script.Parent.Tooltip)
|
||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
@@ -44,29 +44,29 @@ end
|
|||||||
|
|
||||||
function Dropdown:render()
|
function Dropdown:render()
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
theme = theme.Dropdown
|
local dropdownTheme = theme.Dropdown
|
||||||
|
|
||||||
local optionButtons = {}
|
local optionButtons = {}
|
||||||
local width = -1
|
local width = -1
|
||||||
for i, option in self.props.options do
|
for i, option in self.props.options do
|
||||||
local text = tostring(option or "")
|
local text = tostring(option or "")
|
||||||
local textSize = TextService:GetTextSize(text, 15, Enum.Font.GothamMedium, Vector2.new(math.huge, 20))
|
local textBounds = getTextBoundsAsync(text, theme.Font.Main, theme.TextSize.Body, math.huge)
|
||||||
if textSize.X > width then
|
if textBounds.X > width then
|
||||||
width = textSize.X
|
width = textBounds.X
|
||||||
end
|
end
|
||||||
|
|
||||||
optionButtons[text] = e("TextButton", {
|
optionButtons[text] = e("TextButton", {
|
||||||
Text = text,
|
Text = text,
|
||||||
LayoutOrder = i,
|
LayoutOrder = i,
|
||||||
Size = UDim2.new(1, 0, 0, 24),
|
Size = UDim2.new(1, 0, 0, 24),
|
||||||
BackgroundColor3 = theme.BackgroundColor,
|
BackgroundColor3 = dropdownTheme.BackgroundColor,
|
||||||
TextTransparency = self.props.transparency,
|
TextTransparency = self.props.transparency,
|
||||||
BackgroundTransparency = self.props.transparency,
|
BackgroundTransparency = self.props.transparency,
|
||||||
BorderSizePixel = 0,
|
BorderSizePixel = 0,
|
||||||
TextColor3 = theme.TextColor,
|
TextColor3 = dropdownTheme.TextColor,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextSize = 15,
|
TextSize = theme.TextSize.Body,
|
||||||
Font = Enum.Font.GothamMedium,
|
FontFace = theme.Font.Main,
|
||||||
|
|
||||||
[Roact.Event.Activated] = function()
|
[Roact.Event.Activated] = function()
|
||||||
if self.props.locked then
|
if self.props.locked then
|
||||||
@@ -103,15 +103,13 @@ function Dropdown:render()
|
|||||||
}, {
|
}, {
|
||||||
Border = e(SlicedImage, {
|
Border = e(SlicedImage, {
|
||||||
slice = Assets.Slices.RoundedBorder,
|
slice = Assets.Slices.RoundedBorder,
|
||||||
color = theme.BorderColor,
|
color = dropdownTheme.BorderColor,
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
}, {
|
}, {
|
||||||
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 = dropdownTheme.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),
|
||||||
@@ -122,15 +120,21 @@ function Dropdown:render()
|
|||||||
end),
|
end),
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
StateTip = if self.props.locked
|
||||||
|
then e(Tooltip.Trigger, {
|
||||||
|
text = self.props.lockedTooltip or "(Cannot be changed right now)",
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
}),
|
}),
|
||||||
Active = e("TextLabel", {
|
Active = e("TextLabel", {
|
||||||
Size = UDim2.new(1, -30, 1, 0),
|
Size = UDim2.new(1, -30, 1, 0),
|
||||||
Position = UDim2.new(0, 6, 0, 0),
|
Position = UDim2.new(0, 6, 0, 0),
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Text = self.props.active,
|
Text = self.props.active,
|
||||||
Font = Enum.Font.GothamMedium,
|
FontFace = theme.Font.Main,
|
||||||
TextSize = 15,
|
TextSize = theme.TextSize.Body,
|
||||||
TextColor3 = theme.TextColor,
|
TextColor3 = dropdownTheme.TextColor,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextTransparency = self.props.transparency,
|
TextTransparency = self.props.transparency,
|
||||||
}),
|
}),
|
||||||
@@ -138,7 +142,7 @@ function Dropdown:render()
|
|||||||
Options = if self.state.open
|
Options = if self.state.open
|
||||||
then e(SlicedImage, {
|
then e(SlicedImage, {
|
||||||
slice = Assets.Slices.RoundedBackground,
|
slice = Assets.Slices.RoundedBackground,
|
||||||
color = theme.BackgroundColor,
|
color = dropdownTheme.BackgroundColor,
|
||||||
position = UDim2.new(1, 0, 1, 3),
|
position = UDim2.new(1, 0, 1, 3),
|
||||||
size = self.openBinding:map(function(a)
|
size = self.openBinding:map(function(a)
|
||||||
return UDim2.new(1, 0, a * math.min(3, #self.props.options), 0)
|
return UDim2.new(1, 0, a * math.min(3, #self.props.options), 0)
|
||||||
@@ -147,7 +151,7 @@ function Dropdown:render()
|
|||||||
}, {
|
}, {
|
||||||
Border = e(SlicedImage, {
|
Border = e(SlicedImage, {
|
||||||
slice = Assets.Slices.RoundedBorder,
|
slice = Assets.Slices.RoundedBorder,
|
||||||
color = theme.BorderColor,
|
color = dropdownTheme.BorderColor,
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
}),
|
}),
|
||||||
@@ -167,7 +171,7 @@ function Dropdown:render()
|
|||||||
self.setContentSize(object.AbsoluteContentSize)
|
self.setContentSize(object.AbsoluteContentSize)
|
||||||
end,
|
end,
|
||||||
}),
|
}),
|
||||||
Roact.createFragment(optionButtons),
|
Options = Roact.createFragment(optionButtons),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
else nil,
|
else nil,
|
||||||
|
|||||||
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
|
||||||
@@ -9,8 +9,70 @@ local Assets = require(Plugin.Assets)
|
|||||||
local Config = require(Plugin.Config)
|
local Config = require(Plugin.Config)
|
||||||
local Version = require(Plugin.Version)
|
local Version = require(Plugin.Version)
|
||||||
|
|
||||||
|
local Tooltip = require(Plugin.App.Components.Tooltip)
|
||||||
|
local SlicedImage = require(script.Parent.SlicedImage)
|
||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local function VersionIndicator(props)
|
||||||
|
local updateMessage = Version.getUpdateMessage()
|
||||||
|
|
||||||
|
return Theme.with(function(theme)
|
||||||
|
return e("Frame", {
|
||||||
|
LayoutOrder = props.layoutOrder,
|
||||||
|
Size = UDim2.new(0, 0, 0, 25),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
AutomaticSize = Enum.AutomaticSize.X,
|
||||||
|
}, {
|
||||||
|
Border = if updateMessage
|
||||||
|
then e(SlicedImage, {
|
||||||
|
slice = Assets.Slices.RoundedBorder,
|
||||||
|
color = theme.Button.Bordered.Enabled.BorderColor,
|
||||||
|
transparency = props.transparency,
|
||||||
|
size = UDim2.fromScale(1, 1),
|
||||||
|
zIndex = 0,
|
||||||
|
}, {
|
||||||
|
Indicator = e("ImageLabel", {
|
||||||
|
Size = UDim2.new(0, 10, 0, 10),
|
||||||
|
ScaleType = Enum.ScaleType.Fit,
|
||||||
|
Image = Assets.Images.Circles[16],
|
||||||
|
ImageColor3 = theme.Header.LogoColor,
|
||||||
|
ImageTransparency = props.transparency,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Position = UDim2.new(1, 0, 0, 0),
|
||||||
|
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
|
|
||||||
|
Tip = if updateMessage
|
||||||
|
then e(Tooltip.Trigger, {
|
||||||
|
text = updateMessage,
|
||||||
|
delay = 0.1,
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
|
|
||||||
|
VersionText = e("TextLabel", {
|
||||||
|
Text = Version.display(Config.version),
|
||||||
|
FontFace = theme.Font.Thin,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
TextColor3 = theme.Header.VersionColor,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
TextTransparency = props.transparency,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
|
||||||
|
Size = UDim2.new(0, 0, 1, 0),
|
||||||
|
AutomaticSize = Enum.AutomaticSize.X,
|
||||||
|
}, {
|
||||||
|
Padding = e("UIPadding", {
|
||||||
|
PaddingLeft = UDim.new(0, 6),
|
||||||
|
PaddingRight = UDim.new(0, 6),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
local function Header(props)
|
local function Header(props)
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
return e("Frame", {
|
return e("Frame", {
|
||||||
@@ -29,18 +91,9 @@ local function Header(props)
|
|||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Version = e("TextLabel", {
|
VersionIndicator = e(VersionIndicator, {
|
||||||
Text = Version.display(Config.version),
|
transparency = props.transparency,
|
||||||
Font = Enum.Font.Gotham,
|
layoutOrder = 2,
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = theme.Header.VersionColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
|
|
||||||
Size = UDim2.new(1, 0, 0, 14),
|
|
||||||
|
|
||||||
LayoutOrder = 2,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Layout = e("UIListLayout", {
|
Layout = e("UIListLayout", {
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
local StudioService = game:GetService("StudioService")
|
||||||
|
|
||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
|
local Roact = require(Packages.Roact)
|
||||||
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
|
local Theme = require(Plugin.App.Theme)
|
||||||
|
local Assets = require(Plugin.Assets)
|
||||||
|
|
||||||
|
local TextButton = require(Plugin.App.Components.TextButton)
|
||||||
|
|
||||||
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local FullscreenNotification = Roact.Component:extend("FullscreeFullscreenNotificationnNotification")
|
||||||
|
|
||||||
|
function FullscreenNotification:init()
|
||||||
|
self.transparency, self.setTransparency = Roact.createBinding(0)
|
||||||
|
self.lifetime = self.props.timeout
|
||||||
|
end
|
||||||
|
|
||||||
|
function FullscreenNotification:dismiss()
|
||||||
|
if self.props.onClose then
|
||||||
|
self.props.onClose()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function FullscreenNotification:didMount()
|
||||||
|
self.props.soundPlayer:play(Assets.Sounds.Notification)
|
||||||
|
|
||||||
|
self.timeout = task.spawn(function()
|
||||||
|
local clock = os.clock()
|
||||||
|
local seen = false
|
||||||
|
while task.wait(1 / 10) do
|
||||||
|
local now = os.clock()
|
||||||
|
local dt = now - clock
|
||||||
|
clock = now
|
||||||
|
|
||||||
|
if not seen then
|
||||||
|
seen = StudioService.ActiveScript == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if not seen then
|
||||||
|
-- Don't run down timer before being viewed
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
|
||||||
|
self.lifetime -= dt
|
||||||
|
if self.lifetime <= 0 then
|
||||||
|
self:dismiss()
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self.timeout = nil
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function FullscreenNotification:willUnmount()
|
||||||
|
if self.timeout and coroutine.status(self.timeout) ~= "dead" then
|
||||||
|
task.cancel(self.timeout)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function FullscreenNotification:render()
|
||||||
|
return Theme.with(function(theme)
|
||||||
|
local actionButtons = {}
|
||||||
|
if self.props.actions then
|
||||||
|
for key, action in self.props.actions do
|
||||||
|
actionButtons[key] = e(TextButton, {
|
||||||
|
text = action.text,
|
||||||
|
style = action.style,
|
||||||
|
onClick = function()
|
||||||
|
self:dismiss()
|
||||||
|
if action.onClick then
|
||||||
|
local success, err = pcall(action.onClick, self)
|
||||||
|
if not success then
|
||||||
|
Log.warn("Error in notification action: " .. tostring(err))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
layoutOrder = -action.layoutOrder,
|
||||||
|
transparency = self.transparency,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return e("Frame", {
|
||||||
|
BackgroundColor3 = theme.BackgroundColor,
|
||||||
|
Size = UDim2.fromScale(1, 1),
|
||||||
|
ZIndex = self.props.layoutOrder,
|
||||||
|
}, {
|
||||||
|
Padding = e("UIPadding", {
|
||||||
|
PaddingLeft = UDim.new(0, 17),
|
||||||
|
PaddingRight = UDim.new(0, 15),
|
||||||
|
PaddingTop = UDim.new(0, 10),
|
||||||
|
PaddingBottom = UDim.new(0, 10),
|
||||||
|
}),
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
FillDirection = Enum.FillDirection.Vertical,
|
||||||
|
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||||
|
Padding = UDim.new(0, 10),
|
||||||
|
}),
|
||||||
|
Logo = e("ImageLabel", {
|
||||||
|
ImageTransparency = self.transparency,
|
||||||
|
Image = Assets.Images.Logo,
|
||||||
|
ImageColor3 = theme.Header.LogoColor,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
Size = UDim2.fromOffset(60, 27),
|
||||||
|
LayoutOrder = 1,
|
||||||
|
}),
|
||||||
|
Info = e("TextLabel", {
|
||||||
|
Text = self.props.text,
|
||||||
|
FontFace = theme.Font.Main,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
TextColor3 = theme.Notification.InfoColor,
|
||||||
|
TextTransparency = self.transparency,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Center,
|
||||||
|
TextYAlignment = Enum.TextYAlignment.Center,
|
||||||
|
TextWrapped = true,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
|
||||||
|
AutomaticSize = Enum.AutomaticSize.Y,
|
||||||
|
Size = UDim2.fromScale(0.4, 0),
|
||||||
|
LayoutOrder = 2,
|
||||||
|
}),
|
||||||
|
Actions = if self.props.actions
|
||||||
|
then e("Frame", {
|
||||||
|
Size = UDim2.new(1, -40, 0, 37),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
LayoutOrder = 3,
|
||||||
|
}, {
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
FillDirection = Enum.FillDirection.Horizontal,
|
||||||
|
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
Padding = UDim.new(0, 5),
|
||||||
|
}),
|
||||||
|
Buttons = Roact.createFragment(actionButtons),
|
||||||
|
})
|
||||||
|
else nil,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return FullscreenNotification
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
local TextService = game:GetService("TextService")
|
|
||||||
local StudioService = game:GetService("StudioService")
|
local StudioService = game:GetService("StudioService")
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
@@ -9,16 +8,14 @@ local Roact = require(Packages.Roact)
|
|||||||
local Flipper = require(Packages.Flipper)
|
local Flipper = require(Packages.Flipper)
|
||||||
local Log = require(Packages.Log)
|
local Log = require(Packages.Log)
|
||||||
|
|
||||||
local bindingUtil = require(script.Parent.bindingUtil)
|
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
|
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||||
|
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
|
||||||
|
|
||||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||||
local TextButton = require(Plugin.App.Components.TextButton)
|
local TextButton = require(Plugin.App.Components.TextButton)
|
||||||
|
|
||||||
local baseClock = DateTime.now().UnixTimestampMillis
|
|
||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
local Notification = Roact.Component:extend("Notification")
|
local Notification = Roact.Component:extend("Notification")
|
||||||
@@ -78,7 +75,9 @@ function Notification:didMount()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Notification:willUnmount()
|
function Notification:willUnmount()
|
||||||
task.cancel(self.timeout)
|
if self.timeout and coroutine.status(self.timeout) ~= "dead" then
|
||||||
|
task.cancel(self.timeout)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Notification:render()
|
function Notification:render()
|
||||||
@@ -86,51 +85,49 @@ function Notification:render()
|
|||||||
return 1 - value
|
return 1 - value
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local textBounds = TextService:GetTextSize(self.props.text, 15, Enum.Font.GothamMedium, Vector2.new(350, 700))
|
return Theme.with(function(theme)
|
||||||
|
local actionButtons = {}
|
||||||
|
local buttonsX = 0
|
||||||
|
if self.props.actions then
|
||||||
|
local count = 0
|
||||||
|
for key, action in self.props.actions do
|
||||||
|
actionButtons[key] = e(TextButton, {
|
||||||
|
text = action.text,
|
||||||
|
style = action.style,
|
||||||
|
onClick = function()
|
||||||
|
self:dismiss()
|
||||||
|
if action.onClick then
|
||||||
|
local success, err = pcall(action.onClick, self)
|
||||||
|
if not success then
|
||||||
|
Log.warn("Error in notification action: " .. tostring(err))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
layoutOrder = -action.layoutOrder,
|
||||||
|
transparency = transparency,
|
||||||
|
})
|
||||||
|
|
||||||
local actionButtons = {}
|
buttonsX += getTextBoundsAsync(action.text, theme.Font.Main, theme.TextSize.Large, math.huge).X + (theme.TextSize.Body * 2)
|
||||||
local buttonsX = 0
|
|
||||||
if self.props.actions then
|
|
||||||
local count = 0
|
|
||||||
for key, action in self.props.actions do
|
|
||||||
actionButtons[key] = e(TextButton, {
|
|
||||||
text = action.text,
|
|
||||||
style = action.style,
|
|
||||||
onClick = function()
|
|
||||||
local success, err = pcall(action.onClick, self)
|
|
||||||
if not success then
|
|
||||||
Log.warn("Error in notification action: " .. tostring(err))
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
layoutOrder = -action.layoutOrder,
|
|
||||||
transparency = transparency,
|
|
||||||
})
|
|
||||||
|
|
||||||
buttonsX += TextService:GetTextSize(
|
count += 1
|
||||||
action.text,
|
end
|
||||||
18,
|
|
||||||
Enum.Font.GothamMedium,
|
|
||||||
Vector2.new(math.huge, math.huge)
|
|
||||||
).X + 30
|
|
||||||
|
|
||||||
count += 1
|
buttonsX += (count - 1) * 5
|
||||||
end
|
end
|
||||||
|
|
||||||
buttonsX += (count - 1) * 5
|
local paddingY, logoSize = 20, 32
|
||||||
end
|
local actionsY = if self.props.actions then 37 else 0
|
||||||
|
local textXSpace = math.max(250, buttonsX) + 35
|
||||||
|
local textBounds = getTextBoundsAsync(self.props.text, theme.Font.Main, theme.TextSize.Body, textXSpace)
|
||||||
|
local contentX = math.max(textBounds.X, buttonsX)
|
||||||
|
|
||||||
local paddingY, logoSize = 20, 32
|
local size = self.binding:map(function(value)
|
||||||
local actionsY = if self.props.actions then 35 else 0
|
return UDim2.fromOffset(
|
||||||
local contentX = math.max(textBounds.X, buttonsX)
|
(35 + 40 + contentX) * value,
|
||||||
|
5 + actionsY + paddingY + math.max(logoSize, textBounds.Y)
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
local size = self.binding:map(function(value)
|
|
||||||
return UDim2.fromOffset(
|
|
||||||
(35 + 40 + contentX) * value,
|
|
||||||
5 + actionsY + paddingY + math.max(logoSize, textBounds.Y)
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
|
|
||||||
return Theme.with(function(theme)
|
|
||||||
return e("TextButton", {
|
return e("TextButton", {
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Size = size,
|
Size = size,
|
||||||
@@ -144,31 +141,31 @@ function Notification:render()
|
|||||||
}, {
|
}, {
|
||||||
e(BorderedContainer, {
|
e(BorderedContainer, {
|
||||||
transparency = transparency,
|
transparency = transparency,
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.fromScale(1, 1),
|
||||||
}, {
|
}, {
|
||||||
Contents = e("Frame", {
|
Contents = e("Frame", {
|
||||||
Size = UDim2.new(0, 35 + contentX, 1, -paddingY),
|
Size = UDim2.fromScale(1, 1),
|
||||||
Position = UDim2.new(0, 0, 0, paddingY / 2),
|
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}, {
|
}, {
|
||||||
Logo = e("ImageLabel", {
|
Logo = e("ImageLabel", {
|
||||||
ImageTransparency = transparency,
|
ImageTransparency = transparency,
|
||||||
Image = Assets.Images.PluginButton,
|
Image = Assets.Images.PluginButton,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Size = UDim2.new(0, logoSize, 0, logoSize),
|
Size = UDim2.fromOffset(logoSize, logoSize),
|
||||||
Position = UDim2.new(0, 0, 0, 0),
|
Position = UDim2.new(0, 0, 0, 0),
|
||||||
AnchorPoint = Vector2.new(0, 0),
|
AnchorPoint = Vector2.new(0, 0),
|
||||||
}),
|
}),
|
||||||
Info = e("TextLabel", {
|
Info = e("TextLabel", {
|
||||||
Text = self.props.text,
|
Text = self.props.text,
|
||||||
Font = Enum.Font.GothamMedium,
|
FontFace = theme.Font.Main,
|
||||||
TextSize = 15,
|
TextSize = theme.TextSize.Body,
|
||||||
TextColor3 = theme.Notification.InfoColor,
|
TextColor3 = theme.Notification.InfoColor,
|
||||||
TextTransparency = transparency,
|
TextTransparency = transparency,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
TextYAlignment = Enum.TextYAlignment.Center,
|
||||||
TextWrapped = true,
|
TextWrapped = true,
|
||||||
|
|
||||||
Size = UDim2.new(0, textBounds.X, 0, textBounds.Y),
|
Size = UDim2.new(0, textBounds.X, 1, -actionsY),
|
||||||
Position = UDim2.fromOffset(35, 0),
|
Position = UDim2.fromOffset(35, 0),
|
||||||
|
|
||||||
LayoutOrder = 1,
|
LayoutOrder = 1,
|
||||||
@@ -176,8 +173,8 @@ function Notification:render()
|
|||||||
}),
|
}),
|
||||||
Actions = if self.props.actions
|
Actions = if self.props.actions
|
||||||
then e("Frame", {
|
then e("Frame", {
|
||||||
Size = UDim2.new(1, -40, 0, 35),
|
Size = UDim2.new(1, -40, 0, actionsY),
|
||||||
Position = UDim2.new(1, 0, 1, 0),
|
Position = UDim2.fromScale(1, 1),
|
||||||
AnchorPoint = Vector2.new(1, 1),
|
AnchorPoint = Vector2.new(1, 1),
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}, {
|
}, {
|
||||||
@@ -196,32 +193,12 @@ function Notification:render()
|
|||||||
Padding = e("UIPadding", {
|
Padding = e("UIPadding", {
|
||||||
PaddingLeft = UDim.new(0, 17),
|
PaddingLeft = UDim.new(0, 17),
|
||||||
PaddingRight = UDim.new(0, 15),
|
PaddingRight = UDim.new(0, 15),
|
||||||
|
PaddingTop = UDim.new(0, paddingY / 2),
|
||||||
|
PaddingBottom = UDim.new(0, paddingY / 2),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local Notifications = Roact.Component:extend("Notifications")
|
return Notification
|
||||||
|
|
||||||
function Notifications:render()
|
|
||||||
local notifs = {}
|
|
||||||
|
|
||||||
for id, notif in self.props.notifications do
|
|
||||||
notifs["NotifID_" .. id] = e(Notification, {
|
|
||||||
soundPlayer = self.props.soundPlayer,
|
|
||||||
text = notif.text,
|
|
||||||
timestamp = notif.timestamp,
|
|
||||||
timeout = notif.timeout,
|
|
||||||
actions = notif.actions,
|
|
||||||
layoutOrder = (notif.timestamp - baseClock),
|
|
||||||
onClose = function()
|
|
||||||
self.props.onClose(id)
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
return Roact.createFragment(notifs)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Notifications
|
|
||||||
66
plugin/src/App/Components/Notifications/init.lua
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local Notification = require(script.Notification)
|
||||||
|
local FullscreenNotification = require(script.FullscreenNotification)
|
||||||
|
|
||||||
|
local Notifications = Roact.Component:extend("Notifications")
|
||||||
|
|
||||||
|
function Notifications:render()
|
||||||
|
local popupNotifs = {}
|
||||||
|
local fullscreenNotifs = {}
|
||||||
|
|
||||||
|
for id, notif in self.props.notifications do
|
||||||
|
local targetTable = if notif.isFullscreen then fullscreenNotifs else popupNotifs
|
||||||
|
local targetComponent = if notif.isFullscreen then FullscreenNotification else Notification
|
||||||
|
targetTable["NotifID_" .. id] = e(targetComponent, {
|
||||||
|
soundPlayer = self.props.soundPlayer,
|
||||||
|
text = notif.text,
|
||||||
|
timeout = notif.timeout,
|
||||||
|
actions = notif.actions,
|
||||||
|
layoutOrder = id,
|
||||||
|
onClose = function()
|
||||||
|
if notif.onClose then
|
||||||
|
notif.onClose()
|
||||||
|
end
|
||||||
|
self.props.onClose(id)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return e("Frame", {
|
||||||
|
Size = UDim2.fromScale(1, 1),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
Fullscreen = e("Frame", {
|
||||||
|
Size = UDim2.fromScale(1, 1),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
notifs = Roact.createFragment(fullscreenNotifs),
|
||||||
|
}),
|
||||||
|
Popups = e("Frame", {
|
||||||
|
Size = UDim2.fromScale(1, 1),
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
}, {
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
HorizontalAlignment = Enum.HorizontalAlignment.Right,
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Bottom,
|
||||||
|
Padding = UDim.new(0, 5),
|
||||||
|
}),
|
||||||
|
Padding = e("UIPadding", {
|
||||||
|
PaddingTop = UDim.new(0, 5),
|
||||||
|
PaddingBottom = UDim.new(0, 5),
|
||||||
|
PaddingLeft = UDim.new(0, 5),
|
||||||
|
PaddingRight = UDim.new(0, 5),
|
||||||
|
}),
|
||||||
|
notifs = Roact.createFragment(popupNotifs),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
return Notifications
|
||||||
@@ -14,6 +14,123 @@ local EMPTY_TABLE = {}
|
|||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
local function ViewDiffButton(props)
|
||||||
|
return Theme.with(function(theme)
|
||||||
|
return e("TextButton", {
|
||||||
|
Text = "",
|
||||||
|
Size = UDim2.new(0.7, 0, 1, -4),
|
||||||
|
LayoutOrder = 2,
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
[Roact.Event.Activated] = props.onClick,
|
||||||
|
}, {
|
||||||
|
e(BorderedContainer, {
|
||||||
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
|
transparency = props.transparency:map(function(t)
|
||||||
|
return 0.5 + (0.5 * t)
|
||||||
|
end),
|
||||||
|
}, {
|
||||||
|
Layout = e("UIListLayout", {
|
||||||
|
FillDirection = Enum.FillDirection.Horizontal,
|
||||||
|
SortOrder = Enum.SortOrder.LayoutOrder,
|
||||||
|
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = Enum.VerticalAlignment.Center,
|
||||||
|
Padding = UDim.new(0, 5),
|
||||||
|
}),
|
||||||
|
Label = e("TextLabel", {
|
||||||
|
Text = "View Diff",
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
FontFace = theme.Font.Main,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
TextTransparency = props.transparency,
|
||||||
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
|
Size = UDim2.new(0, 65, 1, 0),
|
||||||
|
LayoutOrder = 1,
|
||||||
|
}),
|
||||||
|
Icon = e("ImageLabel", {
|
||||||
|
Image = Assets.Images.Icons.Expand,
|
||||||
|
ImageColor3 = theme.Settings.Setting.DescriptionColor,
|
||||||
|
ImageTransparency = props.transparency,
|
||||||
|
|
||||||
|
Size = UDim2.new(0, 16, 0, 16),
|
||||||
|
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||||
|
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||||
|
|
||||||
|
BackgroundTransparency = 1,
|
||||||
|
LayoutOrder = 2,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
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")
|
local ChangeList = Roact.Component:extend("ChangeList")
|
||||||
|
|
||||||
function ChangeList:init()
|
function ChangeList:init()
|
||||||
@@ -36,8 +153,9 @@ function ChangeList:render()
|
|||||||
PaddingRight = UDim.new(0, 5),
|
PaddingRight = UDim.new(0, 5),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local headerRow = changes[1]
|
||||||
local headers = e("Frame", {
|
local headers = e("Frame", {
|
||||||
Size = UDim2.new(1, 0, 0, 30),
|
Size = UDim2.new(1, 0, 0, 24),
|
||||||
BackgroundTransparency = rowTransparency,
|
BackgroundTransparency = rowTransparency,
|
||||||
BackgroundColor3 = theme.Diff.Row,
|
BackgroundColor3 = theme.Diff.Row,
|
||||||
LayoutOrder = 0,
|
LayoutOrder = 0,
|
||||||
@@ -49,36 +167,36 @@ 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 = tostring(changes[1][1]),
|
Text = tostring(headerRow[1]),
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Font = Enum.Font.GothamBold,
|
FontFace = theme.Font.Bold,
|
||||||
TextSize = 14,
|
TextSize = theme.TextSize.Body,
|
||||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
TextColor3 = 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("TextLabel", {
|
ColumnB = e("TextLabel", {
|
||||||
Text = tostring(changes[1][2]),
|
Text = tostring(headerRow[2]),
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Font = Enum.Font.GothamBold,
|
FontFace = theme.Font.Bold,
|
||||||
TextSize = 14,
|
TextSize = theme.TextSize.Body,
|
||||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
TextColor3 = 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.35, 0, 1, 0),
|
Size = UDim2.new(0.35, 0, 1, 0),
|
||||||
LayoutOrder = 2,
|
LayoutOrder = 2,
|
||||||
}),
|
}),
|
||||||
C = e("TextLabel", {
|
ColumnC = e("TextLabel", {
|
||||||
Text = tostring(changes[1][3]),
|
Text = tostring(headerRow[3]),
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Font = Enum.Font.GothamBold,
|
FontFace = theme.Font.Bold,
|
||||||
TextSize = 14,
|
TextSize = theme.TextSize.Body,
|
||||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
TextColor3 = theme.TextColor,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextTransparency = props.transparency,
|
TextTransparency = props.transparency,
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
@@ -95,91 +213,8 @@ function ChangeList:render()
|
|||||||
local metadata = values[4] or EMPTY_TABLE
|
local metadata = values[4] or EMPTY_TABLE
|
||||||
local isWarning = metadata.isWarning
|
local isWarning = metadata.isWarning
|
||||||
|
|
||||||
-- Special case for .Source updates
|
|
||||||
-- because we want to display a syntax highlighted diff for better UX
|
|
||||||
if self.props.showSourceDiff and tostring(values[1]) == "Source" then
|
|
||||||
rows[row] = e("Frame", {
|
|
||||||
Size = UDim2.new(1, 0, 0, 30),
|
|
||||||
BackgroundTransparency = row % 2 ~= 0 and rowTransparency or 1,
|
|
||||||
BackgroundColor3 = theme.Diff.Row,
|
|
||||||
BorderSizePixel = 0,
|
|
||||||
LayoutOrder = row,
|
|
||||||
}, {
|
|
||||||
Padding = e("UIPadding", pad),
|
|
||||||
Layout = e("UIListLayout", {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
||||||
HorizontalAlignment = Enum.HorizontalAlignment.Left,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
}),
|
|
||||||
A = e("TextLabel", {
|
|
||||||
Text = (if isWarning then "⚠ " else "") .. tostring(values[1]),
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Font = Enum.Font.GothamMedium,
|
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = if isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
||||||
Size = UDim2.new(0.3, 0, 1, 0),
|
|
||||||
LayoutOrder = 1,
|
|
||||||
}),
|
|
||||||
Button = e("TextButton", {
|
|
||||||
Text = "",
|
|
||||||
Size = UDim2.new(0.7, 0, 1, -4),
|
|
||||||
LayoutOrder = 2,
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
[Roact.Event.Activated] = function()
|
|
||||||
if props.showSourceDiff then
|
|
||||||
props.showSourceDiff(tostring(values[2]), tostring(values[3]))
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
}, {
|
|
||||||
e(BorderedContainer, {
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
|
||||||
transparency = self.props.transparency:map(function(t)
|
|
||||||
return 0.5 + (0.5 * t)
|
|
||||||
end),
|
|
||||||
}, {
|
|
||||||
Layout = e("UIListLayout", {
|
|
||||||
FillDirection = Enum.FillDirection.Horizontal,
|
|
||||||
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
||||||
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
||||||
Padding = UDim.new(0, 5),
|
|
||||||
}),
|
|
||||||
Label = e("TextLabel", {
|
|
||||||
Text = "View Diff",
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Font = Enum.Font.GothamMedium,
|
|
||||||
TextSize = 14,
|
|
||||||
TextColor3 = theme.Settings.Setting.DescriptionColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = props.transparency,
|
|
||||||
TextTruncate = Enum.TextTruncate.AtEnd,
|
|
||||||
Size = UDim2.new(0, 65, 1, 0),
|
|
||||||
LayoutOrder = 1,
|
|
||||||
}),
|
|
||||||
Icon = e("ImageLabel", {
|
|
||||||
Image = Assets.Images.Icons.Expand,
|
|
||||||
ImageColor3 = theme.Settings.Setting.DescriptionColor,
|
|
||||||
ImageTransparency = self.props.transparency,
|
|
||||||
|
|
||||||
Size = UDim2.new(0, 16, 0, 16),
|
|
||||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
||||||
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
LayoutOrder = 2,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
|
|
||||||
rows[row] = e("Frame", {
|
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,
|
FontFace = theme.Font.Main,
|
||||||
TextSize = 14,
|
TextSize = theme.TextSize.Body,
|
||||||
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,
|
transparency = props.transparency,
|
||||||
Size = UDim2.new(0.35, 0, 1, 0),
|
showStringDiff = props.showStringDiff,
|
||||||
LayoutOrder = 2,
|
showTableDiff = props.showTableDiff,
|
||||||
},
|
}),
|
||||||
e(DisplayValue, {
|
|
||||||
value = values[2],
|
|
||||||
transparency = props.transparency,
|
|
||||||
textColor = if isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
C = e(
|
|
||||||
"Frame",
|
|
||||||
{
|
|
||||||
BackgroundTransparency = 1,
|
|
||||||
Size = UDim2.new(0.35, 0, 1, 0),
|
|
||||||
LayoutOrder = 3,
|
|
||||||
},
|
|
||||||
e(DisplayValue, {
|
|
||||||
value = values[3],
|
|
||||||
transparency = props.transparency,
|
|
||||||
textColor = if isWarning then theme.Diff.Warning else theme.Settings.Setting.DescriptionColor,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
end
|
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),
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ local function DisplayValue(props)
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
Label = e("TextLabel", {
|
Label = e("TextLabel", {
|
||||||
Text = string.format("%d,%d,%d", props.value.R * 255, props.value.G * 255, props.value.B * 255),
|
Text = string.format("%d, %d, %d", props.value.R * 255, props.value.G * 255, props.value.B * 255),
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Font = Enum.Font.GothamMedium,
|
FontFace = theme.Font.Main,
|
||||||
TextSize = 14,
|
TextSize = theme.TextSize.Body,
|
||||||
TextColor3 = props.textColor,
|
TextColor3 = props.textColor,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextTransparency = props.transparency,
|
TextTransparency = props.transparency,
|
||||||
@@ -57,7 +57,7 @@ local function DisplayValue(props)
|
|||||||
-- We don't need to support mixed tables, so checking the first key is enough
|
-- We don't need to support mixed tables, so checking the first key is enough
|
||||||
-- to determine if it's a simple array
|
-- to determine if it's a simple array
|
||||||
local out, i = table.create(#props.value), 0
|
local out, i = table.create(#props.value), 0
|
||||||
for k, v in props.value do
|
for _, v in props.value do
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
-- Wrap strings in quotes
|
-- Wrap strings in quotes
|
||||||
@@ -90,8 +90,8 @@ local function DisplayValue(props)
|
|||||||
return e("TextLabel", {
|
return e("TextLabel", {
|
||||||
Text = textRepresentation,
|
Text = textRepresentation,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Font = Enum.Font.GothamMedium,
|
FontFace = theme.Font.Main,
|
||||||
TextSize = 14,
|
TextSize = theme.TextSize.Body,
|
||||||
TextColor3 = props.textColor,
|
TextColor3 = props.textColor,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextTransparency = props.transparency,
|
TextTransparency = props.transparency,
|
||||||
@@ -104,11 +104,16 @@ 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,
|
FontFace = theme.Font.Main,
|
||||||
TextSize = 14,
|
TextSize = theme.TextSize.Body,
|
||||||
TextColor3 = props.textColor,
|
TextColor3 = props.textColor,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextTransparency = props.transparency,
|
TextTransparency = props.transparency,
|
||||||
|
|||||||
@@ -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,33 +90,59 @@ 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.Background[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
|
||||||
table.insert(
|
if props.depthsComplete[i] then
|
||||||
lineGuides,
|
continue
|
||||||
e("Frame", {
|
end
|
||||||
Name = "Line_" .. i,
|
if props.isFinalChild and i == depth then
|
||||||
Size = UDim2.new(0, 2, 1, 2),
|
-- This line stops halfway down to merge with our connector for the right angle
|
||||||
Position = UDim2.new(0, (20 * i) + 15, 0, -1),
|
lineGuides["Line_" .. i] = e("Frame", {
|
||||||
|
Size = UDim2.new(0, 2, 0, 15),
|
||||||
|
Position = UDim2.new(0, (12 * (i - 1)) + 6, 0, -1),
|
||||||
BorderSizePixel = 0,
|
BorderSizePixel = 0,
|
||||||
BackgroundTransparency = props.transparency,
|
BackgroundTransparency = props.transparency,
|
||||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
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,
|
||||||
|
BackgroundTransparency = props.transparency,
|
||||||
|
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return e("Frame", {
|
return e("Frame", {
|
||||||
Name = "Change",
|
|
||||||
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),
|
||||||
@@ -145,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,
|
||||||
@@ -171,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,
|
FontFace = if props.patchType then theme.Font.Bold else theme.Font.Main,
|
||||||
TextSize = 14,
|
TextSize = theme.TextSize.Body,
|
||||||
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,
|
||||||
|
FontFace = theme.Font.Thin,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
TextColor3 = theme.SubTextColor,
|
||||||
|
TextTransparency = props.transparency,
|
||||||
|
Size = UDim2.new(0, 0, 0, theme.TextSize.Body),
|
||||||
|
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,
|
||||||
|
FontFace = theme.Font.Thin,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
TextColor3 = theme.Diff.Warning,
|
||||||
|
TextTransparency = props.transparency,
|
||||||
|
Size = UDim2.new(0, 0, 0, theme.TextSize.Body),
|
||||||
|
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
|
||||||
updateEvent = self.updateEvent,
|
for _id, sibling in parentNode.children do
|
||||||
elementHeight = elementHeight,
|
if type(sibling) == "table" and sibling.name and sibling.name > node.name then
|
||||||
setElementHeight = setElementHeight,
|
isFinalChild = false
|
||||||
patchType = node.patchType,
|
break
|
||||||
className = node.className,
|
end
|
||||||
isWarning = node.isWarning,
|
end
|
||||||
instance = node.instance,
|
end
|
||||||
name = node.name,
|
|
||||||
hint = node.hint,
|
local elementHeight, setElementHeight = Roact.createBinding(24)
|
||||||
changeList = node.changeList,
|
elementHeights[elementIndex] = elementHeight
|
||||||
depth = depth,
|
scrollElements[elementIndex] = e(DomLabel, {
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
showSourceDiff = self.props.showSourceDiff,
|
showStringDiff = self.props.showStringDiff,
|
||||||
})
|
showTableDiff = self.props.showTableDiff,
|
||||||
)
|
updateEvent = self.updateEvent,
|
||||||
|
elementHeight = elementHeight,
|
||||||
|
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,
|
||||||
|
className = node.className,
|
||||||
|
isWarning = node.isWarning,
|
||||||
|
instance = node.instance,
|
||||||
|
name = node.name,
|
||||||
|
changeInfo = node.changeInfo,
|
||||||
|
changeList = node.changeList,
|
||||||
|
})
|
||||||
|
|
||||||
|
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,21 +118,23 @@ 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", {
|
||||||
Visible = #scrollElements == 0,
|
Visible = #scrollElements == 0,
|
||||||
Text = "No changes to sync, project is up to date.",
|
Text = "No changes to sync, project is up to date.",
|
||||||
Font = Enum.Font.GothamMedium,
|
FontFace = theme.Font.Main,
|
||||||
TextSize = 15,
|
TextSize = theme.TextSize.Medium,
|
||||||
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,16 +34,21 @@ 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
|
||||||
return UDim2.new(
|
then props.contentSize:map(function(value)
|
||||||
0,
|
return UDim2.new(
|
||||||
if (props.scrollingDirection and props.scrollingDirection ~= Enum.ScrollingDirection.Y)
|
0,
|
||||||
then value.X
|
if (props.scrollingDirection and props.scrollingDirection ~= Enum.ScrollingDirection.Y)
|
||||||
else 0,
|
then value.X
|
||||||
0,
|
else 0,
|
||||||
value.Y
|
0,
|
||||||
)
|
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,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
local TextService = game:GetService("TextService")
|
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
local Packages = Rojo.Packages
|
||||||
@@ -9,7 +7,9 @@ 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 getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
|
||||||
|
|
||||||
local CodeLabel = require(Plugin.App.Components.CodeLabel)
|
local CodeLabel = require(Plugin.App.Components.CodeLabel)
|
||||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||||
@@ -31,7 +31,6 @@ function StringDiffVisualizer:init()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
self:calculateContentSize()
|
|
||||||
self:updateScriptBackground()
|
self:updateScriptBackground()
|
||||||
|
|
||||||
self:setState({
|
self:setState({
|
||||||
@@ -52,8 +51,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()
|
|
||||||
local add, remove = self:calculateDiffLines()
|
local add, remove = self:calculateDiffLines()
|
||||||
self:setState({
|
self:setState({
|
||||||
add = add,
|
add = add,
|
||||||
@@ -62,29 +60,30 @@ function StringDiffVisualizer:didUpdate(previousProps)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function StringDiffVisualizer:calculateContentSize()
|
function StringDiffVisualizer:calculateContentSize(theme)
|
||||||
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 = getTextBoundsAsync(oldString, theme.Font.Code, theme.TextSize.Code, math.huge)
|
||||||
local newTextBounds = TextService:GetTextSize(newText, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999))
|
local newStringBounds = getTextBoundsAsync(newString, theme.Font.Code, theme.TextSize.Code, math.huge)
|
||||||
|
|
||||||
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,13 +132,16 @@ 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)
|
||||||
|
self:calculateContentSize(theme)
|
||||||
|
|
||||||
return e(BorderedContainer, {
|
return e(BorderedContainer, {
|
||||||
size = self.props.size,
|
size = self.props.size,
|
||||||
position = self.props.position,
|
position = self.props.position,
|
||||||
@@ -175,8 +177,8 @@ 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.Background.Remove,
|
||||||
markedLines = self.state.remove,
|
markedLines = self.state.remove,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@@ -190,8 +192,8 @@ 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.Background.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 = theme.Diff.Background[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,
|
||||||
|
FontFace = theme.Font.Bold,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
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,
|
||||||
|
FontFace = theme.Font.Bold,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
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
|
||||||
209
plugin/src/App/Components/TableDiffVisualizer/Dictionary.lua
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
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 = theme.Diff.Background[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,
|
||||||
|
FontFace = theme.Font.Main,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
TextColor3 = theme.Diff.Text[line.patchType],
|
||||||
|
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.Diff.Text[line.patchType],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
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.Diff.Text[line.patchType],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
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,
|
||||||
|
FontFace = theme.Font.Bold,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
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,
|
||||||
|
FontFace = theme.Font.Bold,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
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,
|
||||||
|
FontFace = theme.Font.Bold,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
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
|
||||||
59
plugin/src/App/Components/Tag.lua
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
|
local Plugin = Rojo.Plugin
|
||||||
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
|
local Theme = require(Plugin.App.Theme)
|
||||||
|
local Assets = require(Plugin.Assets)
|
||||||
|
|
||||||
|
local SlicedImage = require(Plugin.App.Components.SlicedImage)
|
||||||
|
|
||||||
|
local e = Roact.createElement
|
||||||
|
|
||||||
|
return function(props)
|
||||||
|
return Theme.with(function(theme)
|
||||||
|
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, theme.TextSize.Medium),
|
||||||
|
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,
|
||||||
|
FontFace = theme.Font.Main,
|
||||||
|
TextSize = theme.TextSize.Small,
|
||||||
|
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)
|
||||||
|
end
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
local TextService = game:GetService("TextService")
|
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
local Packages = Rojo.Packages
|
||||||
@@ -10,6 +8,7 @@ local Flipper = require(Packages.Flipper)
|
|||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
local bindingUtil = require(Plugin.App.bindingUtil)
|
local bindingUtil = require(Plugin.App.bindingUtil)
|
||||||
|
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
|
||||||
|
|
||||||
local SlicedImage = require(script.Parent.SlicedImage)
|
local SlicedImage = require(script.Parent.SlicedImage)
|
||||||
local TouchRipple = require(script.Parent.TouchRipple)
|
local TouchRipple = require(script.Parent.TouchRipple)
|
||||||
@@ -41,18 +40,17 @@ end
|
|||||||
|
|
||||||
function TextButton:render()
|
function TextButton:render()
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
local textSize =
|
local textBounds = getTextBoundsAsync(self.props.text, theme.Font.Main, theme.TextSize.Large, math.huge)
|
||||||
TextService:GetTextSize(self.props.text, 18, Enum.Font.GothamSemibold, Vector2.new(math.huge, math.huge))
|
|
||||||
|
|
||||||
local style = self.props.style
|
local style = self.props.style
|
||||||
|
|
||||||
theme = theme.Button[style]
|
local buttonTheme = theme.Button[style]
|
||||||
|
|
||||||
local bindingHover = bindingUtil.deriveProperty(self.binding, "hover")
|
local bindingHover = bindingUtil.deriveProperty(self.binding, "hover")
|
||||||
local bindingEnabled = bindingUtil.deriveProperty(self.binding, "enabled")
|
local bindingEnabled = bindingUtil.deriveProperty(self.binding, "enabled")
|
||||||
|
|
||||||
return e("ImageButton", {
|
return e("ImageButton", {
|
||||||
Size = UDim2.new(0, 15 + textSize.X + 15, 0, 34),
|
Size = UDim2.new(0, (theme.TextSize.Body * 2) + textBounds.X, 0, 34),
|
||||||
Position = self.props.position,
|
Position = self.props.position,
|
||||||
AnchorPoint = self.props.anchorPoint,
|
AnchorPoint = self.props.anchorPoint,
|
||||||
|
|
||||||
@@ -74,18 +72,22 @@ function TextButton:render()
|
|||||||
end,
|
end,
|
||||||
}, {
|
}, {
|
||||||
TouchRipple = e(TouchRipple, {
|
TouchRipple = e(TouchRipple, {
|
||||||
color = theme.ActionFillColor,
|
color = buttonTheme.ActionFillColor,
|
||||||
transparency = self.props.transparency:map(function(value)
|
transparency = self.props.transparency:map(function(value)
|
||||||
return bindingUtil.blendAlpha({ theme.ActionFillTransparency, value })
|
return bindingUtil.blendAlpha({ buttonTheme.ActionFillTransparency, value })
|
||||||
end),
|
end),
|
||||||
zIndex = 2,
|
zIndex = 2,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Text = e("TextLabel", {
|
Text = e("TextLabel", {
|
||||||
Text = self.props.text,
|
Text = self.props.text,
|
||||||
Font = Enum.Font.GothamSemibold,
|
FontFace = theme.Font.Main,
|
||||||
TextSize = 18,
|
TextSize = theme.TextSize.Large,
|
||||||
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.TextColor, theme.Disabled.TextColor),
|
TextColor3 = bindingUtil.mapLerp(
|
||||||
|
bindingEnabled,
|
||||||
|
buttonTheme.Enabled.TextColor,
|
||||||
|
buttonTheme.Disabled.TextColor
|
||||||
|
),
|
||||||
TextTransparency = self.props.transparency,
|
TextTransparency = self.props.transparency,
|
||||||
|
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
Size = UDim2.new(1, 0, 1, 0),
|
||||||
@@ -95,7 +97,11 @@ function TextButton:render()
|
|||||||
|
|
||||||
Border = style == "Bordered" and e(SlicedImage, {
|
Border = style == "Bordered" and e(SlicedImage, {
|
||||||
slice = Assets.Slices.RoundedBorder,
|
slice = Assets.Slices.RoundedBorder,
|
||||||
color = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.BorderColor, theme.Disabled.BorderColor),
|
color = bindingUtil.mapLerp(
|
||||||
|
bindingEnabled,
|
||||||
|
buttonTheme.Enabled.BorderColor,
|
||||||
|
buttonTheme.Disabled.BorderColor
|
||||||
|
),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
@@ -105,14 +111,18 @@ function TextButton:render()
|
|||||||
|
|
||||||
HoverOverlay = e(SlicedImage, {
|
HoverOverlay = e(SlicedImage, {
|
||||||
slice = Assets.Slices.RoundedBackground,
|
slice = Assets.Slices.RoundedBackground,
|
||||||
color = theme.ActionFillColor,
|
color = buttonTheme.ActionFillColor,
|
||||||
transparency = Roact.joinBindings({
|
transparency = Roact.joinBindings({
|
||||||
hover = bindingHover:map(function(value)
|
hover = bindingHover:map(function(value)
|
||||||
return 1 - value
|
return 1 - value
|
||||||
end),
|
end),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
}):map(function(values)
|
}):map(function(values)
|
||||||
return bindingUtil.blendAlpha({ theme.ActionFillTransparency, values.hover, values.transparency })
|
return bindingUtil.blendAlpha({
|
||||||
|
buttonTheme.ActionFillTransparency,
|
||||||
|
values.hover,
|
||||||
|
values.transparency,
|
||||||
|
})
|
||||||
end),
|
end),
|
||||||
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
@@ -124,8 +134,8 @@ function TextButton:render()
|
|||||||
slice = Assets.Slices.RoundedBackground,
|
slice = Assets.Slices.RoundedBackground,
|
||||||
color = bindingUtil.mapLerp(
|
color = bindingUtil.mapLerp(
|
||||||
bindingEnabled,
|
bindingEnabled,
|
||||||
theme.Enabled.BackgroundColor,
|
buttonTheme.Enabled.BackgroundColor,
|
||||||
theme.Disabled.BackgroundColor
|
buttonTheme.Disabled.BackgroundColor
|
||||||
),
|
),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
|
|
||||||
|
|||||||
@@ -38,14 +38,18 @@ end
|
|||||||
|
|
||||||
function TextInput:render()
|
function TextInput:render()
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
theme = theme.TextInput
|
local textInputTheme = theme.TextInput
|
||||||
|
|
||||||
local bindingHover = bindingUtil.deriveProperty(self.binding, "hover")
|
local bindingHover = bindingUtil.deriveProperty(self.binding, "hover")
|
||||||
local bindingEnabled = bindingUtil.deriveProperty(self.binding, "enabled")
|
local bindingEnabled = bindingUtil.deriveProperty(self.binding, "enabled")
|
||||||
|
|
||||||
return e(SlicedImage, {
|
return e(SlicedImage, {
|
||||||
slice = Assets.Slices.RoundedBorder,
|
slice = Assets.Slices.RoundedBorder,
|
||||||
color = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.BorderColor, theme.Disabled.BorderColor),
|
color = bindingUtil.mapLerp(
|
||||||
|
bindingEnabled,
|
||||||
|
textInputTheme.Enabled.BorderColor,
|
||||||
|
textInputTheme.Disabled.BorderColor
|
||||||
|
),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
|
|
||||||
size = self.props.size or UDim2.new(1, 0, 1, 0),
|
size = self.props.size or UDim2.new(1, 0, 1, 0),
|
||||||
@@ -55,14 +59,18 @@ function TextInput:render()
|
|||||||
}, {
|
}, {
|
||||||
HoverOverlay = e(SlicedImage, {
|
HoverOverlay = e(SlicedImage, {
|
||||||
slice = Assets.Slices.RoundedBackground,
|
slice = Assets.Slices.RoundedBackground,
|
||||||
color = theme.ActionFillColor,
|
color = textInputTheme.ActionFillColor,
|
||||||
transparency = Roact.joinBindings({
|
transparency = Roact.joinBindings({
|
||||||
hover = bindingHover:map(function(value)
|
hover = bindingHover:map(function(value)
|
||||||
return 1 - value
|
return 1 - value
|
||||||
end),
|
end),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
}):map(function(values)
|
}):map(function(values)
|
||||||
return bindingUtil.blendAlpha({ theme.ActionFillTransparency, values.hover, values.transparency })
|
return bindingUtil.blendAlpha({
|
||||||
|
textInputTheme.ActionFillTransparency,
|
||||||
|
values.hover,
|
||||||
|
values.transparency,
|
||||||
|
})
|
||||||
end),
|
end),
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
zIndex = -1,
|
zIndex = -1,
|
||||||
@@ -72,14 +80,18 @@ function TextInput:render()
|
|||||||
Size = UDim2.fromScale(1, 1),
|
Size = UDim2.fromScale(1, 1),
|
||||||
Text = self.props.text,
|
Text = self.props.text,
|
||||||
PlaceholderText = self.props.placeholder,
|
PlaceholderText = self.props.placeholder,
|
||||||
Font = Enum.Font.GothamMedium,
|
FontFace = theme.Font.Main,
|
||||||
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Disabled.TextColor, theme.Enabled.TextColor),
|
TextColor3 = bindingUtil.mapLerp(
|
||||||
|
bindingEnabled,
|
||||||
|
textInputTheme.Disabled.TextColor,
|
||||||
|
textInputTheme.Enabled.TextColor
|
||||||
|
),
|
||||||
PlaceholderColor3 = bindingUtil.mapLerp(
|
PlaceholderColor3 = bindingUtil.mapLerp(
|
||||||
bindingEnabled,
|
bindingEnabled,
|
||||||
theme.Disabled.PlaceholderColor,
|
textInputTheme.Disabled.PlaceholderColor,
|
||||||
theme.Enabled.PlaceholderColor
|
textInputTheme.Enabled.PlaceholderColor
|
||||||
),
|
),
|
||||||
TextSize = 18,
|
TextSize = theme.TextSize.Large,
|
||||||
TextEditable = self.props.enabled,
|
TextEditable = self.props.enabled,
|
||||||
ClearTextOnFocus = self.props.clearTextOnFocus,
|
ClearTextOnFocus = self.props.clearTextOnFocus,
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
local TextService = game:GetService("TextService")
|
|
||||||
local HttpService = game:GetService("HttpService")
|
local HttpService = game:GetService("HttpService")
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
@@ -8,6 +7,8 @@ local Packages = Rojo.Packages
|
|||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
|
|
||||||
|
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
|
||||||
|
|
||||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||||
|
|
||||||
local e = Roact.createElement
|
local e = Roact.createElement
|
||||||
@@ -21,50 +22,48 @@ local Y_OVERLAP = 10 -- Let the triangle tail piece overlap the target a bit to
|
|||||||
local TooltipContext = Roact.createContext({})
|
local TooltipContext = Roact.createContext({})
|
||||||
|
|
||||||
local function Popup(props)
|
local function Popup(props)
|
||||||
local textSize = TextService:GetTextSize(
|
|
||||||
props.Text,
|
|
||||||
16,
|
|
||||||
Enum.Font.GothamMedium,
|
|
||||||
Vector2.new(math.min(props.parentSize.X, 160), math.huge)
|
|
||||||
) + TEXT_PADDING + (Vector2.one * 2)
|
|
||||||
|
|
||||||
local trigger = props.Trigger:getValue()
|
|
||||||
|
|
||||||
local spaceBelow = props.parentSize.Y
|
|
||||||
- (trigger.AbsolutePosition.Y + trigger.AbsoluteSize.Y - Y_OVERLAP + TAIL_SIZE)
|
|
||||||
local spaceAbove = trigger.AbsolutePosition.Y + Y_OVERLAP - TAIL_SIZE
|
|
||||||
|
|
||||||
-- If there's not enough space below, and there's more space above, then show the tooltip above the trigger
|
|
||||||
local displayAbove = spaceBelow < textSize.Y and spaceAbove > spaceBelow
|
|
||||||
|
|
||||||
local X = math.clamp(props.Position.X - X_OFFSET, 0, props.parentSize.X - textSize.X)
|
|
||||||
local Y = 0
|
|
||||||
|
|
||||||
if displayAbove then
|
|
||||||
Y = math.max(trigger.AbsolutePosition.Y - TAIL_SIZE - textSize.Y + Y_OVERLAP, 0)
|
|
||||||
else
|
|
||||||
Y = math.min(
|
|
||||||
trigger.AbsolutePosition.Y + trigger.AbsoluteSize.Y + TAIL_SIZE - Y_OVERLAP,
|
|
||||||
props.parentSize.Y - textSize.Y
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
|
local textXSpace = math.min(props.parentSize.X, 250) - TEXT_PADDING.X
|
||||||
|
local textBounds = getTextBoundsAsync(props.Text, theme.Font.Main, theme.TextSize.Medium, textXSpace)
|
||||||
|
local contentSize = textBounds + TEXT_PADDING + (Vector2.one * 2)
|
||||||
|
|
||||||
|
local trigger = props.Trigger:getValue()
|
||||||
|
|
||||||
|
local spaceBelow = props.parentSize.Y
|
||||||
|
- (trigger.AbsolutePosition.Y + trigger.AbsoluteSize.Y - Y_OVERLAP + TAIL_SIZE)
|
||||||
|
local spaceAbove = trigger.AbsolutePosition.Y + Y_OVERLAP - TAIL_SIZE
|
||||||
|
|
||||||
|
-- If there's not enough space below, and there's more space above, then show the tooltip above the trigger
|
||||||
|
local displayAbove = spaceBelow < contentSize.Y and spaceAbove > spaceBelow
|
||||||
|
|
||||||
|
local X = math.clamp(props.Position.X - X_OFFSET, 0, math.max(props.parentSize.X - contentSize.X, 1))
|
||||||
|
local Y = 0
|
||||||
|
|
||||||
|
if displayAbove then
|
||||||
|
Y = math.max(trigger.AbsolutePosition.Y - TAIL_SIZE - contentSize.Y + Y_OVERLAP, 0)
|
||||||
|
else
|
||||||
|
Y = math.min(
|
||||||
|
trigger.AbsolutePosition.Y + trigger.AbsoluteSize.Y + TAIL_SIZE - Y_OVERLAP,
|
||||||
|
props.parentSize.Y - contentSize.Y
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
return e(BorderedContainer, {
|
return e(BorderedContainer, {
|
||||||
position = UDim2.fromOffset(X, Y),
|
position = UDim2.fromOffset(X, Y),
|
||||||
size = UDim2.fromOffset(textSize.X, textSize.Y),
|
size = UDim2.fromOffset(contentSize.X, contentSize.Y),
|
||||||
transparency = props.transparency,
|
transparency = props.transparency,
|
||||||
}, {
|
}, {
|
||||||
Label = e("TextLabel", {
|
Label = e("TextLabel", {
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Position = UDim2.fromScale(0.5, 0.5),
|
Position = UDim2.fromScale(0.5, 0.5),
|
||||||
Size = UDim2.new(1, -TEXT_PADDING.X, 1, -TEXT_PADDING.Y),
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||||
|
Size = UDim2.fromOffset(textBounds.X, textBounds.Y),
|
||||||
Text = props.Text,
|
Text = props.Text,
|
||||||
TextSize = 16,
|
TextSize = theme.TextSize.Medium,
|
||||||
Font = Enum.Font.GothamMedium,
|
FontFace = theme.Font.Main,
|
||||||
TextWrapped = true,
|
TextWrapped = true,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
TextYAlignment = Enum.TextYAlignment.Center,
|
||||||
TextColor3 = theme.Button.Bordered.Enabled.TextColor,
|
TextColor3 = theme.Button.Bordered.Enabled.TextColor,
|
||||||
TextTransparency = props.transparency,
|
TextTransparency = props.transparency,
|
||||||
}),
|
}),
|
||||||
@@ -72,8 +71,8 @@ local function Popup(props)
|
|||||||
Tail = e("ImageLabel", {
|
Tail = e("ImageLabel", {
|
||||||
ZIndex = 100,
|
ZIndex = 100,
|
||||||
Position = if displayAbove
|
Position = if displayAbove
|
||||||
then UDim2.new(0, math.clamp(props.Position.X - X, 6, textSize.X - 6), 1, -1)
|
then UDim2.new(0, math.clamp(props.Position.X - X, 6, contentSize.X - 6), 1, -1)
|
||||||
else UDim2.new(0, math.clamp(props.Position.X - X, 6, textSize.X - 6), 0, -TAIL_SIZE + 1),
|
else UDim2.new(0, math.clamp(props.Position.X - X, 6, contentSize.X - 6), 0, -TAIL_SIZE + 1),
|
||||||
Size = UDim2.fromOffset(TAIL_SIZE, TAIL_SIZE),
|
Size = UDim2.fromOffset(TAIL_SIZE, TAIL_SIZE),
|
||||||
AnchorPoint = Vector2.new(0.5, 0),
|
AnchorPoint = Vector2.new(0.5, 0),
|
||||||
Rotation = if displayAbove then 180 else 0,
|
Rotation = if displayAbove then 180 else 0,
|
||||||
@@ -163,7 +162,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 +193,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
|
||||||
@@ -214,10 +216,10 @@ function Trigger:managePopup()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
self.showDelayThread = task.delay(DELAY, function()
|
self.showDelayThread = task.delay(self.props.delay or 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 +236,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 +246,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),
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
local TextService = game:GetService("TextService")
|
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
local 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
|
||||||
|
|
||||||
@@ -24,50 +24,75 @@ 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,
|
FontFace = theme.Font.Thin,
|
||||||
Font = Enum.Font.Gotham,
|
|
||||||
LineHeight = 1.2,
|
LineHeight = 1.2,
|
||||||
TextSize = 14,
|
TextSize = theme.TextSize.Body,
|
||||||
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, theme.TextSize.Large + 2),
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
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,
|
||||||
}),
|
}),
|
||||||
@@ -123,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,
|
||||||
@@ -131,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,
|
||||||
@@ -151,7 +176,7 @@ function ConfirmingPage:render()
|
|||||||
|
|
||||||
onClose = function()
|
onClose = function()
|
||||||
self:setState({
|
self:setState({
|
||||||
showingSourceDiff = false,
|
showingStringDiff = false,
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
}, {
|
}, {
|
||||||
@@ -167,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, {
|
|
||||||
icon = Assets.Images.Icons.Close,
|
|
||||||
iconSize = 24,
|
|
||||||
color = theme.ConnectionDetails.DisconnectColor,
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
|
|
||||||
position = UDim2.new(1, 0, 0, 0),
|
|
||||||
anchorPoint = Vector2.new(1, 0),
|
|
||||||
|
|
||||||
onClick = self.props.onClose,
|
|
||||||
}, {
|
}, {
|
||||||
Tip = e(Tooltip.Trigger, {
|
Close = e(IconButton, {
|
||||||
text = "Close the patch visualizer",
|
icon = Assets.Images.Icons.Close,
|
||||||
|
iconSize = 24,
|
||||||
|
color = theme.Settings.Navbar.BackButtonColor,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
|
||||||
|
position = UDim2.new(0, 0, 0.5, 0),
|
||||||
|
anchorPoint = Vector2.new(0, 0.5),
|
||||||
|
|
||||||
|
onClick = self.props.onBack,
|
||||||
|
}, {
|
||||||
|
Tip = e(Tooltip.Trigger, {
|
||||||
|
text = "Close",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
|
||||||
|
Title = e("TextLabel", {
|
||||||
|
Text = "Sync",
|
||||||
|
FontFace = theme.Font.Main,
|
||||||
|
TextSize = theme.TextSize.Large,
|
||||||
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
TextColor3 = theme.TextColor,
|
||||||
|
TextTransparency = self.props.transparency,
|
||||||
|
Size = UDim2.new(1, -40, 0, theme.TextSize.Large + 2),
|
||||||
|
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,
|
||||||
|
FontFace = theme.Font.Thin,
|
||||||
|
TextSize = theme.TextSize.Medium,
|
||||||
|
TextColor3 = theme.SubTextColor,
|
||||||
|
TextTruncate = Enum.TextTruncate.AtEnd,
|
||||||
|
TextTransparency = self.props.transparency,
|
||||||
|
Size = UDim2.new(1, -40, 0, theme.TextSize.Medium),
|
||||||
|
Position = UDim2.new(0, 40, 0, theme.TextSize.Large + 2),
|
||||||
|
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,
|
||||||
|
FontFace = theme.Font.Thin,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
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,
|
||||||
|
FontFace = theme.Font.Thin,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
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),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
PatchVisualizer = e(PatchVisualizer, {
|
Patch = e(PatchVisualizer, {
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
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)
|
||||||
@@ -116,13 +217,13 @@ local function ConnectionDetails(props)
|
|||||||
}, {
|
}, {
|
||||||
ProjectName = e("TextLabel", {
|
ProjectName = e("TextLabel", {
|
||||||
Text = props.projectName,
|
Text = props.projectName,
|
||||||
Font = Enum.Font.GothamBold,
|
FontFace = theme.Font.Bold,
|
||||||
TextSize = 20,
|
TextSize = theme.TextSize.Large,
|
||||||
TextColor3 = theme.ConnectionDetails.ProjectNameColor,
|
TextColor3 = theme.ConnectionDetails.ProjectNameColor,
|
||||||
TextTransparency = props.transparency,
|
TextTransparency = props.transparency,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
|
||||||
Size = UDim2.new(1, 0, 0, 20),
|
Size = UDim2.new(1, 0, 0, theme.TextSize.Large),
|
||||||
|
|
||||||
LayoutOrder = 1,
|
LayoutOrder = 1,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
@@ -130,13 +231,13 @@ local function ConnectionDetails(props)
|
|||||||
|
|
||||||
Address = e("TextLabel", {
|
Address = e("TextLabel", {
|
||||||
Text = props.address,
|
Text = props.address,
|
||||||
Font = Enum.Font.Code,
|
FontFace = theme.Font.Code,
|
||||||
TextSize = 15,
|
TextSize = theme.TextSize.Medium,
|
||||||
TextColor3 = theme.ConnectionDetails.AddressColor,
|
TextColor3 = theme.ConnectionDetails.AddressColor,
|
||||||
TextTransparency = props.transparency,
|
TextTransparency = props.transparency,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
|
|
||||||
Size = UDim2.new(1, 0, 0, 15),
|
Size = UDim2.new(1, 0, 0, theme.TextSize.Medium),
|
||||||
|
|
||||||
LayoutOrder = 2,
|
LayoutOrder = 2,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
@@ -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(self:getChangeInfoText())
|
||||||
self.setChangeInfoText("<u>" .. self:getChangeInfoText() .. "</u>")
|
|
||||||
else
|
|
||||||
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),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Header = e(Header, {
|
Heading = e("Frame", {
|
||||||
transparency = self.props.transparency,
|
BackgroundTransparency = 1,
|
||||||
layoutOrder = 1,
|
Size = UDim2.new(1, 0, 0, 32),
|
||||||
|
}, {
|
||||||
|
Header = e(Header, {
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
}),
|
||||||
|
|
||||||
|
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,
|
||||||
|
FontFace = theme.Font.Thin,
|
||||||
|
TextSize = theme.TextSize.Body,
|
||||||
|
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()
|
onClose = function()
|
||||||
self:setState({
|
self:setState({
|
||||||
hoveringChangeInfo = true,
|
renderChanges = false,
|
||||||
})
|
})
|
||||||
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,
|
end,
|
||||||
}, {
|
}, {
|
||||||
Tooltip = e(Tooltip.Trigger, {
|
TooltipsProvider = e(Tooltip.Provider, nil, {
|
||||||
text = if self.state.renderChanges then "Hide the changes" else "View the changes",
|
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,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
ChangesDrawer = e(ChangesDrawer, {
|
StringDiff = e(StudioPluginGui, {
|
||||||
rendered = self.state.renderChanges,
|
id = "Rojo_ConnectedStringDiff",
|
||||||
transparency = self.props.transparency,
|
title = "String diff",
|
||||||
patchTree = self.props.patchTree,
|
active = self.state.showingStringDiff,
|
||||||
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()
|
|
||||||
self.changeDrawerMotor:setGoal(Flipper.Spring.new(0, {
|
|
||||||
frequency = 4,
|
|
||||||
dampingRatio = 1,
|
|
||||||
}))
|
|
||||||
end,
|
|
||||||
}),
|
|
||||||
|
|
||||||
SourceDiff = e(StudioPluginGui, {
|
|
||||||
id = "Rojo_ConnectedSourceDiff",
|
|
||||||
title = "Source diff",
|
|
||||||
active = self.state.showingSourceDiff,
|
|
||||||
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,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
local TextService = game:GetService("TextService")
|
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
local Packages = Rojo.Packages
|
||||||
@@ -7,8 +5,10 @@ local Packages = Rojo.Packages
|
|||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
|
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
|
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
|
||||||
|
|
||||||
local TextButton = require(Plugin.App.Components.TextButton)
|
local TextButton = require(Plugin.App.Components.TextButton)
|
||||||
|
local Header = require(Plugin.App.Components.Header)
|
||||||
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
|
||||||
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
local ScrollingFrame = require(Plugin.App.Components.ScrollingFrame)
|
||||||
local Tooltip = require(Plugin.App.Components.Tooltip)
|
local Tooltip = require(Plugin.App.Components.Tooltip)
|
||||||
@@ -24,43 +24,44 @@ function Error:init()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Error:render()
|
function Error:render()
|
||||||
return e(BorderedContainer, {
|
return Theme.with(function(theme)
|
||||||
size = Roact.joinBindings({
|
return e(BorderedContainer, {
|
||||||
containerSize = self.props.containerSize,
|
size = Roact.joinBindings({
|
||||||
contentSize = self.contentSize,
|
containerSize = self.props.containerSize,
|
||||||
}):map(function(values)
|
contentSize = self.contentSize,
|
||||||
local maximumSize = values.containerSize
|
}):map(function(values)
|
||||||
maximumSize -= Vector2.new(14, 14) * 2 -- Page padding
|
local maximumSize = values.containerSize
|
||||||
maximumSize -= Vector2.new(0, 34 + 10) -- Buttons and spacing
|
maximumSize -= Vector2.new(14, 14) * 2 -- Page padding
|
||||||
|
maximumSize -= Vector2.new(0, 34 + 10) -- Buttons and spacing
|
||||||
|
|
||||||
local outerSize = values.contentSize + ERROR_PADDING * 2
|
local outerSize = values.contentSize + ERROR_PADDING * 2
|
||||||
|
|
||||||
return UDim2.new(1, 0, 0, math.min(outerSize.Y, maximumSize.Y))
|
return UDim2.new(1, 0, 0, math.min(outerSize.Y, maximumSize.Y))
|
||||||
end),
|
|
||||||
transparency = self.props.transparency,
|
|
||||||
}, {
|
|
||||||
ScrollingFrame = e(ScrollingFrame, {
|
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
|
||||||
contentSize = self.contentSize:map(function(value)
|
|
||||||
return value + ERROR_PADDING * 2
|
|
||||||
end),
|
end),
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
|
layoutOrder = self.props.layoutOrder,
|
||||||
[Roact.Change.AbsoluteSize] = function(object)
|
|
||||||
local containerSize = object.AbsoluteSize - ERROR_PADDING * 2
|
|
||||||
|
|
||||||
local textBounds = TextService:GetTextSize(
|
|
||||||
self.props.errorMessage,
|
|
||||||
16,
|
|
||||||
Enum.Font.Code,
|
|
||||||
Vector2.new(containerSize.X, math.huge)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.setContentSize(Vector2.new(containerSize.X, textBounds.Y))
|
|
||||||
end,
|
|
||||||
}, {
|
}, {
|
||||||
ErrorMessage = Theme.with(function(theme)
|
ScrollingFrame = e(ScrollingFrame, {
|
||||||
return e("TextBox", {
|
size = UDim2.new(1, 0, 1, 0),
|
||||||
|
contentSize = self.contentSize:map(function(value)
|
||||||
|
return value + ERROR_PADDING * 2
|
||||||
|
end),
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
|
||||||
|
[Roact.Change.AbsoluteSize] = function(object)
|
||||||
|
local containerSize = object.AbsoluteSize - ERROR_PADDING * 2
|
||||||
|
|
||||||
|
local textBounds = getTextBoundsAsync(
|
||||||
|
self.props.errorMessage,
|
||||||
|
theme.Font.Code,
|
||||||
|
theme.TextSize.Code,
|
||||||
|
containerSize.X
|
||||||
|
)
|
||||||
|
|
||||||
|
self.setContentSize(Vector2.new(containerSize.X, textBounds.Y))
|
||||||
|
end,
|
||||||
|
}, {
|
||||||
|
ErrorMessage = e("TextBox", {
|
||||||
[Roact.Event.InputBegan] = function(rbx, input)
|
[Roact.Event.InputBegan] = function(rbx, input)
|
||||||
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then
|
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then
|
||||||
return
|
return
|
||||||
@@ -71,8 +72,8 @@ function Error:render()
|
|||||||
|
|
||||||
Text = self.props.errorMessage,
|
Text = self.props.errorMessage,
|
||||||
TextEditable = false,
|
TextEditable = false,
|
||||||
Font = Enum.Font.Code,
|
FontFace = theme.Font.Code,
|
||||||
TextSize = 16,
|
TextSize = theme.TextSize.Code,
|
||||||
TextColor3 = theme.ErrorColor,
|
TextColor3 = theme.ErrorColor,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextYAlignment = Enum.TextYAlignment.Top,
|
TextYAlignment = Enum.TextYAlignment.Top,
|
||||||
@@ -81,17 +82,17 @@ function Error:render()
|
|||||||
ClearTextOnFocus = false,
|
ClearTextOnFocus = false,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
Size = UDim2.new(1, 0, 1, 0),
|
||||||
})
|
}),
|
||||||
end),
|
|
||||||
|
|
||||||
Padding = e("UIPadding", {
|
Padding = e("UIPadding", {
|
||||||
PaddingLeft = UDim.new(0, ERROR_PADDING.X),
|
PaddingLeft = UDim.new(0, ERROR_PADDING.X),
|
||||||
PaddingRight = UDim.new(0, ERROR_PADDING.X),
|
PaddingRight = UDim.new(0, ERROR_PADDING.X),
|
||||||
PaddingTop = UDim.new(0, ERROR_PADDING.Y),
|
PaddingTop = UDim.new(0, ERROR_PADDING.Y),
|
||||||
PaddingBottom = UDim.new(0, ERROR_PADDING.Y),
|
PaddingBottom = UDim.new(0, ERROR_PADDING.Y),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
})
|
||||||
})
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local ErrorPage = Roact.Component:extend("ErrorPage")
|
local ErrorPage = Roact.Component:extend("ErrorPage")
|
||||||
@@ -109,16 +110,21 @@ function ErrorPage:render()
|
|||||||
self.setContainerSize(object.AbsoluteSize)
|
self.setContainerSize(object.AbsoluteSize)
|
||||||
end,
|
end,
|
||||||
}, {
|
}, {
|
||||||
Error = e(Error, {
|
Header = e(Header, {
|
||||||
errorMessage = self.state.errorMessage,
|
|
||||||
containerSize = self.containerSize,
|
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 1,
|
layoutOrder = 1,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
Error = e(Error, {
|
||||||
|
errorMessage = self.state.errorMessage,
|
||||||
|
containerSize = self.containerSize,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
layoutOrder = 2,
|
||||||
|
}),
|
||||||
|
|
||||||
Buttons = e("Frame", {
|
Buttons = e("Frame", {
|
||||||
Size = UDim2.new(1, 0, 0, 35),
|
Size = UDim2.new(1, 0, 0, 35),
|
||||||
LayoutOrder = 2,
|
LayoutOrder = 3,
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}, {
|
}, {
|
||||||
Close = e(TextButton, {
|
Close = e(TextButton, {
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ local function AddressEntry(props)
|
|||||||
}, {
|
}, {
|
||||||
Host = e("TextBox", {
|
Host = e("TextBox", {
|
||||||
Text = props.host or "",
|
Text = props.host or "",
|
||||||
Font = Enum.Font.Code,
|
FontFace = theme.Font.Code,
|
||||||
TextSize = 18,
|
TextSize = theme.TextSize.Large,
|
||||||
TextColor3 = theme.AddressEntry.TextColor,
|
TextColor3 = theme.AddressEntry.TextColor,
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
TextXAlignment = Enum.TextXAlignment.Left,
|
||||||
TextTransparency = props.transparency,
|
TextTransparency = props.transparency,
|
||||||
@@ -51,8 +51,8 @@ local function AddressEntry(props)
|
|||||||
|
|
||||||
Port = e("TextBox", {
|
Port = e("TextBox", {
|
||||||
Text = props.port or "",
|
Text = props.port or "",
|
||||||
Font = Enum.Font.Code,
|
FontFace = theme.Font.Code,
|
||||||
TextSize = 18,
|
TextSize = theme.TextSize.Large,
|
||||||
TextColor3 = theme.AddressEntry.TextColor,
|
TextColor3 = theme.AddressEntry.TextColor,
|
||||||
TextTransparency = props.transparency,
|
TextTransparency = props.transparency,
|
||||||
PlaceholderText = Config.defaultPort,
|
PlaceholderText = Config.defaultPort,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
local TextService = game:GetService("TextService")
|
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
local Packages = Rojo.Packages
|
||||||
@@ -9,24 +7,55 @@ local Roact = require(Packages.Roact)
|
|||||||
local Settings = require(Plugin.Settings)
|
local Settings = require(Plugin.Settings)
|
||||||
local Assets = require(Plugin.Assets)
|
local Assets = require(Plugin.Assets)
|
||||||
local Theme = require(Plugin.App.Theme)
|
local Theme = require(Plugin.App.Theme)
|
||||||
|
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
|
||||||
|
|
||||||
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 getTextBoundsWithLineHeight(
|
||||||
local textBounds = TextService:GetTextSize(text, textSize, font, bounds)
|
text: string,
|
||||||
|
font: Font,
|
||||||
|
textSize: number,
|
||||||
|
width: number,
|
||||||
|
lineHeight: number
|
||||||
|
)
|
||||||
|
local textBounds = getTextBoundsAsync(text, font, textSize, width)
|
||||||
|
|
||||||
local lineCount = textBounds.Y / textSize
|
local lineCount = math.ceil(textBounds.Y / textSize)
|
||||||
local lineHeightAbsolute = textSize * lineHeight
|
local lineHeightAbsolute = textSize * lineHeight
|
||||||
|
|
||||||
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 +80,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,
|
||||||
@@ -85,6 +114,7 @@ function Setting:render()
|
|||||||
then self.props.input
|
then self.props.input
|
||||||
elseif self.props.options ~= nil then e(Dropdown, {
|
elseif self.props.options ~= nil then e(Dropdown, {
|
||||||
locked = self.props.locked,
|
locked = self.props.locked,
|
||||||
|
lockedTooltip = self.props.lockedTooltip,
|
||||||
options = self.props.options,
|
options = self.props.options,
|
||||||
active = self.state.setting,
|
active = self.state.setting,
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
@@ -94,6 +124,7 @@ function Setting:render()
|
|||||||
})
|
})
|
||||||
else e(Checkbox, {
|
else e(Checkbox, {
|
||||||
locked = self.props.locked,
|
locked = self.props.locked,
|
||||||
|
lockedTooltip = self.props.lockedTooltip,
|
||||||
active = self.state.setting,
|
active = self.state.setting,
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
onClick = function()
|
onClick = function()
|
||||||
@@ -106,7 +137,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 +151,49 @@ function Setting:render()
|
|||||||
Size = UDim2.new(1, 0, 1, 0),
|
Size = UDim2.new(1, 0, 1, 0),
|
||||||
BackgroundTransparency = 1,
|
BackgroundTransparency = 1,
|
||||||
}, {
|
}, {
|
||||||
Name = e("TextLabel", {
|
Heading = e("Frame", {
|
||||||
Text = (if self.props.experimental then '<font color="#FF8E3C">⚠ </font>' else "")
|
Size = UDim2.new(1, 0, 0, theme.TextSize.Medium),
|
||||||
.. self.props.name,
|
|
||||||
Font = Enum.Font.GothamBold,
|
|
||||||
TextSize = 17,
|
|
||||||
TextColor3 = theme.Setting.NameColor,
|
|
||||||
TextXAlignment = Enum.TextXAlignment.Left,
|
|
||||||
TextTransparency = self.props.transparency,
|
|
||||||
RichText = true,
|
|
||||||
|
|
||||||
Size = UDim2.new(1, 0, 0, 17),
|
|
||||||
|
|
||||||
LayoutOrder = 1,
|
|
||||||
BackgroundTransparency = 1,
|
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", {
|
||||||
|
Text = self.props.name,
|
||||||
|
FontFace = theme.Font.Bold,
|
||||||
|
TextSize = theme.TextSize.Medium,
|
||||||
|
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,
|
||||||
|
TextTransparency = self.props.transparency,
|
||||||
|
RichText = true,
|
||||||
|
|
||||||
|
Size = UDim2.new(1, 0, 0, theme.TextSize.Medium),
|
||||||
|
|
||||||
|
LayoutOrder = 2,
|
||||||
|
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,
|
FontFace = theme.Font.Main,
|
||||||
Font = Enum.Font.Gotham,
|
|
||||||
LineHeight = 1.2,
|
LineHeight = 1.2,
|
||||||
TextSize = 14,
|
TextSize = theme.TextSize.Body,
|
||||||
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,20 +203,18 @@ 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 = getTextBoundsWithLineHeight(
|
||||||
desc,
|
self.props.description,
|
||||||
14,
|
theme.Font.Main,
|
||||||
Enum.Font.Gotham,
|
theme.TextSize.Body,
|
||||||
1.2,
|
values.containerSize.X - offset,
|
||||||
Vector2.new(values.containerSize.X - offset, math.huge)
|
1.2
|
||||||
)
|
)
|
||||||
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 +222,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,
|
||||||
|
|||||||
@@ -26,11 +26,12 @@ local function invertTbl(tbl)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local invertedLevels = invertTbl(Log.Level)
|
local invertedLevels = invertTbl(Log.Level)
|
||||||
local confirmationBehaviors = { "Initial", "Always", "Large Changes", "Unlisted PlaceId" }
|
local confirmationBehaviors = { "Initial", "Always", "Large Changes", "Unlisted PlaceId", "Never" }
|
||||||
|
local syncReminderModes = { "None", "Notify", "Fullscreen" }
|
||||||
|
|
||||||
local function Navbar(props)
|
local function Navbar(props)
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
theme = theme.Settings.Navbar
|
local navbarTheme = theme.Settings.Navbar
|
||||||
|
|
||||||
return e("Frame", {
|
return e("Frame", {
|
||||||
Size = UDim2.new(1, 0, 0, 46),
|
Size = UDim2.new(1, 0, 0, 46),
|
||||||
@@ -40,7 +41,7 @@ local function Navbar(props)
|
|||||||
Back = e(IconButton, {
|
Back = e(IconButton, {
|
||||||
icon = Assets.Images.Icons.Back,
|
icon = Assets.Images.Icons.Back,
|
||||||
iconSize = 24,
|
iconSize = 24,
|
||||||
color = theme.BackButtonColor,
|
color = navbarTheme.BackButtonColor,
|
||||||
transparency = props.transparency,
|
transparency = props.transparency,
|
||||||
|
|
||||||
position = UDim2.new(0, 0, 0.5, 0),
|
position = UDim2.new(0, 0, 0.5, 0),
|
||||||
@@ -55,9 +56,9 @@ local function Navbar(props)
|
|||||||
|
|
||||||
Text = e("TextLabel", {
|
Text = e("TextLabel", {
|
||||||
Text = "Settings",
|
Text = "Settings",
|
||||||
Font = Enum.Font.Gotham,
|
FontFace = theme.Font.Thin,
|
||||||
TextSize = 18,
|
TextSize = theme.TextSize.Large,
|
||||||
TextColor3 = theme.TextColor,
|
TextColor3 = navbarTheme.TextColor,
|
||||||
TextTransparency = props.transparency,
|
TextTransparency = props.transparency,
|
||||||
|
|
||||||
Size = UDim2.new(1, 0, 1, 0),
|
Size = UDim2.new(1, 0, 1, 0),
|
||||||
@@ -75,18 +76,30 @@ function SettingsPage:init()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function SettingsPage:render()
|
function SettingsPage:render()
|
||||||
return Theme.with(function(theme)
|
local layoutOrder = 0
|
||||||
theme = theme.Settings
|
local function layoutIncrement()
|
||||||
|
layoutOrder += 1
|
||||||
|
return layoutOrder
|
||||||
|
end
|
||||||
|
|
||||||
return e(ScrollingFrame, {
|
return Roact.createFragment({
|
||||||
size = UDim2.new(1, 0, 1, 0),
|
Navbar = e(Navbar, {
|
||||||
|
onBack = self.props.onBack,
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
layoutOrder = layoutIncrement(),
|
||||||
|
}),
|
||||||
|
Content = e(ScrollingFrame, {
|
||||||
|
size = UDim2.new(1, 0, 1, -47),
|
||||||
|
position = UDim2.new(0, 0, 0, 47),
|
||||||
contentSize = self.contentSize,
|
contentSize = self.contentSize,
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
}, {
|
}, {
|
||||||
Navbar = e(Navbar, {
|
AutoReconnect = e(Setting, {
|
||||||
onBack = self.props.onBack,
|
id = "autoReconnect",
|
||||||
|
name = "Auto Reconnect",
|
||||||
|
description = "Reconnect to server on place open if the served project matches the last sync to the place",
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
layoutOrder = 0,
|
layoutOrder = layoutIncrement(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
ShowNotifications = e(Setting, {
|
ShowNotifications = e(Setting, {
|
||||||
@@ -94,16 +107,29 @@ function SettingsPage:render()
|
|||||||
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, {
|
SyncReminderMode = e(Setting, {
|
||||||
id = "syncReminder",
|
id = "syncReminderMode",
|
||||||
name = "Sync Reminder",
|
name = "Sync Reminder",
|
||||||
description = "Notify to sync when opening a place that has previously been synced",
|
description = "What type of reminders you receive for syncing your project",
|
||||||
transparency = self.props.transparency,
|
transparency = self.props.transparency,
|
||||||
|
layoutOrder = layoutIncrement(),
|
||||||
visible = Settings:getBinding("showNotifications"),
|
visible = Settings:getBinding("showNotifications"),
|
||||||
layoutOrder = 2,
|
|
||||||
|
options = syncReminderModes,
|
||||||
|
}),
|
||||||
|
|
||||||
|
SyncReminderPolling = e(Setting, {
|
||||||
|
id = "syncReminderPolling",
|
||||||
|
name = "Sync Reminder Polling",
|
||||||
|
description = "Look for available sync servers periodically",
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
layoutOrder = layoutIncrement(),
|
||||||
|
visible = Settings:getBindings("syncReminderMode", "showNotifications"):map(function(values)
|
||||||
|
return values.syncReminderMode ~= "None" and values.showNotifications
|
||||||
|
end),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
ConfirmationBehavior = e(Setting, {
|
ConfirmationBehavior = e(Setting, {
|
||||||
@@ -111,7 +137,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 +147,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 +178,52 @@ 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(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
EnableSyncFallback = e(Setting, {
|
||||||
|
id = "enableSyncFallback",
|
||||||
|
name = "Enable Sync Fallback",
|
||||||
|
description = "Whether Instances that fail to sync are remade as a fallback. If this is enabled, Instances may be destroyed and remade when syncing.",
|
||||||
|
transparency = self.props.transparency,
|
||||||
|
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 +231,19 @@ 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,
|
lockedTooltip = "(Cannot change while currently syncing. Disconnect first.)",
|
||||||
|
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 +258,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,8 +285,8 @@ 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
|
||||||
|
|
||||||
return SettingsPage
|
return SettingsPage
|
||||||
|
|||||||