Compare commits

...

24 Commits

Author SHA1 Message Date
Ken Loeffler
4ddbefa88f Change release build linux version to ubuntu-latest (#1034) 2025-04-25 20:04:43 +01:00
Ken Loeffler
d935115591 Release 7.5.0 (#1033) 2025-04-25 19:46:16 +01:00
Cameron Campbell
bd2ea42732 Fixes issues with refs in the plugin. (#1005) 2025-04-18 08:44:11 -07:00
Micah
3bac38ee34 Re-add the hack to write NeedsPivotMigration as false for models (#1027) 2025-04-16 15:03:09 -07:00
Micah
a7a4f6d8f2 Update rbx-dom-lua database to latest version (#1029) 2025-04-13 07:42:41 -07:00
Micah
80b6facbd3 Add missing CHANGELOG entry for 7.4.4 (#1025) 2025-04-07 16:22:08 -07:00
Parritz
7dee898400 Add place ID blacklist config (#1021) 2025-04-03 08:37:40 -07:00
Micah
4c4b2dbe17 Add legacy and runContext script sync rule middlewares (#909) 2025-04-02 12:47:27 -07:00
Sasial
73ed5ae697 Add Support for Plugin Scripts (#1008) 2025-04-02 11:37:49 -07:00
Micah
833320de64 Update rbx-dom (#1023) 2025-04-02 11:32:27 -07:00
boatbomber
0d6ff8ef8a Improve notification layout (#997) 2025-01-13 16:06:49 -08:00
Jack T
55a207a275 Fix clippy lint warnings (#1004) 2025-01-13 10:07:53 -08:00
Jack T
f33d1f1cc4 Ignore .git directory when building VfsSnapshot in build script (#1002) 2025-01-01 01:38:34 -08:00
boatbomber
19ca2b12fc Add locked tooltip (#998)
Adds the ability to define descriptive tooltips for settings when they
are locked.


![image](https://github.com/user-attachments/assets/5d5778c8-911b-4358-b4e6-f0389270ad76)


Makes some minor improvements to tooltip layout logic as well.
2024-12-28 15:03:11 -08:00
boatbomber
b7d3394464 Plugin dev ux improvements (#992)
Co-authored-by: kennethloeffler <kenloef@gmail.com>
2024-11-10 15:53:58 -08:00
boatbomber
8c33100d7a Use FontFace and consistent text sizing (#988) 2024-11-09 12:05:57 +00:00
Kenneth Loeffler
80c406f196 Fix returning NoProjectFound for any project load error (#985)
In #917, we accidentally changed ServeSession::new's project loading
logic so that it always returns `ServeSession::ProjectNotFound` if the
load fails for any reason. This PR fixes this so that it returns the
right error when there is an error loading the project, and moves the
`NoProjectFound` error to `project::Error`, since I think it makes more
sense there.
2024-11-08 08:40:32 +00:00
Kenneth Loeffler
bc2c76e5e2 Use 7.5.0-prealpha for master branch version, not 7.4.4
<p dir="auto">in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="2636858743" data-permission-text="Title is private" data-url="https://github.com/rojo-rbx/rojo/issues/989" data-hovercard-type="pull_request" data-hovercard-url="/rojo-rbx/rojo/pull/989/hovercard" href="https://github.com/rojo-rbx/rojo/pull/989">#989</a>, we changed Rojo's version number on the master branch to 7.4.4. This is a little odd, because 7.4.4 is already released, is diverged from the master branch, and we are not working towards 7.4.4 on the master branch. If we're going to spend time on this, I think we should use a more appropriate version number.</p>
<p dir="auto">This PR changes the version number to 7.5.0-prealpha, since Rojo's master branch is currently undergoing development towards 7.5.0. We will most likely <strong>not</strong> be making a release of this version - the only intent is better clarity for those running Rojo's latest master.</p>
2024-11-06 15:59:03 +00:00
boatbomber
4a7bddbc09 Update version in master branch (#989) 2024-11-05 18:10:24 -08:00
boatbomber
e316fdbaef Make sync reminder more detailed (#987) 2024-11-05 22:47:07 +00:00
Micah
34106f470f Remove maplit dependency and stop using a macro for hashmaps (#982) 2024-10-31 11:56:54 -07:00
Micah
d9ab0e7de8 Support $schema in JSON structures (#974) 2024-10-24 10:55:51 -07:00
Micah
5ca1573e2e Correct mistake in build command docs (#977) 2024-10-18 14:08:58 -07:00
Micah
c9ce996626 Update workflow and tooling versions (#910) 2024-09-03 15:36:36 -07:00
130 changed files with 11056 additions and 3222 deletions

2
.dir-locals.el Normal file
View File

@@ -0,0 +1,2 @@
((nil . ((eglot-luau-rojo-project-path . "plugin.project.json")
(eglot-luau-rojo-sourcemap-enabled . 't))))

View File

@@ -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:

View File

@@ -30,9 +30,9 @@ jobs:
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
- name: Setup Aftman - name: Setup Aftman
uses: ok-nick/setup-aftman@v0.3.0 uses: ok-nick/setup-aftman@v0.4.2
with: with:
version: 'v0.2.7' version: 'v0.3.0'
- name: Build - name: Build
run: cargo build --locked --verbose run: cargo build --locked --verbose
@@ -56,9 +56,9 @@ jobs:
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
- name: Setup Aftman - name: Setup Aftman
uses: ok-nick/setup-aftman@v0.3.0 uses: ok-nick/setup-aftman@v0.4.2
with: with:
version: 'v0.2.7' version: 'v0.3.0'
- name: Build - name: Build
run: cargo build --locked --verbose run: cargo build --locked --verbose
@@ -81,9 +81,9 @@ jobs:
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
- name: Setup Aftman - name: Setup Aftman
uses: ok-nick/setup-aftman@v0.3.0 uses: ok-nick/setup-aftman@v0.4.2
with: with:
version: 'v0.2.7' version: 'v0.3.0'
- name: Stylua - name: Stylua
run: stylua --check plugin/src run: stylua --check plugin/src

View File

@@ -26,14 +26,12 @@ jobs:
submodules: true submodules: true
- name: Setup Aftman - name: Setup Aftman
uses: ok-nick/setup-aftman@v0.1.0 uses: ok-nick/setup-aftman@v0.4.2
with: with:
token: ${{ secrets.GITHUB_TOKEN }} version: 'v0.3.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
env: env:
@@ -55,7 +53,7 @@ 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-latest
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
label: linux-x86_64 label: linux-x86_64
@@ -89,11 +87,9 @@ jobs:
targets: ${{ matrix.target }} targets: ${{ matrix.target }}
- name: Setup Aftman - name: Setup Aftman
uses: ok-nick/setup-aftman@v0.1.0 uses: ok-nick/setup-aftman@v0.4.2
with: with:
token: ${{ secrets.GITHUB_TOKEN }} version: 'v0.3.0'
trust-check: false
version: 'v0.2.6'
- name: Build Release - name: Build Release
run: cargo build --release --locked --verbose --target ${{ matrix.target }} run: cargo build --release --locked --verbose --target ${{ matrix.target }}

8
.gitignore vendored
View File

@@ -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
View 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
View File

@@ -0,0 +1,4 @@
{
"luau-lsp.sourcemap.rojoProjectFile": "plugin.project.json",
"luau-lsp.sourcemap.autogenerate": true
}

View File

@@ -1,6 +1,14 @@
# Rojo Changelog # Rojo Changelog
## Unreleased Changes ## 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]) * 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 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 is given an ID. Then, that ID may be used elsewhere in the project to point to an Instance
@@ -28,6 +36,8 @@
* Added experimental setting for Auto Connect in playtests ([#840]) * Added experimental setting for Auto Connect in playtests ([#840])
* Improved settings UI ([#886]) * Improved settings UI ([#886])
* `Open Scripts Externally` option can now be changed while syncing ([#911]) * `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]) * 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`: This is specified via a new field on project files, `syncRules`:
@@ -56,7 +66,7 @@
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. Additionally, the `exclude` field allows files to be excluded from the sync rule if they match a pattern specified by it. If it's not present, all files that match `pattern` will be modified using the sync rule.
The `use` field corresponds to one of the potential file type that Rojo will currently include in a project. Files that match the provided pattern will be treated as if they had the file extension for that file type. A full list is below: 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 | | `use` value | file extension |
|:---------------|:----------------| |:---------------|:----------------|
@@ -73,6 +83,16 @@
| `project` | `.project.json` | | `project` | `.project.json` |
| `ignore` | None! | | `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! **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 [#813]: https://github.com/rojo-rbx/rojo/pull/813
@@ -83,8 +103,19 @@
[#843]: https://github.com/rojo-rbx/rojo/pull/843 [#843]: https://github.com/rojo-rbx/rojo/pull/843
[#883]: https://github.com/rojo-rbx/rojo/pull/883 [#883]: https://github.com/rojo-rbx/rojo/pull/883
[#886]: https://github.com/rojo-rbx/rojo/pull/886 [#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 [#911]: https://github.com/rojo-rbx/rojo/pull/911
[#915]: https://github.com/rojo-rbx/rojo/pull/915 [#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 ## [7.4.3] - August 6th, 2024
* Fixed issue with building binary files introduced in 7.4.2 * Fixed issue with building binary files introduced in 7.4.2

View File

@@ -16,6 +16,23 @@ 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) * [Foreman](https://github.com/Roblox/foreman)
* [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.

180
Cargo.lock generated
View File

@@ -17,6 +17,19 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if 1.0.0",
"getrandom",
"once_cell",
"version_check",
"zerocopy",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.2" version = "1.1.2"
@@ -171,6 +184,10 @@ name = "cc"
version = "1.0.89" version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723" checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723"
dependencies = [
"jobserver",
"libc",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@@ -492,7 +509,7 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
"redox_syscall", "redox_syscall 0.4.1",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@@ -935,6 +952,15 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "jobserver"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "jod-thread" name = "jod-thread"
version = "0.1.2" version = "0.1.2"
@@ -962,9 +988,9 @@ dependencies = [
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "lazycell" name = "lazycell"
@@ -986,7 +1012,7 @@ checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
dependencies = [ dependencies = [
"bitflags 2.4.2", "bitflags 2.4.2",
"libc", "libc",
"redox_syscall", "redox_syscall 0.4.1",
] ]
[[package]] [[package]]
@@ -1001,6 +1027,16 @@ version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.21" version = "0.4.21"
@@ -1241,6 +1277,29 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall 0.5.10",
"smallvec",
"windows-targets 0.52.4",
]
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.14" version = "1.0.14"
@@ -1310,6 +1369,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]] [[package]]
name = "plotters" name = "plotters"
version = "0.3.5" version = "0.3.5"
@@ -1498,10 +1563,11 @@ dependencies = [
[[package]] [[package]]
name = "rbx_binary" name = "rbx_binary"
version = "0.7.7" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b85057e8ff75a1ce99248200c4b3c7b481a3d52f921f1053ecd67921dcc7930" checksum = "9573fee5e073d7b303f475c285197fdc8179468de66ca60ee115a58fbac99296"
dependencies = [ dependencies = [
"ahash",
"log", "log",
"lz4", "lz4",
"profiling", "profiling",
@@ -1509,23 +1575,26 @@ dependencies = [
"rbx_reflection", "rbx_reflection",
"rbx_reflection_database", "rbx_reflection_database",
"thiserror", "thiserror",
"zstd",
] ]
[[package]] [[package]]
name = "rbx_dom_weak" name = "rbx_dom_weak"
version = "2.9.0" version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcd2a17d09e46af0805f8b311a926402172b97e8d9388745c9adf8f448901841" checksum = "04425cf6e9376e5486f4fb35906c120d1b1b45618a490318cf563fab1fa230a9"
dependencies = [ dependencies = [
"ahash",
"rbx_types", "rbx_types",
"serde", "serde",
"ustr",
] ]
[[package]] [[package]]
name = "rbx_reflection" name = "rbx_reflection"
version = "4.7.0" version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8118ac6021d700e8debe324af6b40ecfd2cef270a00247849dbdfeebb0802677" checksum = "1b6d0d62baa613556b058a5f94a53b01cf0ccde0ea327ce03056e335b982e77e"
dependencies = [ dependencies = [
"rbx_types", "rbx_types",
"serde", "serde",
@@ -1534,9 +1603,9 @@ dependencies = [
[[package]] [[package]]
name = "rbx_reflection_database" name = "rbx_reflection_database"
version = "0.2.12+roblox-638" version = "1.0.2+roblox-670"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e29381d675420e841f8c02db5755cbb2545ed3e13f56c539546dc58702b512a" checksum = "5349b19e5e94fbcaba7a52175263ab64011e0a13f17ff57729f2f560ccdec615"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"rbx_reflection", "rbx_reflection",
@@ -1546,9 +1615,9 @@ dependencies = [
[[package]] [[package]]
name = "rbx_types" name = "rbx_types"
version = "1.10.0" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e30f49b2a3bb667e4074ba73c2dfb8ca0873f610b448ccf318a240acfdec6c73" checksum = "78e4fdde46493def107e5f923d82e813dec9b3eef52c2f75fbad3a716023eda2"
dependencies = [ dependencies = [
"base64 0.13.1", "base64 0.13.1",
"bitflags 1.3.2", "bitflags 1.3.2",
@@ -1561,10 +1630,11 @@ dependencies = [
[[package]] [[package]]
name = "rbx_xml" name = "rbx_xml"
version = "0.13.5" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b14b3027bc9ccd82e2fc854c8bcd25ed58318e570c355bf2cf63df9cdbd5ba8" checksum = "bb623833c31cc43bbdaeb32f5e91db8ecd63fc46e438d0d268baf9e61539cf1c"
dependencies = [ dependencies = [
"ahash",
"base64 0.13.1", "base64 0.13.1",
"log", "log",
"rbx_dom_weak", "rbx_dom_weak",
@@ -1582,6 +1652,15 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
] ]
[[package]]
name = "redox_syscall"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
dependencies = [
"bitflags 2.4.2",
]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.4" version = "0.4.4"
@@ -1752,7 +1831,7 @@ dependencies = [
[[package]] [[package]]
name = "rojo" name = "rojo"
version = "7.4.0" version = "7.5.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"backtrace", "backtrace",
@@ -1896,6 +1975,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "sct" name = "sct"
version = "0.7.1" version = "0.7.1"
@@ -2393,6 +2478,19 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "ustr"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18b19e258aa08450f93369cf56dd78063586adf19e92a75b338a800f799a0208"
dependencies = [
"ahash",
"byteorder",
"lazy_static",
"parking_lot",
"serde",
]
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.7.0" version = "1.7.0"
@@ -2784,3 +2882,51 @@ name = "yansi"
version = "0.5.1" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2 1.0.78",
"quote 1.0.35",
"syn 2.0.52",
]
[[package]]
name = "zstd"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.15+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
dependencies = [
"cc",
"pkg-config",
]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "rojo" name = "rojo"
version = "7.4.0" version = "7.5.0"
rust-version = "1.70.0" rust-version = "1.70.0"
authors = ["Lucien Greathouse <me@lpghatguy.com>"] authors = ["Lucien Greathouse <me@lpghatguy.com>"]
description = "Enables professional-grade development tools for Roblox developers" description = "Enables professional-grade development tools for Roblox developers"
@@ -51,11 +51,11 @@ memofs = { version = "0.3.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.7" rbx_binary = "1.0.0"
rbx_dom_weak = "2.9.0" rbx_dom_weak = "3.0.0"
rbx_reflection = "4.7.0" rbx_reflection = "5.0.0"
rbx_reflection_database = "0.2.12" rbx_reflection_database = "1.0.2"
rbx_xml = "0.13.5" rbx_xml = "1.0.0"
anyhow = "1.0.80" anyhow = "1.0.80"
backtrace = "0.3.69" backtrace = "0.3.69"
@@ -70,7 +70,6 @@ humantime = "2.1.0"
hyper = { version = "0.14.28", 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.21" log = "0.4.21"
maplit = "1.0.2"
num_cpus = "1.16.0" num_cpus = "1.16.0"
opener = "0.5.2" opener = "0.5.2"
rayon = "1.9.0" rayon = "1.9.0"

View File

@@ -1,5 +1,5 @@
[tools] [tools]
rojo = "rojo-rbx/rojo@7.3.0" rojo = "rojo-rbx/rojo@7.4.1"
selene = "Kampfkarren/selene@0.26.1" selene = "Kampfkarren/selene@0.27.1"
stylua = "JohnnyMorganz/stylua@0.18.2" stylua = "JohnnyMorganz/stylua@0.20.0"
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0" run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"

View File

@@ -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,12 +45,12 @@ 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 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,
@@ -54,14 +58,16 @@ fn main() -> Result<(), anyhow::Error> {
); );
let snapshot = VfsSnapshot::dir(hashmap! { let snapshot = VfsSnapshot::dir(hashmap! {
"default.project.json" => snapshot_from_fs_path(&plugin_root.join("default.project.json"))?, "default.project.json" => snapshot_from_fs_path(&root_dir.join("plugin.project.json"))?,
"fmt" => snapshot_from_fs_path(&plugin_root.join("fmt"))?, "plugin" => VfsSnapshot::dir(hashmap! {
"http" => snapshot_from_fs_path(&plugin_root.join("http"))?, "fmt" => snapshot_from_fs_path(&plugin_dir.join("fmt"))?,
"log" => snapshot_from_fs_path(&plugin_root.join("log"))?, "http" => snapshot_from_fs_path(&plugin_dir.join("http"))?,
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_root.join("rbx_dom_lua"))?, "log" => snapshot_from_fs_path(&plugin_dir.join("log"))?,
"src" => snapshot_from_fs_path(&plugin_root.join("src"))?, "rbx_dom_lua" => snapshot_from_fs_path(&plugin_dir.join("rbx_dom_lua"))?,
"Packages" => snapshot_from_fs_path(&plugin_root.join("Packages"))?, "src" => snapshot_from_fs_path(&plugin_dir.join("src"))?,
"Version.txt" => snapshot_from_fs_path(&plugin_root.join("Version.txt"))?, "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 out_path = Path::new(&out_dir).join("plugin.bincode");

View File

@@ -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();

View File

@@ -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"
} }
} }
} }

View File

@@ -1 +1 @@
7.4.0 7.5.0

View File

@@ -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,
} }

View File

@@ -31,4 +31,4 @@ function Response:json()
return HttpService:JSONDecode(self.body) return HttpService:JSONDecode(self.body)
end end
return Response return Response

View File

@@ -2,4 +2,4 @@ return function()
it("should load", function() it("should load", function()
require(script.Parent) require(script.Parent)
end) end)
end end

View File

@@ -57,4 +57,4 @@ function Log.error(template, ...)
error(Fmt.fmt(template, ...)) error(Fmt.fmt(template, ...))
end end
return Log return Log

View File

@@ -2,4 +2,4 @@ return function()
it("should load", function() it("should load", function()
require(script.Parent) require(script.Parent)
end) end)
end end

View File

@@ -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)

View File

@@ -5,6 +5,7 @@ Error.Kind = {
UnknownProperty = "UnknownProperty", UnknownProperty = "UnknownProperty",
PropertyNotReadable = "PropertyNotReadable", PropertyNotReadable = "PropertyNotReadable",
PropertyNotWritable = "PropertyNotWritable", PropertyNotWritable = "PropertyNotWritable",
CannotParseBinaryString = "CannotParseBinaryString",
Roblox = "Roblox", Roblox = "Roblox",
} }

View File

@@ -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": [

View File

@@ -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,
@@ -51,6 +53,10 @@ 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
@@ -160,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,
}, },

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -45,14 +45,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 +55,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

View File

@@ -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),

View File

@@ -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

View File

@@ -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,13 +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 = theme.IconColor, ImageColor3 = dropdownTheme.IconColor,
ImageTransparency = self.props.transparency, ImageTransparency = self.props.transparency,
Size = UDim2.new(0, 18, 0, 18), Size = UDim2.new(0, 18, 0, 18),
@@ -120,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,
}), }),
@@ -136,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)
@@ -145,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),
}), }),

View File

@@ -31,13 +31,13 @@ local function Header(props)
Version = e("TextLabel", { Version = e("TextLabel", {
Text = Version.display(Config.version), Text = Version.display(Config.version),
Font = Enum.Font.Gotham, FontFace = theme.Font.Thin,
TextSize = 14, TextSize = theme.TextSize.Body,
TextColor3 = theme.Header.VersionColor, TextColor3 = theme.Header.VersionColor,
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency, TextTransparency = props.transparency,
Size = UDim2.new(1, 0, 0, 14), Size = UDim2.new(1, 0, 0, theme.TextSize.Body),
LayoutOrder = 2, LayoutOrder = 2,
BackgroundTransparency = 1, BackgroundTransparency = 1,

View File

@@ -39,8 +39,8 @@ local function ViewDiffButton(props)
Label = e("TextLabel", { Label = e("TextLabel", {
Text = "View Diff", Text = "View Diff",
BackgroundTransparency = 1, BackgroundTransparency = 1,
Font = Enum.Font.GothamMedium, FontFace = theme.Font.Main,
TextSize = 14, TextSize = theme.TextSize.Body,
TextColor3 = theme.Settings.Setting.DescriptionColor, TextColor3 = theme.Settings.Setting.DescriptionColor,
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency, TextTransparency = props.transparency,
@@ -170,8 +170,8 @@ function ChangeList:render()
ColumnA = e("TextLabel", { ColumnA = e("TextLabel", {
Text = tostring(headerRow[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.TextColor, TextColor3 = theme.TextColor,
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency, TextTransparency = props.transparency,
@@ -182,8 +182,8 @@ function ChangeList:render()
ColumnB = e("TextLabel", { ColumnB = e("TextLabel", {
Text = tostring(headerRow[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.TextColor, TextColor3 = theme.TextColor,
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency, TextTransparency = props.transparency,
@@ -194,8 +194,8 @@ function ChangeList:render()
ColumnC = e("TextLabel", { ColumnC = e("TextLabel", {
Text = tostring(headerRow[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.TextColor, TextColor3 = theme.TextColor,
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency, TextTransparency = props.transparency,
@@ -230,8 +230,8 @@ function ChangeList:render()
ColumnA = 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.TextColor, TextColor3 = if isWarning then theme.Diff.Warning else theme.TextColor,
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency, TextTransparency = props.transparency,

View File

@@ -32,8 +32,8 @@ 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,
@@ -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,
@@ -112,8 +112,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,

View File

@@ -225,8 +225,8 @@ function DomLabel:render()
Text = (if props.isWarning then "" else "") .. props.name, Text = (if props.isWarning then "" else "") .. props.name,
RichText = true, RichText = true,
BackgroundTransparency = 1, BackgroundTransparency = 1,
Font = if props.patchType then Enum.Font.GothamBold else Enum.Font.GothamMedium, FontFace = if props.patchType then theme.Font.Bold else theme.Font.Main,
TextSize = 14, TextSize = theme.TextSize.Body,
TextColor3 = color, TextColor3 = color,
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency, TextTransparency = props.transparency,
@@ -251,11 +251,11 @@ function DomLabel:render()
then e("TextLabel", { then e("TextLabel", {
Text = props.changeInfo.edits .. if props.changeInfo.failed then "," else "", Text = props.changeInfo.edits .. if props.changeInfo.failed then "," else "",
BackgroundTransparency = 1, BackgroundTransparency = 1,
Font = Enum.Font.Gotham, FontFace = theme.Font.Thin,
TextSize = 14, TextSize = theme.TextSize.Body,
TextColor3 = theme.SubTextColor, TextColor3 = theme.SubTextColor,
TextTransparency = props.transparency, TextTransparency = props.transparency,
Size = UDim2.new(0, 0, 0, 16), Size = UDim2.new(0, 0, 0, theme.TextSize.Body),
AutomaticSize = Enum.AutomaticSize.X, AutomaticSize = Enum.AutomaticSize.X,
LayoutOrder = 2, LayoutOrder = 2,
}) })
@@ -264,11 +264,11 @@ function DomLabel:render()
then e("TextLabel", { then e("TextLabel", {
Text = props.changeInfo.failed, Text = props.changeInfo.failed,
BackgroundTransparency = 1, BackgroundTransparency = 1,
Font = Enum.Font.Gotham, FontFace = theme.Font.Thin,
TextSize = 14, TextSize = theme.TextSize.Body,
TextColor3 = theme.Diff.Warning, TextColor3 = theme.Diff.Warning,
TextTransparency = props.transparency, TextTransparency = props.transparency,
Size = UDim2.new(0, 0, 0, 16), Size = UDim2.new(0, 0, 0, theme.TextSize.Body),
AutomaticSize = Enum.AutomaticSize.X, AutomaticSize = Enum.AutomaticSize.X,
LayoutOrder = 6, LayoutOrder = 6,
}) })

View File

@@ -124,8 +124,8 @@ function PatchVisualizer:render()
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.TextColor, TextColor3 = theme.TextColor,
TextWrapped = true, TextWrapped = true,
Size = UDim2.new(1, 0, 1, 0), Size = UDim2.new(1, 0, 1, 0),

View File

@@ -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
@@ -11,6 +9,7 @@ local StringDiff = require(script:FindFirstChild("StringDiff"))
local Timer = require(Plugin.Timer) 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)
@@ -32,7 +31,6 @@ function StringDiffVisualizer:init()
end) end)
end) end)
self:calculateContentSize()
self:updateScriptBackground() self:updateScriptBackground()
self:setState({ self:setState({
@@ -54,7 +52,6 @@ end
function StringDiffVisualizer:didUpdate(previousProps) function StringDiffVisualizer:didUpdate(previousProps)
if previousProps.oldString ~= self.props.oldString or previousProps.newString ~= self.props.newString 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,
@@ -63,11 +60,11 @@ function StringDiffVisualizer:didUpdate(previousProps)
end end
end end
function StringDiffVisualizer:calculateContentSize() function StringDiffVisualizer:calculateContentSize(theme)
local oldString, newString = self.props.oldString, self.props.newString local oldString, newString = self.props.oldString, self.props.newString
local oldStringBounds = TextService:GetTextSize(oldString, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999)) local oldStringBounds = getTextBoundsAsync(oldString, theme.Font.Code, theme.TextSize.Code, math.huge)
local newStringBounds = TextService:GetTextSize(newString, 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(oldStringBounds.X, newStringBounds.X), math.max(oldStringBounds.Y, newStringBounds.Y)) Vector2.new(math.max(oldStringBounds.X, newStringBounds.X), math.max(oldStringBounds.Y, newStringBounds.Y))
@@ -143,6 +140,8 @@ function StringDiffVisualizer:render()
local oldString, newString = self.props.oldString, self.props.newString 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,

View File

@@ -152,8 +152,8 @@ function Array:render()
BackgroundTransparency = 1, BackgroundTransparency = 1,
Text = "Old", Text = "Old",
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
Font = Enum.Font.GothamBold, FontFace = theme.Font.Bold,
TextSize = 14, TextSize = theme.TextSize.Body,
TextColor3 = theme.Settings.Setting.DescriptionColor, TextColor3 = theme.Settings.Setting.DescriptionColor,
TextTruncate = Enum.TextTruncate.AtEnd, TextTruncate = Enum.TextTruncate.AtEnd,
}), }),
@@ -163,8 +163,8 @@ function Array:render()
BackgroundTransparency = 1, BackgroundTransparency = 1,
Text = "New", Text = "New",
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
Font = Enum.Font.GothamBold, FontFace = theme.Font.Bold,
TextSize = 14, TextSize = theme.TextSize.Body,
TextColor3 = theme.Settings.Setting.DescriptionColor, TextColor3 = theme.Settings.Setting.DescriptionColor,
TextTruncate = Enum.TextTruncate.AtEnd, TextTruncate = Enum.TextTruncate.AtEnd,
}), }),

View File

@@ -112,8 +112,8 @@ function Dictionary:render()
BackgroundTransparency = 1, BackgroundTransparency = 1,
Text = key, Text = key,
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
Font = Enum.Font.GothamMedium, FontFace = theme.Font.Main,
TextSize = 14, TextSize = theme.TextSize.Body,
TextColor3 = theme.Settings.Setting.DescriptionColor, TextColor3 = theme.Settings.Setting.DescriptionColor,
TextTruncate = Enum.TextTruncate.AtEnd, TextTruncate = Enum.TextTruncate.AtEnd,
}), }),
@@ -157,8 +157,8 @@ function Dictionary:render()
BackgroundTransparency = 1, BackgroundTransparency = 1,
Text = "Key", Text = "Key",
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
Font = Enum.Font.GothamBold, FontFace = theme.Font.Bold,
TextSize = 14, TextSize = theme.TextSize.Body,
TextColor3 = theme.Settings.Setting.DescriptionColor, TextColor3 = theme.Settings.Setting.DescriptionColor,
TextTruncate = Enum.TextTruncate.AtEnd, TextTruncate = Enum.TextTruncate.AtEnd,
}), }),
@@ -168,8 +168,8 @@ function Dictionary:render()
BackgroundTransparency = 1, BackgroundTransparency = 1,
Text = "Old", Text = "Old",
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
Font = Enum.Font.GothamBold, FontFace = theme.Font.Bold,
TextSize = 14, TextSize = theme.TextSize.Body,
TextColor3 = theme.Settings.Setting.DescriptionColor, TextColor3 = theme.Settings.Setting.DescriptionColor,
TextTruncate = Enum.TextTruncate.AtEnd, TextTruncate = Enum.TextTruncate.AtEnd,
}), }),
@@ -179,8 +179,8 @@ function Dictionary:render()
BackgroundTransparency = 1, BackgroundTransparency = 1,
Text = "New", Text = "New",
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
Font = Enum.Font.GothamBold, FontFace = theme.Font.Bold,
TextSize = 14, TextSize = theme.TextSize.Body,
TextColor3 = theme.Settings.Setting.DescriptionColor, TextColor3 = theme.Settings.Setting.DescriptionColor,
TextTruncate = Enum.TextTruncate.AtEnd, TextTruncate = Enum.TextTruncate.AtEnd,
}), }),

View File

@@ -4,6 +4,7 @@ local Packages = Rojo.Packages
local Roact = require(Packages.Roact) local Roact = require(Packages.Roact)
local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets) local Assets = require(Plugin.Assets)
local SlicedImage = require(Plugin.App.Components.SlicedImage) local SlicedImage = require(Plugin.App.Components.SlicedImage)
@@ -11,46 +12,48 @@ local SlicedImage = require(Plugin.App.Components.SlicedImage)
local e = Roact.createElement local e = Roact.createElement
return function(props) return function(props)
return e(SlicedImage, { return Theme.with(function(theme)
slice = Assets.Slices.RoundedBackground, return e(SlicedImage, {
color = props.color, slice = Assets.Slices.RoundedBackground,
transparency = props.transparency:map(function(transparency) color = props.color,
return 0.9 + (0.1 * transparency) transparency = props.transparency:map(function(transparency)
end), return 0.9 + (0.1 * transparency)
layoutOrder = props.layoutOrder, end),
position = props.position, layoutOrder = props.layoutOrder,
anchorPoint = props.anchorPoint, position = props.position,
size = UDim2.new(0, 0, 0, 16), anchorPoint = props.anchorPoint,
automaticSize = Enum.AutomaticSize.X, size = UDim2.new(0, 0, 0, theme.TextSize.Medium),
}, { automaticSize = Enum.AutomaticSize.X,
Padding = e("UIPadding", { }, {
PaddingLeft = UDim.new(0, 4), Padding = e("UIPadding", {
PaddingRight = UDim.new(0, 4), PaddingLeft = UDim.new(0, 4),
PaddingTop = UDim.new(0, 2), PaddingRight = UDim.new(0, 4),
PaddingBottom = UDim.new(0, 2), PaddingTop = UDim.new(0, 2),
}), PaddingBottom = UDim.new(0, 2),
Icon = if props.icon }),
then e("ImageLabel", { Icon = if props.icon
Size = UDim2.new(0, 12, 0, 12), then e("ImageLabel", {
Position = UDim2.new(0, 0, 0.5, 0), Size = UDim2.new(0, 12, 0, 12),
AnchorPoint = Vector2.new(0, 0.5), Position = UDim2.new(0, 0, 0.5, 0),
Image = props.icon, 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, BackgroundTransparency = 1,
ImageColor3 = props.color, }),
ImageTransparency = props.transparency, })
}) end)
else nil,
Text = e("TextLabel", {
Text = props.text,
Font = Enum.Font.GothamMedium,
TextSize = 12,
TextColor3 = props.color,
TextXAlignment = Enum.TextXAlignment.Center,
TextTransparency = props.transparency,
Size = UDim2.new(0, 0, 1, 0),
Position = UDim2.new(0, if props.icon then 15 else 0, 0, 0),
AutomaticSize = Enum.AutomaticSize.X,
BackgroundTransparency = 1,
}),
})
end end

View File

@@ -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.GothamMedium, 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.GothamMedium, 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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,10 +8,10 @@ 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)
@@ -86,51 +85,46 @@ 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()
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,
})
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,
@@ -147,8 +141,7 @@ function Notification:render()
size = UDim2.new(1, 0, 1, 0), size = UDim2.new(1, 0, 1, 0),
}, { }, {
Contents = e("Frame", { Contents = e("Frame", {
Size = UDim2.new(0, 35 + contentX, 1, -paddingY), Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0, 0, 0, paddingY / 2),
BackgroundTransparency = 1, BackgroundTransparency = 1,
}, { }, {
Logo = e("ImageLabel", { Logo = e("ImageLabel", {
@@ -161,14 +154,15 @@ function Notification:render()
}), }),
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,7 +170,7 @@ 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.new(1, 0, 1, 0),
AnchorPoint = Vector2.new(1, 1), AnchorPoint = Vector2.new(1, 1),
BackgroundTransparency = 1, BackgroundTransparency = 1,
@@ -196,6 +190,8 @@ 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),
}), }),
}), }),
}) })

View File

@@ -64,13 +64,13 @@ function ConfirmingPage:render()
"Sync changes for project '%s':", "Sync changes for project '%s':",
self.props.confirmData.serverInfo.projectName or "UNKNOWN" self.props.confirmData.serverInfo.projectName or "UNKNOWN"
), ),
Font = Enum.Font.Gotham, FontFace = theme.Font.Thin,
LineHeight = 1.2, LineHeight = 1.2,
TextSize = 14, TextSize = theme.TextSize.Body,
TextColor3 = theme.TextColor, 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,
}), }),

View File

@@ -61,12 +61,12 @@ function ChangesViewer:render()
Title = e("TextLabel", { Title = e("TextLabel", {
Text = "Sync", Text = "Sync",
Font = Enum.Font.GothamMedium, FontFace = theme.Font.Main,
TextSize = 17, TextSize = theme.TextSize.Large,
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
TextColor3 = theme.TextColor, TextColor3 = theme.TextColor,
TextTransparency = self.props.transparency, TextTransparency = self.props.transparency,
Size = UDim2.new(1, -40, 0, 20), Size = UDim2.new(1, -40, 0, theme.TextSize.Large + 2),
Position = UDim2.new(0, 40, 0, 0), Position = UDim2.new(0, 40, 0, 0),
BackgroundTransparency = 1, BackgroundTransparency = 1,
}), }),
@@ -74,13 +74,13 @@ function ChangesViewer:render()
Subtitle = e("TextLabel", { Subtitle = e("TextLabel", {
Text = DateTime.fromUnixTimestamp(self.props.patchData.timestamp):FormatLocalTime("LTS", "en-us"), Text = DateTime.fromUnixTimestamp(self.props.patchData.timestamp):FormatLocalTime("LTS", "en-us"),
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
Font = Enum.Font.Gotham, FontFace = theme.Font.Thin,
TextSize = 15, TextSize = theme.TextSize.Medium,
TextColor3 = theme.SubTextColor, TextColor3 = theme.SubTextColor,
TextTruncate = Enum.TextTruncate.AtEnd, TextTruncate = Enum.TextTruncate.AtEnd,
TextTransparency = self.props.transparency, TextTransparency = self.props.transparency,
Size = UDim2.new(1, -40, 0, 16), Size = UDim2.new(1, -40, 0, theme.TextSize.Medium),
Position = UDim2.new(0, 40, 0, 20), Position = UDim2.new(0, 40, 0, theme.TextSize.Large + 2),
BackgroundTransparency = 1, BackgroundTransparency = 1,
}), }),
@@ -131,8 +131,8 @@ function ChangesViewer:render()
}), }),
AppliedText = e("TextLabel", { AppliedText = e("TextLabel", {
Text = applied, Text = applied,
Font = Enum.Font.Gotham, FontFace = theme.Font.Thin,
TextSize = 15, TextSize = theme.TextSize.Body,
TextColor3 = theme.TextColor, TextColor3 = theme.TextColor,
TextTransparency = self.props.transparency, TextTransparency = self.props.transparency,
Size = UDim2.new(0, 0, 1, 0), Size = UDim2.new(0, 0, 1, 0),
@@ -156,8 +156,8 @@ function ChangesViewer:render()
}), }),
UnappliedText = e("TextLabel", { UnappliedText = e("TextLabel", {
Text = unapplied, Text = unapplied,
Font = Enum.Font.Gotham, FontFace = theme.Font.Thin,
TextSize = 15, TextSize = theme.TextSize.Body,
TextColor3 = theme.Diff.Warning, TextColor3 = theme.Diff.Warning,
TextTransparency = self.props.transparency, TextTransparency = self.props.transparency,
Size = UDim2.new(0, 0, 1, 0), Size = UDim2.new(0, 0, 1, 0),
@@ -217,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,
@@ -231,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,
@@ -410,8 +410,8 @@ function ConnectedPage:render()
Text = e("TextLabel", { Text = e("TextLabel", {
BackgroundTransparency = 1, BackgroundTransparency = 1,
Text = self.changeInfoText, Text = self.changeInfoText,
Font = Enum.Font.Gotham, FontFace = theme.Font.Thin,
TextSize = 15, TextSize = theme.TextSize.Body,
TextColor3 = if syncWarning then theme.Diff.Warning else theme.Header.VersionColor, TextColor3 = if syncWarning then theme.Diff.Warning else theme.Header.VersionColor,
TextTransparency = self.props.transparency, TextTransparency = self.props.transparency,
TextXAlignment = Enum.TextXAlignment.Right, TextXAlignment = Enum.TextXAlignment.Right,

View File

@@ -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,6 +5,7 @@ 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 BorderedContainer = require(Plugin.App.Components.BorderedContainer) local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
@@ -24,43 +23,43 @@ 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,
[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 +70,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 +80,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")

View File

@@ -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,

View File

@@ -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,6 +7,7 @@ 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)
@@ -31,10 +30,16 @@ local TAG_TYPES = {
}, },
} }
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))
@@ -109,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,
@@ -118,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()
@@ -145,7 +152,7 @@ function Setting:render()
BackgroundTransparency = 1, BackgroundTransparency = 1,
}, { }, {
Heading = e("Frame", { Heading = e("Frame", {
Size = UDim2.new(1, 0, 0, 16), Size = UDim2.new(1, 0, 0, theme.TextSize.Medium),
BackgroundTransparency = 1, BackgroundTransparency = 1,
}, { }, {
Layout = e("UIListLayout", { Layout = e("UIListLayout", {
@@ -165,8 +172,8 @@ function Setting:render()
else nil, else nil,
Name = e("TextLabel", { Name = e("TextLabel", {
Text = self.props.name, Text = self.props.name,
Font = Enum.Font.GothamBold, FontFace = theme.Font.Bold,
TextSize = 16, TextSize = theme.TextSize.Medium,
TextColor3 = if self.props.tag and TAG_TYPES[self.props.tag] TextColor3 = if self.props.tag and TAG_TYPES[self.props.tag]
then getThemeColorFromPath(theme, TAG_TYPES[self.props.tag].color) then getThemeColorFromPath(theme, TAG_TYPES[self.props.tag].color)
else settingsTheme.Setting.NameColor, else settingsTheme.Setting.NameColor,
@@ -174,7 +181,7 @@ function Setting:render()
TextTransparency = self.props.transparency, TextTransparency = self.props.transparency,
RichText = true, RichText = true,
Size = UDim2.new(1, 0, 0, 16), Size = UDim2.new(1, 0, 0, theme.TextSize.Medium),
LayoutOrder = 2, LayoutOrder = 2,
BackgroundTransparency = 1, BackgroundTransparency = 1,
@@ -183,9 +190,9 @@ function Setting:render()
Description = e("TextLabel", { Description = e("TextLabel", {
Text = self.props.description, Text = self.props.description,
Font = Enum.Font.Gotham, FontFace = theme.Font.Main,
LineHeight = 1.2, LineHeight = 1.2,
TextSize = 14, TextSize = theme.TextSize.Body,
TextColor3 = settingsTheme.Setting.DescriptionColor, TextColor3 = settingsTheme.Setting.DescriptionColor,
TextXAlignment = Enum.TextXAlignment.Left, TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency, TextTransparency = self.props.transparency,
@@ -197,12 +204,12 @@ function Setting:render()
inputSize = self.inputSize, inputSize = self.inputSize,
}):map(function(values) }):map(function(values)
local offset = values.inputSize.X + 5 local offset = values.inputSize.X + 5
local textBounds = getTextBounds( local textBounds = getTextBoundsWithLineHeight(
self.props.description, 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),

View File

@@ -30,7 +30,7 @@ local confirmationBehaviors = { "Initial", "Always", "Large Changes", "Unlisted
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 +40,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 +55,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),
@@ -81,185 +81,182 @@ function SettingsPage:render()
return layoutOrder return layoutOrder
end end
return Theme.with(function(theme) return Roact.createFragment({
theme = theme.Settings Navbar = e(Navbar, {
onBack = self.props.onBack,
return Roact.createFragment({ transparency = self.props.transparency,
Navbar = e(Navbar, { layoutOrder = layoutIncrement(),
onBack = self.props.onBack, }),
Content = e(ScrollingFrame, {
size = UDim2.new(1, 0, 1, -47),
position = UDim2.new(0, 0, 0, 47),
contentSize = self.contentSize,
transparency = self.props.transparency,
}, {
ShowNotifications = e(Setting, {
id = "showNotifications",
name = "Show Notifications",
description = "Popup notifications in viewport",
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = layoutIncrement(), layoutOrder = layoutIncrement(),
}), }),
Content = e(ScrollingFrame, {
size = UDim2.new(1, 0, 1, -47), SyncReminder = e(Setting, {
position = UDim2.new(0, 0, 0, 47), id = "syncReminder",
contentSize = self.contentSize, name = "Sync Reminder",
description = "Notify to sync when opening a place that has previously been synced",
transparency = self.props.transparency, transparency = self.props.transparency,
}, { visible = Settings:getBinding("showNotifications"),
ShowNotifications = e(Setting, { layoutOrder = layoutIncrement(),
id = "showNotifications", }),
name = "Show Notifications",
description = "Popup notifications in viewport",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
}),
SyncReminder = e(Setting, { ConfirmationBehavior = e(Setting, {
id = "syncReminder", id = "confirmationBehavior",
name = "Sync Reminder", name = "Confirmation Behavior",
description = "Notify to sync when opening a place that has previously been synced", description = "When to prompt for confirmation before syncing",
transparency = self.props.transparency, transparency = self.props.transparency,
visible = Settings:getBinding("showNotifications"), layoutOrder = layoutIncrement(),
layoutOrder = layoutIncrement(),
}),
ConfirmationBehavior = e(Setting, { options = confirmationBehaviors,
id = "confirmationBehavior", }),
name = "Confirmation Behavior",
description = "When to prompt for confirmation before syncing",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
options = confirmationBehaviors, LargeChangesConfirmationThreshold = e(Setting, {
}), id = "largeChangesConfirmationThreshold",
name = "Confirmation Threshold",
LargeChangesConfirmationThreshold = e(Setting, { description = "How many modified instances to be considered a large change",
id = "largeChangesConfirmationThreshold", transparency = self.props.transparency,
name = "Confirmation Threshold", layoutOrder = layoutIncrement(),
description = "How many modified instances to be considered a large change", visible = Settings:getBinding("confirmationBehavior"):map(function(value)
transparency = self.props.transparency, return value == "Large Changes"
layoutOrder = layoutIncrement(), end),
visible = Settings:getBinding("confirmationBehavior"):map(function(value) input = e(TextInput, {
return value == "Large Changes" size = UDim2.new(0, 40, 0, 28),
text = Settings:getBinding("largeChangesConfirmationThreshold"):map(function(value)
return tostring(value)
end), end),
input = e(TextInput, {
size = UDim2.new(0, 40, 0, 28),
text = Settings:getBinding("largeChangesConfirmationThreshold"):map(function(value)
return tostring(value)
end),
transparency = self.props.transparency,
enabled = true,
onEntered = function(text)
local number = tonumber(string.match(text, "%d+"))
if number then
Settings:set("largeChangesConfirmationThreshold", math.clamp(number, 1, 999))
else
-- Force text back to last valid value
Settings:set(
"largeChangesConfirmationThreshold",
Settings:get("largeChangesConfirmationThreshold")
)
end
end,
}),
}),
PlaySounds = e(Setting, {
id = "playSounds",
name = "Play Sounds",
description = "Toggle sound effects",
transparency = self.props.transparency, transparency = self.props.transparency,
layoutOrder = layoutIncrement(), enabled = true,
}), onEntered = function(text)
local number = tonumber(string.match(text, "%d+"))
CheckForUpdates = e(Setting, { if number then
id = "checkForUpdates", Settings:set("largeChangesConfirmationThreshold", math.clamp(number, 1, 999))
name = "Check For Updates", else
description = "Notify about newer compatible Rojo releases", -- Force text back to last valid value
transparency = self.props.transparency, Settings:set(
layoutOrder = layoutIncrement(), "largeChangesConfirmationThreshold",
}), Settings:get("largeChangesConfirmationThreshold")
)
CheckForPreleases = e(Setting, { end
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, {
id = "openScriptsExternally",
name = "Open Scripts Externally",
description = "Attempt to open scripts in an external editor",
tag = "unstable",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
}),
TwoWaySync = e(Setting, {
id = "twoWaySync",
name = "Two-Way Sync",
description = "Editing files in Studio will sync them into the filesystem",
locked = self.props.syncActive,
tag = "unstable",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
}),
LogLevel = e(Setting, {
id = "logLevel",
name = "Log Level",
description = "Plugin output verbosity level",
tag = "debug",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
options = invertedLevels,
showReset = Settings:getBinding("logLevel"):map(function(value)
return value ~= "Info"
end),
onReset = function()
Settings:set("logLevel", "Info")
end, end,
}), }),
TypecheckingEnabled = e(Setting, {
id = "typecheckingEnabled",
name = "Typechecking",
description = "Toggle typechecking on the API surface",
tag = "debug",
transparency = self.props.transparency,
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", {
FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder,
[Roact.Change.AbsoluteContentSize] = function(object)
self.setContentSize(object.AbsoluteContentSize)
end,
}),
Padding = e("UIPadding", {
PaddingLeft = UDim.new(0, 20),
PaddingRight = UDim.new(0, 20),
}),
}), }),
})
end) PlaySounds = e(Setting, {
id = "playSounds",
name = "Play Sounds",
description = "Toggle sound effects",
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, {
id = "openScriptsExternally",
name = "Open Scripts Externally",
description = "Attempt to open scripts in an external editor",
tag = "unstable",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
}),
TwoWaySync = e(Setting, {
id = "twoWaySync",
name = "Two-Way Sync",
description = "Editing files in Studio will sync them into the filesystem",
locked = self.props.syncActive,
lockedTooltip = "(Cannot change while currently syncing. Disconnect first.)",
tag = "unstable",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
}),
LogLevel = e(Setting, {
id = "logLevel",
name = "Log Level",
description = "Plugin output verbosity level",
tag = "debug",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
options = invertedLevels,
showReset = Settings:getBinding("logLevel"):map(function(value)
return value ~= "Info"
end),
onReset = function()
Settings:set("logLevel", "Info")
end,
}),
TypecheckingEnabled = e(Setting, {
id = "typecheckingEnabled",
name = "Typechecking",
description = "Toggle typechecking on the API surface",
tag = "debug",
transparency = self.props.transparency,
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", {
FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder,
[Roact.Change.AbsoluteContentSize] = function(object)
self.setContentSize(object.AbsoluteContentSize)
end,
}),
Padding = e("UIPadding", {
PaddingLeft = UDim.new(0, 20),
PaddingRight = UDim.new(0, 20),
}),
}),
})
end end
return SettingsPage return SettingsPage

View File

@@ -1,7 +1,6 @@
--[[ --[[
Theming system taking advantage of Roact's new context API. Theming system provided through Roact's context.
Doesn't use colors provided by Studio and instead just branches on theme Uses Studio colors when possible.
name. This isn't exactly best practice.
]] ]]
-- Studio does not exist outside Roblox Studio, so we'll lazily initialize it -- Studio does not exist outside Roblox Studio, so we'll lazily initialize it
@@ -15,6 +14,8 @@ local function getStudio()
return _Studio return _Studio
end end
local ContentProvider = game:GetService("ContentProvider")
local Rojo = script:FindFirstAncestor("Rojo") local Rojo = script:FindFirstAncestor("Rojo")
local Packages = Rojo.Packages local Packages = Rojo.Packages
@@ -35,6 +36,27 @@ function StudioProvider:updateTheme()
local isDark = studioTheme.Name == "Dark" local isDark = studioTheme.Name == "Dark"
local theme = strict(studioTheme.Name .. "Theme", { local theme = strict(studioTheme.Name .. "Theme", {
Font = {
Main = Font.new("rbxasset://fonts/families/Montserrat.json", Enum.FontWeight.Medium, Enum.FontStyle.Normal),
Bold = Font.new("rbxasset://fonts/families/Montserrat.json", Enum.FontWeight.Bold, Enum.FontStyle.Normal),
Thin = Font.new(
"rbxasset://fonts/families/Montserrat.json",
Enum.FontWeight.Regular,
Enum.FontStyle.Normal
),
Code = Font.new(
"rbxasset://fonts/families/Inconsolata.json",
Enum.FontWeight.Regular,
Enum.FontStyle.Normal
),
},
TextSize = {
Body = 15,
Small = 13,
Medium = 16,
Large = 18,
Code = 16,
},
BrandColor = BRAND_COLOR, BrandColor = BRAND_COLOR,
BackgroundColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainBackground), BackgroundColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainBackground),
TextColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainText), TextColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainText),
@@ -190,6 +212,13 @@ end
function StudioProvider:init() function StudioProvider:init()
self:updateTheme() self:updateTheme()
-- Preload the Fonts so that getTextBoundsAsync won't yield
local fontAssetIds = {}
for _, font in self.state.theme.Font do
table.insert(fontAssetIds, font.Family)
end
pcall(ContentProvider.PreloadAsync, ContentProvider, fontAssetIds)
end end
function StudioProvider:render() function StudioProvider:render()

View File

@@ -0,0 +1,41 @@
local TextService = game:GetService("TextService")
local Rojo = script:FindFirstAncestor("Rojo")
local Packages = Rojo.Packages
local Log = require(Packages.Log)
local params = Instance.new("GetTextBoundsParams")
local function getTextBoundsAsync(
text: string,
font: Font,
textSize: number,
width: number,
richText: boolean?
): Vector2
if type(text) ~= "string" then
Log.warn(`Invalid text. Expected string, received {type(text)} instead`)
return Vector2.zero
end
if #text >= 200_000 then
Log.warn(`Invalid text. Exceeds the 199,999 character limit`)
return Vector2.zero
end
params.Text = text
params.Font = font
params.Size = textSize
params.Width = width
params.RichText = not not richText
local success, bounds = pcall(TextService.GetTextBoundsAsync, TextService, params)
if not success then
Log.warn(`Failed to get text bounds: {bounds}`)
return Vector2.zero
end
return bounds
end
return getTextBoundsAsync

View File

@@ -52,9 +52,9 @@ local App = Roact.Component:extend("App")
function App:init() function App:init()
preloadAssets() preloadAssets()
local priorHost, priorPort = self:getPriorEndpoint() local priorSyncInfo = self:getPriorSyncInfo()
self.host, self.setHost = Roact.createBinding(priorHost or "") self.host, self.setHost = Roact.createBinding(priorSyncInfo.host or "")
self.port, self.setPort = Roact.createBinding(priorPort or "") self.port, self.setPort = Roact.createBinding(priorSyncInfo.port or "")
self.confirmationBindable = Instance.new("BindableEvent") self.confirmationBindable = Instance.new("BindableEvent")
self.confirmationEvent = self.confirmationBindable.Event self.confirmationEvent = self.confirmationBindable.Event
@@ -145,28 +145,38 @@ function App:init()
if if
Settings:get("syncReminder") Settings:get("syncReminder")
and self.serveSession == nil and self.serveSession == nil
and self:getLastSyncTimestamp() and self:getPriorSyncInfo().timestamp ~= nil
and (self:isSyncLockAvailable()) and (self:isSyncLockAvailable())
then then
self:addNotification("You've previously synced this place. Would you like to reconnect?", 300, { local syncInfo = self:getPriorSyncInfo()
Connect = { local timeSinceSync = timeUtil.elapsedToText(os.time() - syncInfo.timestamp)
text = "Connect", local syncDetail = if syncInfo.projectName
style = "Solid", then `project '{syncInfo.projectName}'`
layoutOrder = 1, else `{syncInfo.host or Config.defaultHost}:{syncInfo.port or Config.defaultPort}`
onClick = function(notification)
notification:dismiss() self:addNotification(
self:startSession() `You synced {syncDetail} to this place {timeSinceSync}. Would you like to reconnect?`,
end, 300,
}, {
Dismiss = { Connect = {
text = "Dismiss", text = "Connect",
style = "Bordered", style = "Solid",
layoutOrder = 2, layoutOrder = 1,
onClick = function(notification) onClick = function(notification)
notification:dismiss() notification:dismiss()
end, self:startSession()
}, end,
}) },
Dismiss = {
text = "Dismiss",
style = "Bordered",
layoutOrder = 2,
onClick = function(notification)
notification:dismiss()
end,
},
}
)
end end
end end
@@ -274,54 +284,32 @@ function App:checkForUpdates()
) )
end end
function App:getPriorEndpoint() function App:getPriorSyncInfo(): { host: string?, port: string?, projectName: string?, timestamp: number? }
local priorEndpoints = Settings:get("priorEndpoints") local priorSyncInfos = Settings:get("priorEndpoints")
if not priorEndpoints then if not priorSyncInfos then
return return {}
end end
local id = tostring(game.PlaceId) local id = tostring(game.PlaceId)
if ignorePlaceIds[id] then if ignorePlaceIds[id] then
return return {}
end end
local place = priorEndpoints[id] return priorSyncInfos[id] or {}
if not place then
return
end
return place.host, place.port
end end
function App:getLastSyncTimestamp() function App:setPriorSyncInfo(host: string, port: string, projectName: string)
local priorEndpoints = Settings:get("priorEndpoints") local priorSyncInfos = Settings:get("priorEndpoints")
if not priorEndpoints then if not priorSyncInfos then
return priorSyncInfos = {}
end end
local id = tostring(game.PlaceId) local now = os.time()
if ignorePlaceIds[id] then
return
end
local place = priorEndpoints[id]
if not place then
return
end
return place.timestamp
end
function App:setPriorEndpoint(host: string, port: string)
local priorEndpoints = Settings:get("priorEndpoints")
if not priorEndpoints then
priorEndpoints = {}
end
-- Clear any stale saves to avoid disc bloat -- Clear any stale saves to avoid disc bloat
for placeId, endpoint in priorEndpoints do for placeId, syncInfo in priorSyncInfos do
if os.time() - endpoint.timestamp > 12_960_000 then if now - (syncInfo.timestamp or now) > 12_960_000 then
priorEndpoints[placeId] = nil priorSyncInfos[placeId] = nil
Log.trace("Cleared stale saved endpoint for {}", placeId) Log.trace("Cleared stale saved endpoint for {}", placeId)
end end
end end
@@ -331,14 +319,15 @@ function App:setPriorEndpoint(host: string, port: string)
return return
end end
priorEndpoints[id] = { priorSyncInfos[id] = {
host = if host ~= Config.defaultHost then host else nil, host = if host ~= Config.defaultHost then host else nil,
port = if port ~= Config.defaultPort then port else nil, port = if port ~= Config.defaultPort then port else nil,
timestamp = os.time(), projectName = projectName,
timestamp = now,
} }
Log.trace("Saved last used endpoint for {}", game.PlaceId) Log.trace("Saved last used endpoint for {}", game.PlaceId)
Settings:set("priorEndpoints", priorEndpoints) Settings:set("priorEndpoints", priorSyncInfos)
end end
function App:getHostAndPort() function App:getHostAndPort()
@@ -533,8 +522,6 @@ function App:startSession()
serveSession:onStatusChanged(function(status, details) serveSession:onStatusChanged(function(status, details)
if status == ServeSession.Status.Connecting then if status == ServeSession.Status.Connecting then
self:setPriorEndpoint(host, port)
self:setState({ self:setState({
appStatus = AppStatus.Connecting, appStatus = AppStatus.Connecting,
toolbarIcon = Assets.Images.PluginButton, toolbarIcon = Assets.Images.PluginButton,
@@ -542,6 +529,7 @@ function App:startSession()
self:addNotification("Connecting to session...") self:addNotification("Connecting to session...")
elseif status == ServeSession.Status.Connected then elseif status == ServeSession.Status.Connected then
self.knownProjects[details] = true self.knownProjects[details] = true
self:setPriorSyncInfo(host, port, details)
self:setRunningConnectionInfo(baseUrl) self:setRunningConnectionInfo(baseUrl)
local address = ("%s:%s"):format(host, port) local address = ("%s:%s"):format(host, port)

View File

@@ -16,6 +16,7 @@ local invariant = require(script.Parent.Parent.invariant)
local decodeValue = require(script.Parent.decodeValue) local decodeValue = require(script.Parent.decodeValue)
local reify = require(script.Parent.reify) local reify = require(script.Parent.reify)
local reifyInstance, applyDeferredRefs = reify.reifyInstance, reify.applyDeferredRefs
local setProperty = require(script.Parent.setProperty) local setProperty = require(script.Parent.setProperty)
local function applyPatch(instanceMap, patch) local function applyPatch(instanceMap, patch)
@@ -29,6 +30,11 @@ local function applyPatch(instanceMap, patch)
-- Tracks any portions of the patch that could not be applied to the DOM. -- Tracks any portions of the patch that could not be applied to the DOM.
local unappliedPatch = PatchSet.newEmpty() local unappliedPatch = PatchSet.newEmpty()
-- Contains a list of all of the ref properties that we'll need to assign.
-- It is imperative that refs are assigned after all instances are created
-- to ensure that referents can be mapped to instances correctly.
local deferredRefs = {}
for _, removedIdOrInstance in ipairs(patch.removed) do for _, removedIdOrInstance in ipairs(patch.removed) do
local removeInstanceSuccess = pcall(function() local removeInstanceSuccess = pcall(function()
if Types.RbxId(removedIdOrInstance) then if Types.RbxId(removedIdOrInstance) then
@@ -78,7 +84,7 @@ local function applyPatch(instanceMap, patch)
) )
end end
local failedToReify = reify(instanceMap, patch.added, id, parentInstance) local failedToReify = reifyInstance(deferredRefs, instanceMap, patch.added, id, parentInstance)
if not PatchSet.isEmpty(failedToReify) then if not PatchSet.isEmpty(failedToReify) then
Log.debug("Failed to reify as part of applying a patch: {:#?}", failedToReify) Log.debug("Failed to reify as part of applying a patch: {:#?}", failedToReify)
@@ -143,7 +149,7 @@ local function applyPatch(instanceMap, patch)
[update.id] = mockVirtualInstance, [update.id] = mockVirtualInstance,
} }
local failedToReify = reify(instanceMap, mockAdded, update.id, instance.Parent) local failedToReify = reifyInstance(deferredRefs, instanceMap, mockAdded, update.id, instance.Parent)
local newInstance = instanceMap.fromIds[update.id] local newInstance = instanceMap.fromIds[update.id]
@@ -206,6 +212,18 @@ local function applyPatch(instanceMap, patch)
if update.changedProperties ~= nil then if update.changedProperties ~= nil then
for propertyName, propertyValue in pairs(update.changedProperties) do for propertyName, propertyValue in pairs(update.changedProperties) do
-- Because refs may refer to instances that we haven't constructed yet,
-- we defer applying any ref properties until all instances are created.
if next(propertyValue) == "Ref" then
table.insert(deferredRefs, {
id = update.id,
instance = instance,
propertyName = propertyName,
virtualValue = propertyValue,
})
continue
end
local decodeSuccess, decodedValue = decodeValue(propertyValue, instanceMap) local decodeSuccess, decodedValue = decodeValue(propertyValue, instanceMap)
if not decodeSuccess then if not decodeSuccess then
unappliedUpdate.changedProperties[propertyName] = propertyValue unappliedUpdate.changedProperties[propertyName] = propertyValue
@@ -230,6 +248,8 @@ local function applyPatch(instanceMap, patch)
ChangeHistoryService:FinishRecording(historyRecording, Enum.FinishRecordingOperation.Commit) ChangeHistoryService:FinishRecording(historyRecording, Enum.FinishRecordingOperation.Commit)
end end
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
return unappliedPatch return unappliedPatch
end end

View File

@@ -151,7 +151,24 @@ local function diff(instanceMap, virtualInstances, rootId)
if getProperySuccess then if getProperySuccess then
local existingValue = existingValueOrErr local existingValue = existingValueOrErr
local decodeSuccess, decodedValue = decodeValue(virtualValue, instanceMap) local decodeSuccess, decodedValue
-- If `virtualValue` is a ref then instead of decoding it to an instance,
-- we change `existingValue` to be a ref. This is because `virtualValue`
-- may point to an Instance which doesn't exist yet and therefore
-- decoding it may throw an error.
if next(virtualValue) == "Ref" then
decodeSuccess, decodedValue = true, virtualValue
if existingValue and typeof(existingValue) == "Instance" then
local existingValueRef = instanceMap.fromInstances[existingValue]
if existingValueRef then
existingValue = { Ref = existingValueRef }
end
end
else
decodeSuccess, decodedValue = decodeValue(virtualValue, instanceMap)
end
if decodeSuccess then if decodeSuccess then
if not trueEquals(existingValue, decodedValue) then if not trueEquals(existingValue, decodedValue) then

View File

@@ -7,26 +7,6 @@ local PatchSet = require(script.Parent.Parent.PatchSet)
local setProperty = require(script.Parent.setProperty) local setProperty = require(script.Parent.setProperty)
local decodeValue = require(script.Parent.decodeValue) local decodeValue = require(script.Parent.decodeValue)
local reifyInner, applyDeferredRefs
local function reify(instanceMap, virtualInstances, rootId, parentInstance)
-- Create an empty patch that will be populated with any parts of this reify
-- that could not happen, like instances that couldn't be created and
-- properties that could not be assigned.
local unappliedPatch = PatchSet.newEmpty()
-- Contains a list of all of the ref properties that we'll need to assign
-- after all instances are created. We apply refs in a second pass, after
-- we create as many instances as we can, so that we ensure that referents
-- can be mapped to instances correctly.
local deferredRefs = {}
reifyInner(instanceMap, virtualInstances, rootId, parentInstance, unappliedPatch, deferredRefs)
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
return unappliedPatch
end
--[[ --[[
Add the given ID and all of its descendants in virtualInstances to the given Add the given ID and all of its descendants in virtualInstances to the given
PatchSet, marked for addition. PatchSet, marked for addition.
@@ -40,10 +20,21 @@ local function addAllToPatch(patchSet, virtualInstances, id)
end end
end end
function reifyInstance(deferredRefs, instanceMap, virtualInstances, rootId, parentInstance)
-- Create an empty patch that will be populated with any parts of this reify
-- that could not happen, like instances that couldn't be created and
-- properties that could not be assigned.
local unappliedPatch = PatchSet.newEmpty()
reifyInstanceInner(unappliedPatch, deferredRefs, instanceMap, virtualInstances, rootId, parentInstance)
return unappliedPatch
end
--[[ --[[
Inner function that defines the core routine. Inner function that defines the core routine.
]] ]]
function reifyInner(instanceMap, virtualInstances, id, parentInstance, unappliedPatch, deferredRefs) function reifyInstanceInner(unappliedPatch, deferredRefs, instanceMap, virtualInstances, id, parentInstance)
local virtualInstance = virtualInstances[id] local virtualInstance = virtualInstances[id]
if virtualInstance == nil then if virtualInstance == nil then
@@ -102,7 +93,7 @@ function reifyInner(instanceMap, virtualInstances, id, parentInstance, unapplied
end end
for _, childId in ipairs(virtualInstance.Children) do for _, childId in ipairs(virtualInstance.Children) do
reifyInner(instanceMap, virtualInstances, childId, instance, unappliedPatch, deferredRefs) reifyInstanceInner(unappliedPatch, deferredRefs, instanceMap, virtualInstances, childId, instance)
end end
instance.Parent = parentInstance instance.Parent = parentInstance
@@ -143,6 +134,7 @@ function applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
end end
local targetInstance = instanceMap.fromIds[refId] local targetInstance = instanceMap.fromIds[refId]
if targetInstance == nil then if targetInstance == nil then
markFailed(entry.id, entry.propertyName, entry.virtualValue) markFailed(entry.id, entry.propertyName, entry.virtualValue)
continue continue
@@ -155,4 +147,7 @@ function applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
end end
end end
return reify return {
reifyInstance = reifyInstance,
applyDeferredRefs = applyDeferredRefs,
}

View File

@@ -1,5 +1,6 @@
return function() return function()
local reify = require(script.Parent.reify) local reify = require(script.Parent.reify)
local reifyInstance, applyDeferredRefs = reify.reifyInstance, reify.applyDeferredRefs
local PatchSet = require(script.Parent.Parent.PatchSet) local PatchSet = require(script.Parent.Parent.PatchSet)
local InstanceMap = require(script.Parent.Parent.InstanceMap) local InstanceMap = require(script.Parent.Parent.InstanceMap)
@@ -20,7 +21,11 @@ return function()
it("should throw when given a bogus ID", function() it("should throw when given a bogus ID", function()
expect(function() expect(function()
reify(InstanceMap.new(), {}, "Hi, mom!", game) local deferredRefs = {}
local instanceMap = InstanceMap.new()
local unappliedPatch = reifyInstance(deferredRefs, instanceMap, {}, "Hi, mom!", game)
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
end).to.throw() end).to.throw()
end) end)
@@ -34,8 +39,11 @@ return function()
}, },
} }
local deferredRefs = {}
local instanceMap = InstanceMap.new() local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT", nil) local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT", nil)
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
assert(instanceMap:size() == 0, "expected instanceMap to be empty") assert(instanceMap:size() == 0, "expected instanceMap to be empty")
@@ -60,8 +68,11 @@ return function()
}, },
} }
local deferredRefs = {}
local instanceMap = InstanceMap.new() local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT") local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty") assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
@@ -90,8 +101,11 @@ return function()
}, },
} }
local deferredRefs = {}
local instanceMap = InstanceMap.new() local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT") local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty") assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
@@ -122,8 +136,11 @@ return function()
}, },
} }
local deferredRefs = {}
local instanceMap = InstanceMap.new() local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT") local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
expect(size(unappliedPatch.added)).to.equal(1) expect(size(unappliedPatch.added)).to.equal(1)
expect(unappliedPatch.added["CHILD"]).to.equal(virtualInstances["CHILD"]) expect(unappliedPatch.added["CHILD"]).to.equal(virtualInstances["CHILD"])
@@ -153,8 +170,11 @@ return function()
}, },
} }
local deferredRefs = {}
local instanceMap = InstanceMap.new() local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT") local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
local instance = instanceMap.fromIds["ROOT"] local instance = instanceMap.fromIds["ROOT"]
expect(instance.ClassName).to.equal("StringValue") expect(instance.ClassName).to.equal("StringValue")
@@ -196,8 +216,11 @@ return function()
}, },
} }
local deferredRefs = {}
local instanceMap = InstanceMap.new() local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT") local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty") assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
@@ -223,13 +246,16 @@ return function()
}, },
} }
local deferredRefs = {}
local instanceMap = InstanceMap.new() local instanceMap = InstanceMap.new()
local existing = Instance.new("Folder") local existing = Instance.new("Folder")
existing.Name = "Existing" existing.Name = "Existing"
instanceMap:insert("EXISTING", existing) instanceMap:insert("EXISTING", existing)
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT") local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty") assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
@@ -268,8 +294,11 @@ return function()
}, },
} }
local deferredRefs = {}
local instanceMap = InstanceMap.new() local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT") local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty") assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
@@ -307,8 +336,11 @@ return function()
}, },
} }
local deferredRefs = {}
local instanceMap = InstanceMap.new() local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT") local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty") assert(PatchSet.isEmpty(unappliedPatch), "expected remaining patch to be empty")
@@ -332,8 +364,11 @@ return function()
}, },
} }
local deferredRefs = {}
local instanceMap = InstanceMap.new() local instanceMap = InstanceMap.new()
local unappliedPatch = reify(instanceMap, virtualInstances, "ROOT") local unappliedPatch = reifyInstance(deferredRefs, instanceMap, virtualInstances, "ROOT")
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
assert(not PatchSet.hasRemoves(unappliedPatch), "expected no removes") assert(not PatchSet.hasRemoves(unappliedPatch), "expected no removes")
assert(not PatchSet.hasAdditions(unappliedPatch), "expected no additions") assert(not PatchSet.hasAdditions(unappliedPatch), "expected no additions")

View File

@@ -1,4 +0,0 @@
#!/bin/sh
rojo build test-place.project.json -o TestPlace.rbxlx
run-in-roblox --script run-tests.server.lua --place TestPlace.rbxlx

View File

@@ -5,7 +5,7 @@
"ReplicatedStorage": { "ReplicatedStorage": {
"Rojo": { "Rojo": {
"$path": "default.project.json" "$path": "../plugin.project.json"
}, },
"Packages": { "Packages": {

View File

@@ -1,2 +0,0 @@
# Continously build the rojo plugin into the local plugin directory on Windows
rojo build plugin/default.project.json -o $LOCALAPPDATA/Roblox/Plugins/Rojo.rbxm --watch

View File

@@ -6,6 +6,7 @@ expression: contents
<Item class="Model" referent="0"> <Item class="Model" referent="0">
<Properties> <Properties>
<string name="Name">init_meta_class_name</string> <string name="Name">init_meta_class_name</string>
<bool name="NeedsPivotMigration">false</bool>
</Properties> </Properties>
</Item> </Item>
</roblox> </roblox>

View File

@@ -1,7 +1,6 @@
--- ---
source: tests/tests/build.rs source: tests/tests/build.rs
expression: contents expression: contents
--- ---
<roblox version="4"> <roblox version="4">
<Item class="Folder" referent="0"> <Item class="Folder" referent="0">
@@ -25,6 +24,7 @@ expression: contents
<R21>0</R21> <R21>0</R21>
<R22>1</R22> <R22>1</R22>
</CoordinateFrame> </CoordinateFrame>
<bool name="NeedsPivotMigration">false</bool>
<Ref name="PrimaryPart">null</Ref> <Ref name="PrimaryPart">null</Ref>
<BinaryString name="Tags"></BinaryString> <BinaryString name="Tags"></BinaryString>
</Properties> </Properties>

View File

@@ -1,7 +1,6 @@
--- ---
source: tests/tests/build.rs source: tests/tests/build.rs
expression: contents expression: contents
--- ---
<roblox version="4"> <roblox version="4">
<Item class="DataModel" referent="0"> <Item class="DataModel" referent="0">
@@ -22,6 +21,7 @@ expression: contents
<Item class="Workspace" referent="2"> <Item class="Workspace" referent="2">
<Properties> <Properties>
<string name="Name">Workspace</string> <string name="Name">Workspace</string>
<bool name="NeedsPivotMigration">false</bool>
</Properties> </Properties>
<Item class="BoolValue" referent="3"> <Item class="BoolValue" referent="3">
<Properties> <Properties>

View File

@@ -1,7 +1,6 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
gameId: ~ gameId: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -1,7 +1,6 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
gameId: ~ gameId: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -1,7 +1,6 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
gameId: ~ gameId: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -1,7 +1,6 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
expression: "read_response.intern_and_redact(&mut redactions, root_id)" expression: "read_response.intern_and_redact(&mut redactions, root_id)"
--- ---
instances: instances:
id-2: id-2:
@@ -22,7 +21,8 @@ instances:
ignoreUnknownInstances: false ignoreUnknownInstances: false
Name: test Name: test
Parent: id-2 Parent: id-2
Properties: {} Properties:
NeedsPivotMigration:
Bool: false
messageCursor: 1 messageCursor: 1
sessionId: id-1 sessionId: id-1

View File

@@ -1,7 +1,6 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
gameId: ~ gameId: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -1,7 +1,6 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
expression: "subscribe_response.intern_and_redact(&mut redactions, ())" expression: "subscribe_response.intern_and_redact(&mut redactions, ())"
--- ---
messageCursor: 1 messageCursor: 1
messages: messages:
@@ -14,8 +13,9 @@ messages:
ignoreUnknownInstances: false ignoreUnknownInstances: false
Name: test Name: test
Parent: id-2 Parent: id-2
Properties: {} Properties:
NeedsPivotMigration:
Bool: false
removed: [] removed: []
updated: [] updated: []
sessionId: id-1 sessionId: id-1

View File

@@ -0,0 +1,64 @@
---
source: tests/tests/serve.rs
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
---
instances:
id-2:
Children:
- id-3
ClassName: DataModel
Id: id-2
Metadata:
ignoreUnknownInstances: true
Name: pivot_migration
Parent: "00000000000000000000000000000000"
Properties: {}
id-3:
Children:
- id-4
- id-5
- id-6
ClassName: Workspace
Id: id-3
Metadata:
ignoreUnknownInstances: true
Name: Workspace
Parent: id-2
Properties:
NeedsPivotMigration:
Bool: false
id-4:
Children: []
ClassName: Model
Id: id-4
Metadata:
ignoreUnknownInstances: true
Name: Model
Parent: id-3
Properties:
NeedsPivotMigration:
Bool: false
id-5:
Children: []
ClassName: Tool
Id: id-5
Metadata:
ignoreUnknownInstances: false
Name: Tool
Parent: id-3
Properties:
NeedsPivotMigration:
Bool: false
id-6:
Children: []
ClassName: Actor
Id: id-6
Metadata:
ignoreUnknownInstances: true
Name: Actor
Parent: id-3
Properties:
NeedsPivotMigration:
Bool: false
messageCursor: 1
sessionId: id-1

View File

@@ -0,0 +1,27 @@
---
source: tests/tests/serve.rs
expression: "subscribe_response.intern_and_redact(&mut redactions, ())"
---
messageCursor: 1
messages:
- added:
id-6:
Children: []
ClassName: Actor
Id: id-6
Metadata:
ignoreUnknownInstances: true
Name: Actor
Parent: id-3
Properties:
NeedsPivotMigration:
Bool: false
removed: []
updated:
- changedClassName: ~
changedMetadata:
ignoreUnknownInstances: true
changedName: ~
changedProperties: {}
id: id-3
sessionId: id-1

View File

@@ -1,7 +1,6 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
gameId: ~ gameId: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -1,6 +1,5 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
assertion_line: 316
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -1,6 +1,5 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
assertion_line: 335
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -1,6 +1,5 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
assertion_line: 351
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -0,0 +1,52 @@
---
source: tests/tests/serve.rs
expression: "read_response.intern_and_redact(&mut redactions, root_id)"
---
instances:
id-2:
Children:
- id-3
ClassName: DataModel
Id: id-2
Metadata:
ignoreUnknownInstances: true
Name: pivot_migration
Parent: "00000000000000000000000000000000"
Properties: {}
id-3:
Children:
- id-4
- id-5
ClassName: Workspace
Id: id-3
Metadata:
ignoreUnknownInstances: true
Name: Workspace
Parent: id-2
Properties:
NeedsPivotMigration:
Bool: false
id-4:
Children: []
ClassName: Model
Id: id-4
Metadata:
ignoreUnknownInstances: true
Name: Model
Parent: id-3
Properties:
NeedsPivotMigration:
Bool: false
id-5:
Children: []
ClassName: Tool
Id: id-5
Metadata:
ignoreUnknownInstances: false
Name: Tool
Parent: id-3
Properties:
NeedsPivotMigration:
Bool: false
messageCursor: 0
sessionId: id-1

View File

@@ -0,0 +1,13 @@
---
source: tests/tests/serve.rs
expression: redactions.redacted_yaml(info)
---
expectedPlaceIds: ~
gameId: ~
placeId: ~
projectName: pivot_migration
protocolVersion: 4
rootInstanceId: id-2
serverVersion: "[server-version]"
sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -31,6 +31,8 @@ instances:
Attributes: Attributes:
Rojo_Target_PrimaryPart: Rojo_Target_PrimaryPart:
String: project target String: project target
NeedsPivotMigration:
Bool: false
PrimaryPart: PrimaryPart:
Ref: id-9 Ref: id-9
id-2: id-2:
@@ -55,7 +57,9 @@ instances:
ignoreUnknownInstances: true ignoreUnknownInstances: true
Name: Workspace Name: Workspace
Parent: id-2 Parent: id-2
Properties: {} Properties:
NeedsPivotMigration:
Bool: false
id-4: id-4:
Children: [] Children: []
ClassName: ObjectValue ClassName: ObjectValue
@@ -124,6 +128,8 @@ instances:
Attributes: Attributes:
Rojo_Target_PrimaryPart: Rojo_Target_PrimaryPart:
String: model target 2 String: model target 2
NeedsPivotMigration:
Bool: false
PrimaryPart: PrimaryPart:
Ref: id-7 Ref: id-7
id-9: id-9:
@@ -138,4 +144,3 @@ instances:
Properties: {} Properties: {}
messageCursor: 1 messageCursor: 1
sessionId: id-1 sessionId: id-1

View File

@@ -40,7 +40,9 @@ instances:
ignoreUnknownInstances: true ignoreUnknownInstances: true
Name: Workspace Name: Workspace
Parent: id-2 Parent: id-2
Properties: {} Properties:
NeedsPivotMigration:
Bool: false
id-4: id-4:
Children: [] Children: []
ClassName: ObjectValue ClassName: ObjectValue
@@ -104,6 +106,8 @@ instances:
Attributes: Attributes:
Rojo_Target_PrimaryPart: Rojo_Target_PrimaryPart:
String: model target String: model target
NeedsPivotMigration:
Bool: false
PrimaryPart: PrimaryPart:
Ref: id-7 Ref: id-7
id-9: id-9:
@@ -118,4 +122,3 @@ instances:
Properties: {} Properties: {}
messageCursor: 0 messageCursor: 0
sessionId: id-1 sessionId: id-1

View File

@@ -10,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -40,7 +40,9 @@ instances:
ignoreUnknownInstances: true ignoreUnknownInstances: true
Name: Workspace Name: Workspace
Parent: id-2 Parent: id-2
Properties: {} Properties:
NeedsPivotMigration:
Bool: false
id-4: id-4:
Children: [] Children: []
ClassName: ObjectValue ClassName: ObjectValue
@@ -104,8 +106,12 @@ instances:
Attributes: Attributes:
Rojo_Target_PrimaryPart: Rojo_Target_PrimaryPart:
String: model target String: model target
NeedsPivotMigration:
Bool: false
PrimaryPart: PrimaryPart:
Ref: id-7 Ref: id-7
Scale:
Float32: 1
id-9: id-9:
Children: Children:
- id-10 - id-10
@@ -116,5 +122,5 @@ instances:
Name: ProjectTarget Name: ProjectTarget
Parent: id-3 Parent: id-3
Properties: {} Properties: {}
messageCursor: 0 messageCursor: 1
sessionId: id-1 sessionId: id-1

View File

@@ -40,7 +40,9 @@ instances:
ignoreUnknownInstances: true ignoreUnknownInstances: true
Name: Workspace Name: Workspace
Parent: id-2 Parent: id-2
Properties: {} Properties:
NeedsPivotMigration:
Bool: false
id-4: id-4:
Children: [] Children: []
ClassName: ObjectValue ClassName: ObjectValue
@@ -104,6 +106,8 @@ instances:
Attributes: Attributes:
Rojo_Target_PrimaryPart: Rojo_Target_PrimaryPart:
String: model target String: model target
NeedsPivotMigration:
Bool: false
PrimaryPart: PrimaryPart:
Ref: id-7 Ref: id-7
id-9: id-9:

View File

@@ -10,3 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -0,0 +1,17 @@
---
source: tests/tests/serve.rs
expression: "subscribe_response.intern_and_redact(&mut redactions, ())"
---
messageCursor: 1
messages:
- added: {}
removed: []
updated:
- changedClassName: ~
changedMetadata: ~
changedName: ~
changedProperties:
Scale:
Float32: 1
id: id-8
sessionId: id-1

View File

@@ -10,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -18,6 +18,8 @@ messages:
Attributes: Attributes:
Rojo_Target_PrimaryPart: Rojo_Target_PrimaryPart:
String: project target String: project target
NeedsPivotMigration:
Bool: false
PrimaryPart: PrimaryPart:
Ref: id-9 Ref: id-9
removed: [] removed: []
@@ -43,4 +45,3 @@ messages:
PrimaryPart: ~ PrimaryPart: ~
id: id-8 id: id-8
sessionId: id-1 sessionId: id-1

View File

@@ -1,7 +1,6 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
gameId: ~ gameId: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -1,7 +1,6 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
gameId: ~ gameId: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -1,6 +1,5 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
assertion_line: 265
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -1,6 +1,5 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
assertion_line: 281
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -1,6 +1,5 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
assertion_line: 297
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -1,6 +1,5 @@
--- ---
source: tests/tests/serve.rs source: tests/tests/serve.rs
assertion_line: 383
expression: redactions.redacted_yaml(info) expression: redactions.redacted_yaml(info)
--- ---
expectedPlaceIds: ~ expectedPlaceIds: ~
@@ -11,3 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2 rootInstanceId: id-2
serverVersion: "[server-version]" serverVersion: "[server-version]"
sessionId: id-1 sessionId: id-1
unexpectedPlaceIds: ~

View File

@@ -0,0 +1,3 @@
{
"className": "Tool"
}

View File

@@ -0,0 +1,14 @@
{
"name": "pivot_migration",
"tree": {
"$className": "DataModel",
"Workspace": {
"Model": {
"$className": "Model"
},
"Tool": {
"$path": "Tool.model.json"
}
}
}
}

View File

@@ -0,0 +1,2 @@
rojo build plugin/test-place.project.json -o TestPlace.rbxl
run-in-roblox --script plugin/run-tests.server.lua --place TestPlace.rbxl

View File

@@ -0,0 +1 @@
rojo build plugin.project.json --plugin Rojo.rbxm --watch

View File

@@ -136,7 +136,7 @@ impl JobThreadContext {
// created all at once. // created all at once.
let mut current_path = path.as_path(); let mut current_path = path.as_path();
let affected_ids = loop { let affected_ids = loop {
let ids = tree.get_ids_at_path(&current_path); let ids = tree.get_ids_at_path(current_path);
log::trace!("Path {} affects IDs {:?}", current_path.display(), ids); log::trace!("Path {} affects IDs {:?}", current_path.display(), ids);

View File

@@ -23,7 +23,7 @@ const UNKNOWN_PLUGIN_KIND_ERR: &str = "Could not detect what kind of file to bui
/// Generates a model or place file from the Rojo project. /// Generates a model or place file from the Rojo project.
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
pub struct BuildCommand { pub struct BuildCommand {
/// Path to the project to serve. Defaults to the current directory. /// Path to the project to build. Defaults to the current directory.
#[clap(default_value = "")] #[clap(default_value = "")]
pub project: PathBuf, pub project: PathBuf,

View File

@@ -8,7 +8,7 @@ use clap::Parser;
use fs_err::File; use fs_err::File;
use memofs::Vfs; use memofs::Vfs;
use rayon::prelude::*; use rayon::prelude::*;
use rbx_dom_weak::types::Ref; use rbx_dom_weak::{types::Ref, Ustr};
use serde::Serialize; use serde::Serialize;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
@@ -26,7 +26,7 @@ const PATH_STRIP_FAILED_ERR: &str = "Failed to create relative paths for project
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct SourcemapNode<'a> { struct SourcemapNode<'a> {
name: &'a str, name: &'a str,
class_name: &'a str, class_name: Ustr,
#[serde(skip_serializing_if = "Vec::is_empty")] #[serde(skip_serializing_if = "Vec::is_empty")]
file_paths: Vec<PathBuf>, file_paths: Vec<PathBuf>,
@@ -67,7 +67,7 @@ impl SourcemapCommand {
let vfs = Vfs::new_default(); let vfs = Vfs::new_default();
vfs.set_watch_enabled(self.watch); vfs.set_watch_enabled(self.watch);
let session = ServeSession::new(vfs, &project_path)?; let session = ServeSession::new(vfs, project_path)?;
let mut cursor = session.message_queue().cursor(); let mut cursor = session.message_queue().cursor();
let filter = if self.include_non_scripts { let filter = if self.include_non_scripts {
@@ -113,7 +113,7 @@ fn filter_nothing(_instance: &InstanceWithMeta) -> bool {
fn filter_non_scripts(instance: &InstanceWithMeta) -> bool { fn filter_non_scripts(instance: &InstanceWithMeta) -> bool {
matches!( matches!(
instance.class_name(), instance.class_name().as_str(),
"Script" | "LocalScript" | "ModuleScript" "Script" | "LocalScript" | "ModuleScript"
) )
} }

View File

@@ -22,6 +22,7 @@ trait FmtLua {
} }
} }
#[allow(dead_code)]
struct DisplayLua<T>(T); struct DisplayLua<T>(T);
impl<T> fmt::Display for DisplayLua<T> impl<T> fmt::Display for DisplayLua<T>

Some files were not shown because too many files have changed in this diff Show More