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
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Changelog check
uses: Zomzog/changelog-checker@v1.3.0
with:
fileName: CHANGELOG.md
fileName: CHANGELOG.md
noChangelogLabel: skip changelog
checkNotification: Simple
env:

View File

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

View File

@@ -26,14 +26,12 @@ jobs:
submodules: true
- name: Setup Aftman
uses: ok-nick/setup-aftman@v0.1.0
uses: ok-nick/setup-aftman@v0.4.2
with:
token: ${{ secrets.GITHUB_TOKEN }}
trust-check: false
version: 'v0.2.6'
version: 'v0.3.0'
- name: Build Plugin
run: rojo build plugin --output Rojo.rbxm
run: rojo build plugin.project.json --output Rojo.rbxm
- name: Upload Plugin to Release
env:
@@ -55,7 +53,7 @@ jobs:
# https://doc.rust-lang.org/rustc/platform-support.html
include:
- host: linux
os: ubuntu-20.04
os: ubuntu-latest
target: x86_64-unknown-linux-gnu
label: linux-x86_64
@@ -89,11 +87,9 @@ jobs:
targets: ${{ matrix.target }}
- name: Setup Aftman
uses: ok-nick/setup-aftman@v0.1.0
uses: ok-nick/setup-aftman@v0.4.2
with:
token: ${{ secrets.GITHUB_TOKEN }}
trust-check: false
version: 'v0.2.6'
version: 'v0.3.0'
- name: Build Release
run: cargo build --release --locked --verbose --target ${{ matrix.target }}

8
.gitignore vendored
View File

@@ -10,8 +10,8 @@
/*.rbxl
/*.rbxlx
# Test places for the Roblox Studio Plugin
/plugin/*.rbxlx
# Sourcemap for the Rojo plugin (for better intellisense)
/sourcemap.json
# Roblox Studio holds 'lock' files on places
*.rbxl.lock
@@ -19,3 +19,7 @@
# Snapshot files from the 'insta' Rust crate
**/*.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
## 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])
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
@@ -28,6 +36,8 @@
* Added experimental setting for Auto Connect in playtests ([#840])
* Improved settings UI ([#886])
* `Open Scripts Externally` option can now be changed while syncing ([#911])
* The sync reminder notification will now tell you what was last synced and when ([#987])
* Fixed notification and tooltip text sometimes getting cut off ([#988])
* Projects may now specify rules for syncing files as if they had a different file extension. ([#813])
This is specified via a new field on project files, `syncRules`:
@@ -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.
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 |
|:---------------|:----------------|
@@ -73,6 +83,16 @@
| `project` | `.project.json` |
| `ignore` | None! |
Additionally, there are `use` values for specific script types ([#909]):
| `use` value | script type |
|:-------------------------|:---------------------------------------|
| `legacyServerScript` | `Script` with `Enum.RunContext.Legacy` |
| `legacyClientScript` | `LocalScript` |
| `runContextServerScript` | `Script` with `Enum.RunContext.Server` |
| `runContextClientScript` | `Script` with `Enum.RunContext.Client` |
| `pluginScript` | `Script` with `Enum.RunContext.Plugin` |
**All** sync rules are reset between project files, so they must be specified in each one when nesting them. This is to ensure that nothing can break other projects by changing how files are synced!
[#813]: https://github.com/rojo-rbx/rojo/pull/813
@@ -83,8 +103,19 @@
[#843]: https://github.com/rojo-rbx/rojo/pull/843
[#883]: https://github.com/rojo-rbx/rojo/pull/883
[#886]: https://github.com/rojo-rbx/rojo/pull/886
[#909]: https://github.com/rojo-rbx/rojo/pull/909
[#911]: https://github.com/rojo-rbx/rojo/pull/911
[#915]: https://github.com/rojo-rbx/rojo/pull/915
[#974]: https://github.com/rojo-rbx/rojo/pull/974
[#987]: https://github.com/rojo-rbx/rojo/pull/987
[#988]: https://github.com/rojo-rbx/rojo/pull/988
[#1008]: https://github.com/rojo-rbx/rojo/pull/1008
[#1021]: https://github.com/rojo-rbx/rojo/pull/1021
[#1027]: https://github.com/rojo-rbx/rojo/pull/1027
## [7.4.4] - August 22nd, 2024
* Fixed issue with reading attributes from `Lighting` in new place files
* `Instance.Archivable` will now default to `true` when building a project into a binary (`rbxm`/`rbxl`) file rather than `false`.
## [7.4.3] - August 6th, 2024
* Fixed issue with building binary files introduced in 7.4.2

View File

@@ -16,6 +16,23 @@ You'll want these tools to work on Rojo:
* Latest stable Rust compiler
* Latest stable [Rojo](https://github.com/rojo-rbx/rojo)
* [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 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"
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]]
name = "aho-corasick"
version = "1.1.2"
@@ -171,6 +184,10 @@ name = "cc"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723"
dependencies = [
"jobserver",
"libc",
]
[[package]]
name = "cfg-if"
@@ -492,7 +509,7 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall",
"redox_syscall 0.4.1",
"windows-sys 0.52.0",
]
@@ -935,6 +952,15 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "jobserver"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
dependencies = [
"libc",
]
[[package]]
name = "jod-thread"
version = "0.1.2"
@@ -962,9 +988,9 @@ dependencies = [
[[package]]
name = "lazy_static"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lazycell"
@@ -986,7 +1012,7 @@ checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
dependencies = [
"bitflags 2.4.2",
"libc",
"redox_syscall",
"redox_syscall 0.4.1",
]
[[package]]
@@ -1001,6 +1027,16 @@ version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "log"
version = "0.4.21"
@@ -1241,6 +1277,29 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "paste"
version = "1.0.14"
@@ -1310,6 +1369,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "plotters"
version = "0.3.5"
@@ -1498,10 +1563,11 @@ dependencies = [
[[package]]
name = "rbx_binary"
version = "0.7.7"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b85057e8ff75a1ce99248200c4b3c7b481a3d52f921f1053ecd67921dcc7930"
checksum = "9573fee5e073d7b303f475c285197fdc8179468de66ca60ee115a58fbac99296"
dependencies = [
"ahash",
"log",
"lz4",
"profiling",
@@ -1509,23 +1575,26 @@ dependencies = [
"rbx_reflection",
"rbx_reflection_database",
"thiserror",
"zstd",
]
[[package]]
name = "rbx_dom_weak"
version = "2.9.0"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcd2a17d09e46af0805f8b311a926402172b97e8d9388745c9adf8f448901841"
checksum = "04425cf6e9376e5486f4fb35906c120d1b1b45618a490318cf563fab1fa230a9"
dependencies = [
"ahash",
"rbx_types",
"serde",
"ustr",
]
[[package]]
name = "rbx_reflection"
version = "4.7.0"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8118ac6021d700e8debe324af6b40ecfd2cef270a00247849dbdfeebb0802677"
checksum = "1b6d0d62baa613556b058a5f94a53b01cf0ccde0ea327ce03056e335b982e77e"
dependencies = [
"rbx_types",
"serde",
@@ -1534,9 +1603,9 @@ dependencies = [
[[package]]
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"
checksum = "0e29381d675420e841f8c02db5755cbb2545ed3e13f56c539546dc58702b512a"
checksum = "5349b19e5e94fbcaba7a52175263ab64011e0a13f17ff57729f2f560ccdec615"
dependencies = [
"lazy_static",
"rbx_reflection",
@@ -1546,9 +1615,9 @@ dependencies = [
[[package]]
name = "rbx_types"
version = "1.10.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e30f49b2a3bb667e4074ba73c2dfb8ca0873f610b448ccf318a240acfdec6c73"
checksum = "78e4fdde46493def107e5f923d82e813dec9b3eef52c2f75fbad3a716023eda2"
dependencies = [
"base64 0.13.1",
"bitflags 1.3.2",
@@ -1561,10 +1630,11 @@ dependencies = [
[[package]]
name = "rbx_xml"
version = "0.13.5"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b14b3027bc9ccd82e2fc854c8bcd25ed58318e570c355bf2cf63df9cdbd5ba8"
checksum = "bb623833c31cc43bbdaeb32f5e91db8ecd63fc46e438d0d268baf9e61539cf1c"
dependencies = [
"ahash",
"base64 0.13.1",
"log",
"rbx_dom_weak",
@@ -1582,6 +1652,15 @@ dependencies = [
"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]]
name = "redox_users"
version = "0.4.4"
@@ -1752,7 +1831,7 @@ dependencies = [
[[package]]
name = "rojo"
version = "7.4.0"
version = "7.5.0"
dependencies = [
"anyhow",
"backtrace",
@@ -1896,6 +1975,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
version = "0.7.1"
@@ -2393,6 +2478,19 @@ dependencies = [
"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]]
name = "uuid"
version = "1.7.0"
@@ -2784,3 +2882,51 @@ name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]
name = "rojo"
version = "7.4.0"
version = "7.5.0"
rust-version = "1.70.0"
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
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_xml = { path = "../rbx-dom/rbx_xml" }
rbx_binary = "0.7.7"
rbx_dom_weak = "2.9.0"
rbx_reflection = "4.7.0"
rbx_reflection_database = "0.2.12"
rbx_xml = "0.13.5"
rbx_binary = "1.0.0"
rbx_dom_weak = "3.0.0"
rbx_reflection = "5.0.0"
rbx_reflection_database = "1.0.2"
rbx_xml = "1.0.0"
anyhow = "1.0.80"
backtrace = "0.3.69"
@@ -70,7 +70,6 @@ humantime = "2.1.0"
hyper = { version = "0.14.28", features = ["server", "tcp", "http1"] }
jod-thread = "0.1.2"
log = "0.4.21"
maplit = "1.0.2"
num_cpus = "1.16.0"
opener = "0.5.2"
rayon = "1.9.0"

View File

@@ -1,5 +1,5 @@
[tools]
rojo = "rojo-rbx/rojo@7.3.0"
selene = "Kampfkarren/selene@0.26.1"
stylua = "JohnnyMorganz/stylua@0.18.2"
rojo = "rojo-rbx/rojo@7.4.1"
selene = "Kampfkarren/selene@0.27.1"
stylua = "JohnnyMorganz/stylua@0.20.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();
if file_name.starts_with(".git") {
continue;
}
// We can skip any TestEZ test files since they aren't necessary for
// the plugin to run.
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> {
let out_dir = env::var_os("OUT_DIR").unwrap();
let root_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
let plugin_root = PathBuf::from(root_dir).join("plugin");
let root_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
let plugin_dir = root_dir.join("plugin");
let our_version = Version::parse(env::var_os("CARGO_PKG_VERSION").unwrap().to_str().unwrap())?;
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!(
our_version, plugin_version,
@@ -54,14 +58,16 @@ fn main() -> Result<(), anyhow::Error> {
);
let snapshot = VfsSnapshot::dir(hashmap! {
"default.project.json" => snapshot_from_fs_path(&plugin_root.join("default.project.json"))?,
"fmt" => snapshot_from_fs_path(&plugin_root.join("fmt"))?,
"http" => snapshot_from_fs_path(&plugin_root.join("http"))?,
"log" => snapshot_from_fs_path(&plugin_root.join("log"))?,
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_root.join("rbx_dom_lua"))?,
"src" => snapshot_from_fs_path(&plugin_root.join("src"))?,
"Packages" => snapshot_from_fs_path(&plugin_root.join("Packages"))?,
"Version.txt" => snapshot_from_fs_path(&plugin_root.join("Version.txt"))?,
"default.project.json" => snapshot_from_fs_path(&root_dir.join("plugin.project.json"))?,
"plugin" => VfsSnapshot::dir(hashmap! {
"fmt" => snapshot_from_fs_path(&plugin_dir.join("fmt"))?,
"http" => snapshot_from_fs_path(&plugin_dir.join("http"))?,
"log" => snapshot_from_fs_path(&plugin_dir.join("log"))?,
"rbx_dom_lua" => snapshot_from_fs_path(&plugin_dir.join("rbx_dom_lua"))?,
"src" => snapshot_from_fs_path(&plugin_dir.join("src"))?,
"Packages" => snapshot_from_fs_path(&plugin_dir.join("Packages"))?,
"Version.txt" => snapshot_from_fs_path(&plugin_dir.join("Version.txt"))?,
}),
});
let out_path = Path::new(&out_dir).join("plugin.bincode");

View File

@@ -5,19 +5,13 @@ use serde::Serialize;
/// Enables redacting any value that serializes as a string.
///
/// Used for transforming Rojo instance IDs into something deterministic.
#[derive(Default)]
pub struct RedactionMap {
ids: HashMap<String, usize>,
last_id: usize,
}
impl RedactionMap {
pub fn new() -> Self {
Self {
ids: HashMap::new(),
last_id: 0,
}
}
pub fn get_redacted_value(&self, id: impl ToString) -> Option<String> {
let id = id.to_string();

View File

@@ -3,25 +3,25 @@
"tree": {
"$className": "Folder",
"Plugin": {
"$path": "src"
"$path": "plugin/src"
},
"Packages": {
"$path": "Packages",
"$path": "plugin/Packages",
"Log": {
"$path": "log"
"$path": "plugin/log"
},
"Http": {
"$path": "http"
"$path": "plugin/http"
},
"Fmt": {
"$path": "fmt"
"$path": "plugin/fmt"
},
"RbxDom": {
"$path": "rbx_dom_lua"
"$path": "plugin/rbx_dom_lua"
}
},
"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
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
-- Rust's 'Debug' trait.
@@ -242,4 +242,4 @@ return {
debugOutputBuffer = debugOutputBuffer,
fmt = fmt,
debugify = debugify,
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -188,6 +188,38 @@ types = {
},
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,
toPod = identity,
},
@@ -205,6 +237,19 @@ types = {
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 = {
fromPod = function(pod)
local faces = {}
@@ -300,7 +345,12 @@ types = {
local keypoints = {}
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
return NumberSequence.new(keypoints)

View File

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

View File

@@ -15,6 +15,12 @@
0.0
]
},
"TestEnumItem": {
"EnumItem": {
"type": "Material",
"value": 256
}
},
"TestNumber": {
"Float64": 1337.0
},
@@ -170,9 +176,23 @@
},
"ty": "ColorSequence"
},
"Content": {
"ContentId": {
"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"
},
@@ -182,6 +202,15 @@
},
"ty": "Enum"
},
"EnumItem": {
"value": {
"EnumItem": {
"type": "Material",
"value": 256
}
},
"ty": "EnumItem"
},
"Faces": {
"value": {
"Faces": [

View File

@@ -1,6 +1,8 @@
local CollectionService = game:GetService("CollectionService")
local ScriptEditorService = game:GetService("ScriptEditorService")
local Error = require(script.Parent.Error)
--- A list of `Enum.Material` values that are used for Terrain.MaterialColors
local TERRAIN_MATERIAL_COLORS = {
Enum.Material.Grass,
@@ -51,6 +53,10 @@ return {
return true, instance:GetAttributes()
end,
write = function(instance, _, value)
if typeof(value) ~= "table" then
return false, Error.new(Error.Kind.CannotParseBinaryString)
end
local existing = instance:GetAttributes()
local didAllWritesSucceed = true
@@ -160,9 +166,14 @@ return {
return true, colors
end,
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
instance:SetMaterialColor(material, color)
end
return true
end,
},

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TestEZ = require(ReplicatedStorage.Packages.TestEZ)
local TestEZ = require(ReplicatedStorage.Packages:WaitForChild("TestEZ", 10))
local Rojo = ReplicatedStorage.Rojo

View File

@@ -45,14 +45,7 @@ end
local function rejectWrongPlaceId(infoResponseBody)
if infoResponseBody.expectedPlaceIds ~= nil then
local foundId = false
for _, id in ipairs(infoResponseBody.expectedPlaceIds) do
if id == game.PlaceId then
foundId = true
break
end
end
local foundId = table.find(infoResponseBody.expectedPlaceIds, game.PlaceId)
if not foundId then
local idList = {}
@@ -62,10 +55,30 @@ local function rejectWrongPlaceId(infoResponseBody)
local message = (
"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\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)
end

View File

@@ -32,7 +32,7 @@ end
function Checkbox:render()
return Theme.with(function(theme)
theme = theme.Checkbox
local checkboxTheme = theme.Checkbox
local activeTransparency = Roact.joinBindings({
self.binding:map(function(value)
@@ -57,20 +57,21 @@ function Checkbox:render()
end,
}, {
StateTip = e(Tooltip.Trigger, {
text = (if self.props.locked then "[LOCKED] " else "")
.. (if self.props.active then "Enabled" else "Disabled"),
text = (if self.props.locked
then (self.props.lockedTooltip or "(Cannot be changed right now)") .. "\n"
else "") .. (if self.props.active then "Enabled" else "Disabled"),
}),
Active = e(SlicedImage, {
slice = Assets.Slices.RoundedBackground,
color = theme.Active.BackgroundColor,
color = checkboxTheme.Active.BackgroundColor,
transparency = activeTransparency,
size = UDim2.new(1, 0, 1, 0),
zIndex = 2,
}, {
Icon = e("ImageLabel", {
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,
Size = UDim2.new(0, 16, 0, 16),
@@ -83,7 +84,7 @@ function Checkbox:render()
Inactive = e(SlicedImage, {
slice = Assets.Slices.RoundedBorder,
color = theme.Inactive.BorderColor,
color = checkboxTheme.Inactive.BorderColor,
transparency = self.props.transparency,
size = UDim2.new(1, 0, 1, 0),
}, {
@@ -91,7 +92,7 @@ function Checkbox:render()
Image = if self.props.locked
then Assets.Images.Checkbox.Locked
else Assets.Images.Checkbox.Inactive,
ImageColor3 = theme.Inactive.IconColor,
ImageColor3 = checkboxTheme.Inactive.IconColor,
ImageTransparency = self.props.transparency,
Size = UDim2.new(0, 16, 0, 16),

View File

@@ -1,4 +1,5 @@
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
local Roact = require(Packages.Roact)
@@ -7,6 +8,8 @@ Highlighter.matchStudioSettings()
local e = Roact.createElement
local Theme = require(Plugin.App.Theme)
local CodeLabel = Roact.PureComponent:extend("CodeLabel")
function CodeLabel:init()
@@ -40,22 +43,24 @@ function CodeLabel:updateHighlights()
end
function CodeLabel:render()
return e("TextLabel", {
Size = self.props.size,
Position = self.props.position,
Text = self.props.text,
BackgroundTransparency = 1,
Font = Enum.Font.RobotoMono,
TextSize = 16,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top,
TextColor3 = Color3.fromRGB(255, 255, 255),
[Roact.Ref] = self.labelRef,
}, {
SyntaxHighlights = e("Folder", {
[Roact.Ref] = self.highlightsRef,
}),
})
return Theme.with(function(theme)
return e("TextLabel", {
Size = self.props.size,
Position = self.props.position,
Text = self.props.text,
BackgroundTransparency = 1,
FontFace = theme.Font.Code,
TextSize = theme.TextSize.Code,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top,
TextColor3 = Color3.fromRGB(255, 255, 255),
[Roact.Ref] = self.labelRef,
}, {
SyntaxHighlights = e("Folder", {
[Roact.Ref] = self.highlightsRef,
}),
})
end)
end
return CodeLabel

View File

@@ -1,5 +1,3 @@
local TextService = game:GetService("TextService")
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
@@ -10,9 +8,11 @@ local Flipper = require(Packages.Flipper)
local Assets = require(Plugin.Assets)
local Theme = require(Plugin.App.Theme)
local bindingUtil = require(Plugin.App.bindingUtil)
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
local SlicedImage = require(script.Parent.SlicedImage)
local ScrollingFrame = require(script.Parent.ScrollingFrame)
local Tooltip = require(script.Parent.Tooltip)
local e = Roact.createElement
@@ -44,29 +44,29 @@ end
function Dropdown:render()
return Theme.with(function(theme)
theme = theme.Dropdown
local dropdownTheme = theme.Dropdown
local optionButtons = {}
local width = -1
for i, option in self.props.options do
local text = tostring(option or "")
local textSize = TextService:GetTextSize(text, 15, Enum.Font.GothamMedium, Vector2.new(math.huge, 20))
if textSize.X > width then
width = textSize.X
local textBounds = getTextBoundsAsync(text, theme.Font.Main, theme.TextSize.Body, math.huge)
if textBounds.X > width then
width = textBounds.X
end
optionButtons[text] = e("TextButton", {
Text = text,
LayoutOrder = i,
Size = UDim2.new(1, 0, 0, 24),
BackgroundColor3 = theme.BackgroundColor,
BackgroundColor3 = dropdownTheme.BackgroundColor,
TextTransparency = self.props.transparency,
BackgroundTransparency = self.props.transparency,
BorderSizePixel = 0,
TextColor3 = theme.TextColor,
TextColor3 = dropdownTheme.TextColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextSize = 15,
Font = Enum.Font.GothamMedium,
TextSize = theme.TextSize.Body,
FontFace = theme.Font.Main,
[Roact.Event.Activated] = function()
if self.props.locked then
@@ -103,13 +103,13 @@ function Dropdown:render()
}, {
Border = e(SlicedImage, {
slice = Assets.Slices.RoundedBorder,
color = theme.BorderColor,
color = dropdownTheme.BorderColor,
transparency = self.props.transparency,
size = UDim2.new(1, 0, 1, 0),
}, {
DropArrow = e("ImageLabel", {
Image = if self.props.locked then Assets.Images.Dropdown.Locked else Assets.Images.Dropdown.Arrow,
ImageColor3 = theme.IconColor,
ImageColor3 = dropdownTheme.IconColor,
ImageTransparency = self.props.transparency,
Size = UDim2.new(0, 18, 0, 18),
@@ -120,15 +120,21 @@ function Dropdown:render()
end),
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", {
Size = UDim2.new(1, -30, 1, 0),
Position = UDim2.new(0, 6, 0, 0),
BackgroundTransparency = 1,
Text = self.props.active,
Font = Enum.Font.GothamMedium,
TextSize = 15,
TextColor3 = theme.TextColor,
FontFace = theme.Font.Main,
TextSize = theme.TextSize.Body,
TextColor3 = dropdownTheme.TextColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency,
}),
@@ -136,7 +142,7 @@ function Dropdown:render()
Options = if self.state.open
then e(SlicedImage, {
slice = Assets.Slices.RoundedBackground,
color = theme.BackgroundColor,
color = dropdownTheme.BackgroundColor,
position = UDim2.new(1, 0, 1, 3),
size = self.openBinding:map(function(a)
return UDim2.new(1, 0, a * math.min(3, #self.props.options), 0)
@@ -145,7 +151,7 @@ function Dropdown:render()
}, {
Border = e(SlicedImage, {
slice = Assets.Slices.RoundedBorder,
color = theme.BorderColor,
color = dropdownTheme.BorderColor,
transparency = self.props.transparency,
size = UDim2.new(1, 0, 1, 0),
}),

View File

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

View File

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

View File

@@ -32,8 +32,8 @@ local function DisplayValue(props)
Label = e("TextLabel", {
Text = string.format("%d, %d, %d", props.value.R * 255, props.value.G * 255, props.value.B * 255),
BackgroundTransparency = 1,
Font = Enum.Font.GothamMedium,
TextSize = 14,
FontFace = theme.Font.Main,
TextSize = theme.TextSize.Body,
TextColor3 = props.textColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency,
@@ -90,8 +90,8 @@ local function DisplayValue(props)
return e("TextLabel", {
Text = textRepresentation,
BackgroundTransparency = 1,
Font = Enum.Font.GothamMedium,
TextSize = 14,
FontFace = theme.Font.Main,
TextSize = theme.TextSize.Body,
TextColor3 = props.textColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency,
@@ -112,8 +112,8 @@ local function DisplayValue(props)
return e("TextLabel", {
Text = textRepresentation,
BackgroundTransparency = 1,
Font = Enum.Font.GothamMedium,
TextSize = 14,
FontFace = theme.Font.Main,
TextSize = theme.TextSize.Body,
TextColor3 = props.textColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency,

View File

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

View File

@@ -124,8 +124,8 @@ function PatchVisualizer:render()
CleanMerge = e("TextLabel", {
Visible = #scrollElements == 0,
Text = "No changes to sync, project is up to date.",
Font = Enum.Font.GothamMedium,
TextSize = 15,
FontFace = theme.Font.Main,
TextSize = theme.TextSize.Medium,
TextColor3 = theme.TextColor,
TextWrapped = true,
Size = UDim2.new(1, 0, 1, 0),

View File

@@ -1,5 +1,3 @@
local TextService = game:GetService("TextService")
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
@@ -11,6 +9,7 @@ local StringDiff = require(script:FindFirstChild("StringDiff"))
local Timer = require(Plugin.Timer)
local Theme = require(Plugin.App.Theme)
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
local CodeLabel = require(Plugin.App.Components.CodeLabel)
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
@@ -32,7 +31,6 @@ function StringDiffVisualizer:init()
end)
end)
self:calculateContentSize()
self:updateScriptBackground()
self:setState({
@@ -54,7 +52,6 @@ end
function StringDiffVisualizer:didUpdate(previousProps)
if previousProps.oldString ~= self.props.oldString or previousProps.newString ~= self.props.newString then
self:calculateContentSize()
local add, remove = self:calculateDiffLines()
self:setState({
add = add,
@@ -63,11 +60,11 @@ function StringDiffVisualizer:didUpdate(previousProps)
end
end
function StringDiffVisualizer:calculateContentSize()
function StringDiffVisualizer:calculateContentSize(theme)
local oldString, newString = self.props.oldString, self.props.newString
local oldStringBounds = TextService:GetTextSize(oldString, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999))
local newStringBounds = TextService:GetTextSize(newString, 16, Enum.Font.RobotoMono, Vector2.new(99999, 99999))
local oldStringBounds = getTextBoundsAsync(oldString, theme.Font.Code, theme.TextSize.Code, math.huge)
local newStringBounds = getTextBoundsAsync(newString, theme.Font.Code, theme.TextSize.Code, math.huge)
self.setContentSize(
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
return Theme.with(function(theme)
self:calculateContentSize(theme)
return e(BorderedContainer, {
size = self.props.size,
position = self.props.position,

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,3 @@
local TextService = game:GetService("TextService")
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
@@ -10,6 +8,7 @@ local Flipper = require(Packages.Flipper)
local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets)
local bindingUtil = require(Plugin.App.bindingUtil)
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
local SlicedImage = require(script.Parent.SlicedImage)
local TouchRipple = require(script.Parent.TouchRipple)
@@ -41,18 +40,17 @@ end
function TextButton:render()
return Theme.with(function(theme)
local textSize =
TextService:GetTextSize(self.props.text, 18, Enum.Font.GothamMedium, Vector2.new(math.huge, math.huge))
local textBounds = getTextBoundsAsync(self.props.text, theme.Font.Main, theme.TextSize.Large, math.huge)
local style = self.props.style
theme = theme.Button[style]
local buttonTheme = theme.Button[style]
local bindingHover = bindingUtil.deriveProperty(self.binding, "hover")
local bindingEnabled = bindingUtil.deriveProperty(self.binding, "enabled")
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,
AnchorPoint = self.props.anchorPoint,
@@ -74,18 +72,22 @@ function TextButton:render()
end,
}, {
TouchRipple = e(TouchRipple, {
color = theme.ActionFillColor,
color = buttonTheme.ActionFillColor,
transparency = self.props.transparency:map(function(value)
return bindingUtil.blendAlpha({ theme.ActionFillTransparency, value })
return bindingUtil.blendAlpha({ buttonTheme.ActionFillTransparency, value })
end),
zIndex = 2,
}),
Text = e("TextLabel", {
Text = self.props.text,
Font = Enum.Font.GothamMedium,
TextSize = 18,
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.TextColor, theme.Disabled.TextColor),
FontFace = theme.Font.Main,
TextSize = theme.TextSize.Large,
TextColor3 = bindingUtil.mapLerp(
bindingEnabled,
buttonTheme.Enabled.TextColor,
buttonTheme.Disabled.TextColor
),
TextTransparency = self.props.transparency,
Size = UDim2.new(1, 0, 1, 0),
@@ -95,7 +97,11 @@ function TextButton:render()
Border = style == "Bordered" and e(SlicedImage, {
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,
size = UDim2.new(1, 0, 1, 0),
@@ -105,14 +111,18 @@ function TextButton:render()
HoverOverlay = e(SlicedImage, {
slice = Assets.Slices.RoundedBackground,
color = theme.ActionFillColor,
color = buttonTheme.ActionFillColor,
transparency = Roact.joinBindings({
hover = bindingHover:map(function(value)
return 1 - value
end),
transparency = self.props.transparency,
}):map(function(values)
return bindingUtil.blendAlpha({ theme.ActionFillTransparency, values.hover, values.transparency })
return bindingUtil.blendAlpha({
buttonTheme.ActionFillTransparency,
values.hover,
values.transparency,
})
end),
size = UDim2.new(1, 0, 1, 0),
@@ -124,8 +134,8 @@ function TextButton:render()
slice = Assets.Slices.RoundedBackground,
color = bindingUtil.mapLerp(
bindingEnabled,
theme.Enabled.BackgroundColor,
theme.Disabled.BackgroundColor
buttonTheme.Enabled.BackgroundColor,
buttonTheme.Disabled.BackgroundColor
),
transparency = self.props.transparency,

View File

@@ -38,14 +38,18 @@ end
function TextInput:render()
return Theme.with(function(theme)
theme = theme.TextInput
local textInputTheme = theme.TextInput
local bindingHover = bindingUtil.deriveProperty(self.binding, "hover")
local bindingEnabled = bindingUtil.deriveProperty(self.binding, "enabled")
return e(SlicedImage, {
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,
size = self.props.size or UDim2.new(1, 0, 1, 0),
@@ -55,14 +59,18 @@ function TextInput:render()
}, {
HoverOverlay = e(SlicedImage, {
slice = Assets.Slices.RoundedBackground,
color = theme.ActionFillColor,
color = textInputTheme.ActionFillColor,
transparency = Roact.joinBindings({
hover = bindingHover:map(function(value)
return 1 - value
end),
transparency = self.props.transparency,
}):map(function(values)
return bindingUtil.blendAlpha({ theme.ActionFillTransparency, values.hover, values.transparency })
return bindingUtil.blendAlpha({
textInputTheme.ActionFillTransparency,
values.hover,
values.transparency,
})
end),
size = UDim2.new(1, 0, 1, 0),
zIndex = -1,
@@ -72,14 +80,18 @@ function TextInput:render()
Size = UDim2.fromScale(1, 1),
Text = self.props.text,
PlaceholderText = self.props.placeholder,
Font = Enum.Font.GothamMedium,
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Disabled.TextColor, theme.Enabled.TextColor),
FontFace = theme.Font.Main,
TextColor3 = bindingUtil.mapLerp(
bindingEnabled,
textInputTheme.Disabled.TextColor,
textInputTheme.Enabled.TextColor
),
PlaceholderColor3 = bindingUtil.mapLerp(
bindingEnabled,
theme.Disabled.PlaceholderColor,
theme.Enabled.PlaceholderColor
textInputTheme.Disabled.PlaceholderColor,
textInputTheme.Enabled.PlaceholderColor
),
TextSize = 18,
TextSize = theme.TextSize.Large,
TextEditable = self.props.enabled,
ClearTextOnFocus = self.props.clearTextOnFocus,

View File

@@ -1,4 +1,3 @@
local TextService = game:GetService("TextService")
local HttpService = game:GetService("HttpService")
local Rojo = script:FindFirstAncestor("Rojo")
@@ -8,6 +7,8 @@ local Packages = Rojo.Packages
local Roact = require(Packages.Roact)
local Theme = require(Plugin.App.Theme)
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
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 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)
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, {
position = UDim2.fromOffset(X, Y),
size = UDim2.fromOffset(textSize.X, textSize.Y),
size = UDim2.fromOffset(contentSize.X, contentSize.Y),
transparency = props.transparency,
}, {
Label = e("TextLabel", {
BackgroundTransparency = 1,
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),
Size = UDim2.fromOffset(textBounds.X, textBounds.Y),
Text = props.Text,
TextSize = 16,
Font = Enum.Font.GothamMedium,
TextSize = theme.TextSize.Medium,
FontFace = theme.Font.Main,
TextWrapped = true,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Center,
TextColor3 = theme.Button.Bordered.Enabled.TextColor,
TextTransparency = props.transparency,
}),
@@ -72,8 +71,8 @@ local function Popup(props)
Tail = e("ImageLabel", {
ZIndex = 100,
Position = if displayAbove
then UDim2.new(0, math.clamp(props.Position.X - X, 6, textSize.X - 6), 1, -1)
else UDim2.new(0, math.clamp(props.Position.X - X, 6, textSize.X - 6), 0, -TAIL_SIZE + 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, contentSize.X - 6), 0, -TAIL_SIZE + 1),
Size = UDim2.fromOffset(TAIL_SIZE, TAIL_SIZE),
AnchorPoint = Vector2.new(0.5, 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 Rojo = script:FindFirstAncestor("Rojo")
@@ -9,10 +8,10 @@ local Roact = require(Packages.Roact)
local Flipper = require(Packages.Flipper)
local Log = require(Packages.Log)
local bindingUtil = require(script.Parent.bindingUtil)
local Theme = require(Plugin.App.Theme)
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 TextButton = require(Plugin.App.Components.TextButton)
@@ -86,51 +85,46 @@ function Notification:render()
return 1 - value
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 = {}
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 += getTextBoundsAsync(action.text, theme.Font.Main, theme.TextSize.Large, math.huge).X + (theme.TextSize.Body * 2)
buttonsX += TextService:GetTextSize(
action.text,
18,
Enum.Font.GothamMedium,
Vector2.new(math.huge, math.huge)
).X + 30
count += 1
end
count += 1
buttonsX += (count - 1) * 5
end
buttonsX += (count - 1) * 5
end
local paddingY, logoSize = 20, 32
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 actionsY = if self.props.actions then 35 else 0
local contentX = math.max(textBounds.X, buttonsX)
local size = self.binding:map(function(value)
return UDim2.fromOffset(
(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", {
BackgroundTransparency = 1,
Size = size,
@@ -147,8 +141,7 @@ function Notification:render()
size = UDim2.new(1, 0, 1, 0),
}, {
Contents = e("Frame", {
Size = UDim2.new(0, 35 + contentX, 1, -paddingY),
Position = UDim2.new(0, 0, 0, paddingY / 2),
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
}, {
Logo = e("ImageLabel", {
@@ -161,14 +154,15 @@ function Notification:render()
}),
Info = e("TextLabel", {
Text = self.props.text,
Font = Enum.Font.GothamMedium,
TextSize = 15,
FontFace = theme.Font.Main,
TextSize = theme.TextSize.Body,
TextColor3 = theme.Notification.InfoColor,
TextTransparency = transparency,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Center,
TextWrapped = true,
Size = UDim2.new(0, textBounds.X, 0, textBounds.Y),
Size = UDim2.new(0, textBounds.X, 1, -actionsY),
Position = UDim2.fromOffset(35, 0),
LayoutOrder = 1,
@@ -176,7 +170,7 @@ function Notification:render()
}),
Actions = if self.props.actions
then e("Frame", {
Size = UDim2.new(1, -40, 0, 35),
Size = UDim2.new(1, -40, 0, actionsY),
Position = UDim2.new(1, 0, 1, 0),
AnchorPoint = Vector2.new(1, 1),
BackgroundTransparency = 1,
@@ -196,6 +190,8 @@ function Notification:render()
Padding = e("UIPadding", {
PaddingLeft = UDim.new(0, 17),
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':",
self.props.confirmData.serverInfo.projectName or "UNKNOWN"
),
Font = Enum.Font.Gotham,
FontFace = theme.Font.Thin,
LineHeight = 1.2,
TextSize = 14,
TextSize = theme.TextSize.Body,
TextColor3 = theme.TextColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency,
Size = UDim2.new(1, 0, 0, 20),
Size = UDim2.new(1, 0, 0, theme.TextSize.Large + 2),
BackgroundTransparency = 1,
}),

View File

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

View File

@@ -1,5 +1,3 @@
local TextService = game:GetService("TextService")
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
@@ -7,6 +5,7 @@ local Packages = Rojo.Packages
local Roact = require(Packages.Roact)
local Theme = require(Plugin.App.Theme)
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
local TextButton = require(Plugin.App.Components.TextButton)
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)
@@ -24,43 +23,43 @@ function Error:init()
end
function Error:render()
return e(BorderedContainer, {
size = Roact.joinBindings({
containerSize = self.props.containerSize,
contentSize = self.contentSize,
}):map(function(values)
local maximumSize = values.containerSize
maximumSize -= Vector2.new(14, 14) * 2 -- Page padding
maximumSize -= Vector2.new(0, 34 + 10) -- Buttons and spacing
return Theme.with(function(theme)
return e(BorderedContainer, {
size = Roact.joinBindings({
containerSize = self.props.containerSize,
contentSize = self.contentSize,
}):map(function(values)
local maximumSize = values.containerSize
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))
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
return UDim2.new(1, 0, 0, math.min(outerSize.Y, maximumSize.Y))
end),
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)
return e("TextBox", {
ScrollingFrame = e(ScrollingFrame, {
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)
if input.UserInputType ~= Enum.UserInputType.MouseButton1 then
return
@@ -71,8 +70,8 @@ function Error:render()
Text = self.props.errorMessage,
TextEditable = false,
Font = Enum.Font.Code,
TextSize = 16,
FontFace = theme.Font.Code,
TextSize = theme.TextSize.Code,
TextColor3 = theme.ErrorColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top,
@@ -81,17 +80,17 @@ function Error:render()
ClearTextOnFocus = false,
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 1, 0),
})
end),
}),
Padding = e("UIPadding", {
PaddingLeft = UDim.new(0, ERROR_PADDING.X),
PaddingRight = UDim.new(0, ERROR_PADDING.X),
PaddingTop = UDim.new(0, ERROR_PADDING.Y),
PaddingBottom = UDim.new(0, ERROR_PADDING.Y),
Padding = e("UIPadding", {
PaddingLeft = UDim.new(0, ERROR_PADDING.X),
PaddingRight = UDim.new(0, ERROR_PADDING.X),
PaddingTop = UDim.new(0, ERROR_PADDING.Y),
PaddingBottom = UDim.new(0, ERROR_PADDING.Y),
}),
}),
}),
})
})
end)
end
local ErrorPage = Roact.Component:extend("ErrorPage")

View File

@@ -27,8 +27,8 @@ local function AddressEntry(props)
}, {
Host = e("TextBox", {
Text = props.host or "",
Font = Enum.Font.Code,
TextSize = 18,
FontFace = theme.Font.Code,
TextSize = theme.TextSize.Large,
TextColor3 = theme.AddressEntry.TextColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = props.transparency,
@@ -51,8 +51,8 @@ local function AddressEntry(props)
Port = e("TextBox", {
Text = props.port or "",
Font = Enum.Font.Code,
TextSize = 18,
FontFace = theme.Font.Code,
TextSize = theme.TextSize.Large,
TextColor3 = theme.AddressEntry.TextColor,
TextTransparency = props.transparency,
PlaceholderText = Config.defaultPort,

View File

@@ -1,5 +1,3 @@
local TextService = game:GetService("TextService")
local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages
@@ -9,6 +7,7 @@ local Roact = require(Packages.Roact)
local Settings = require(Plugin.Settings)
local Assets = require(Plugin.Assets)
local Theme = require(Plugin.App.Theme)
local getTextBoundsAsync = require(Plugin.App.getTextBoundsAsync)
local Checkbox = require(Plugin.App.Components.Checkbox)
local Dropdown = require(Plugin.App.Components.Dropdown)
@@ -31,10 +30,16 @@ local TAG_TYPES = {
},
}
local function getTextBounds(text, textSize, font, lineHeight, bounds)
local textBounds = TextService:GetTextSize(text, textSize, font, bounds)
local function getTextBoundsWithLineHeight(
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
return Vector2.new(textBounds.X, lineHeightAbsolute * lineCount - (lineHeightAbsolute - textSize))
@@ -109,6 +114,7 @@ function Setting:render()
then self.props.input
elseif self.props.options ~= nil then e(Dropdown, {
locked = self.props.locked,
lockedTooltip = self.props.lockedTooltip,
options = self.props.options,
active = self.state.setting,
transparency = self.props.transparency,
@@ -118,6 +124,7 @@ function Setting:render()
})
else e(Checkbox, {
locked = self.props.locked,
lockedTooltip = self.props.lockedTooltip,
active = self.state.setting,
transparency = self.props.transparency,
onClick = function()
@@ -145,7 +152,7 @@ function Setting:render()
BackgroundTransparency = 1,
}, {
Heading = e("Frame", {
Size = UDim2.new(1, 0, 0, 16),
Size = UDim2.new(1, 0, 0, theme.TextSize.Medium),
BackgroundTransparency = 1,
}, {
Layout = e("UIListLayout", {
@@ -165,8 +172,8 @@ function Setting:render()
else nil,
Name = e("TextLabel", {
Text = self.props.name,
Font = Enum.Font.GothamBold,
TextSize = 16,
FontFace = theme.Font.Bold,
TextSize = theme.TextSize.Medium,
TextColor3 = if self.props.tag and TAG_TYPES[self.props.tag]
then getThemeColorFromPath(theme, TAG_TYPES[self.props.tag].color)
else settingsTheme.Setting.NameColor,
@@ -174,7 +181,7 @@ function Setting:render()
TextTransparency = self.props.transparency,
RichText = true,
Size = UDim2.new(1, 0, 0, 16),
Size = UDim2.new(1, 0, 0, theme.TextSize.Medium),
LayoutOrder = 2,
BackgroundTransparency = 1,
@@ -183,9 +190,9 @@ function Setting:render()
Description = e("TextLabel", {
Text = self.props.description,
Font = Enum.Font.Gotham,
FontFace = theme.Font.Main,
LineHeight = 1.2,
TextSize = 14,
TextSize = theme.TextSize.Body,
TextColor3 = settingsTheme.Setting.DescriptionColor,
TextXAlignment = Enum.TextXAlignment.Left,
TextTransparency = self.props.transparency,
@@ -197,12 +204,12 @@ function Setting:render()
inputSize = self.inputSize,
}):map(function(values)
local offset = values.inputSize.X + 5
local textBounds = getTextBounds(
local textBounds = getTextBoundsWithLineHeight(
self.props.description,
14,
Enum.Font.Gotham,
1.2,
Vector2.new(values.containerSize.X - offset, math.huge)
theme.Font.Main,
theme.TextSize.Body,
values.containerSize.X - offset,
1.2
)
return UDim2.new(1, -offset, 0, textBounds.Y)
end),

View File

@@ -30,7 +30,7 @@ local confirmationBehaviors = { "Initial", "Always", "Large Changes", "Unlisted
local function Navbar(props)
return Theme.with(function(theme)
theme = theme.Settings.Navbar
local navbarTheme = theme.Settings.Navbar
return e("Frame", {
Size = UDim2.new(1, 0, 0, 46),
@@ -40,7 +40,7 @@ local function Navbar(props)
Back = e(IconButton, {
icon = Assets.Images.Icons.Back,
iconSize = 24,
color = theme.BackButtonColor,
color = navbarTheme.BackButtonColor,
transparency = props.transparency,
position = UDim2.new(0, 0, 0.5, 0),
@@ -55,9 +55,9 @@ local function Navbar(props)
Text = e("TextLabel", {
Text = "Settings",
Font = Enum.Font.Gotham,
TextSize = 18,
TextColor3 = theme.TextColor,
FontFace = theme.Font.Thin,
TextSize = theme.TextSize.Large,
TextColor3 = navbarTheme.TextColor,
TextTransparency = props.transparency,
Size = UDim2.new(1, 0, 1, 0),
@@ -81,185 +81,182 @@ function SettingsPage:render()
return layoutOrder
end
return Theme.with(function(theme)
theme = theme.Settings
return Roact.createFragment({
Navbar = e(Navbar, {
onBack = self.props.onBack,
return Roact.createFragment({
Navbar = e(Navbar, {
onBack = self.props.onBack,
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
}),
Content = e(ScrollingFrame, {
size = UDim2.new(1, 0, 1, -47),
position = UDim2.new(0, 0, 0, 47),
contentSize = self.contentSize,
transparency = self.props.transparency,
}, {
ShowNotifications = e(Setting, {
id = "showNotifications",
name = "Show Notifications",
description = "Popup notifications in viewport",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
}),
Content = e(ScrollingFrame, {
size = UDim2.new(1, 0, 1, -47),
position = UDim2.new(0, 0, 0, 47),
contentSize = self.contentSize,
SyncReminder = e(Setting, {
id = "syncReminder",
name = "Sync Reminder",
description = "Notify to sync when opening a place that has previously been synced",
transparency = self.props.transparency,
}, {
ShowNotifications = e(Setting, {
id = "showNotifications",
name = "Show Notifications",
description = "Popup notifications in viewport",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
}),
visible = Settings:getBinding("showNotifications"),
layoutOrder = layoutIncrement(),
}),
SyncReminder = e(Setting, {
id = "syncReminder",
name = "Sync Reminder",
description = "Notify to sync when opening a place that has previously been synced",
transparency = self.props.transparency,
visible = Settings:getBinding("showNotifications"),
layoutOrder = layoutIncrement(),
}),
ConfirmationBehavior = e(Setting, {
id = "confirmationBehavior",
name = "Confirmation Behavior",
description = "When to prompt for confirmation before syncing",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
ConfirmationBehavior = e(Setting, {
id = "confirmationBehavior",
name = "Confirmation Behavior",
description = "When to prompt for confirmation before syncing",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
options = confirmationBehaviors,
}),
options = confirmationBehaviors,
}),
LargeChangesConfirmationThreshold = e(Setting, {
id = "largeChangesConfirmationThreshold",
name = "Confirmation Threshold",
description = "How many modified instances to be considered a large change",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
visible = Settings:getBinding("confirmationBehavior"):map(function(value)
return value == "Large Changes"
LargeChangesConfirmationThreshold = e(Setting, {
id = "largeChangesConfirmationThreshold",
name = "Confirmation Threshold",
description = "How many modified instances to be considered a large change",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
visible = Settings:getBinding("confirmationBehavior"):map(function(value)
return value == "Large Changes"
end),
input = e(TextInput, {
size = UDim2.new(0, 40, 0, 28),
text = Settings:getBinding("largeChangesConfirmationThreshold"):map(function(value)
return tostring(value)
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,
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,
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")
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,
}),
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
return SettingsPage

View File

@@ -1,7 +1,6 @@
--[[
Theming system taking advantage of Roact's new context API.
Doesn't use colors provided by Studio and instead just branches on theme
name. This isn't exactly best practice.
Theming system provided through Roact's context.
Uses Studio colors when possible.
]]
-- Studio does not exist outside Roblox Studio, so we'll lazily initialize it
@@ -15,6 +14,8 @@ local function getStudio()
return _Studio
end
local ContentProvider = game:GetService("ContentProvider")
local Rojo = script:FindFirstAncestor("Rojo")
local Packages = Rojo.Packages
@@ -35,6 +36,27 @@ function StudioProvider:updateTheme()
local isDark = studioTheme.Name == "Dark"
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,
BackgroundColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainBackground),
TextColor = studioTheme:GetColor(Enum.StudioStyleGuideColor.MainText),
@@ -190,6 +212,13 @@ end
function StudioProvider:init()
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
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()
preloadAssets()
local priorHost, priorPort = self:getPriorEndpoint()
self.host, self.setHost = Roact.createBinding(priorHost or "")
self.port, self.setPort = Roact.createBinding(priorPort or "")
local priorSyncInfo = self:getPriorSyncInfo()
self.host, self.setHost = Roact.createBinding(priorSyncInfo.host or "")
self.port, self.setPort = Roact.createBinding(priorSyncInfo.port or "")
self.confirmationBindable = Instance.new("BindableEvent")
self.confirmationEvent = self.confirmationBindable.Event
@@ -145,28 +145,38 @@ function App:init()
if
Settings:get("syncReminder")
and self.serveSession == nil
and self:getLastSyncTimestamp()
and self:getPriorSyncInfo().timestamp ~= nil
and (self:isSyncLockAvailable())
then
self:addNotification("You've previously synced this place. Would you like to reconnect?", 300, {
Connect = {
text = "Connect",
style = "Solid",
layoutOrder = 1,
onClick = function(notification)
notification:dismiss()
self:startSession()
end,
},
Dismiss = {
text = "Dismiss",
style = "Bordered",
layoutOrder = 2,
onClick = function(notification)
notification:dismiss()
end,
},
})
local syncInfo = self:getPriorSyncInfo()
local timeSinceSync = timeUtil.elapsedToText(os.time() - syncInfo.timestamp)
local syncDetail = if syncInfo.projectName
then `project '{syncInfo.projectName}'`
else `{syncInfo.host or Config.defaultHost}:{syncInfo.port or Config.defaultPort}`
self:addNotification(
`You synced {syncDetail} to this place {timeSinceSync}. Would you like to reconnect?`,
300,
{
Connect = {
text = "Connect",
style = "Solid",
layoutOrder = 1,
onClick = function(notification)
notification:dismiss()
self:startSession()
end,
},
Dismiss = {
text = "Dismiss",
style = "Bordered",
layoutOrder = 2,
onClick = function(notification)
notification:dismiss()
end,
},
}
)
end
end
@@ -274,54 +284,32 @@ function App:checkForUpdates()
)
end
function App:getPriorEndpoint()
local priorEndpoints = Settings:get("priorEndpoints")
if not priorEndpoints then
return
function App:getPriorSyncInfo(): { host: string?, port: string?, projectName: string?, timestamp: number? }
local priorSyncInfos = Settings:get("priorEndpoints")
if not priorSyncInfos then
return {}
end
local id = tostring(game.PlaceId)
if ignorePlaceIds[id] then
return
return {}
end
local place = priorEndpoints[id]
if not place then
return
end
return place.host, place.port
return priorSyncInfos[id] or {}
end
function App:getLastSyncTimestamp()
local priorEndpoints = Settings:get("priorEndpoints")
if not priorEndpoints then
return
function App:setPriorSyncInfo(host: string, port: string, projectName: string)
local priorSyncInfos = Settings:get("priorEndpoints")
if not priorSyncInfos then
priorSyncInfos = {}
end
local id = tostring(game.PlaceId)
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
local now = os.time()
-- Clear any stale saves to avoid disc bloat
for placeId, endpoint in priorEndpoints do
if os.time() - endpoint.timestamp > 12_960_000 then
priorEndpoints[placeId] = nil
for placeId, syncInfo in priorSyncInfos do
if now - (syncInfo.timestamp or now) > 12_960_000 then
priorSyncInfos[placeId] = nil
Log.trace("Cleared stale saved endpoint for {}", placeId)
end
end
@@ -331,14 +319,15 @@ function App:setPriorEndpoint(host: string, port: string)
return
end
priorEndpoints[id] = {
priorSyncInfos[id] = {
host = if host ~= Config.defaultHost then host 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)
Settings:set("priorEndpoints", priorEndpoints)
Settings:set("priorEndpoints", priorSyncInfos)
end
function App:getHostAndPort()
@@ -533,8 +522,6 @@ function App:startSession()
serveSession:onStatusChanged(function(status, details)
if status == ServeSession.Status.Connecting then
self:setPriorEndpoint(host, port)
self:setState({
appStatus = AppStatus.Connecting,
toolbarIcon = Assets.Images.PluginButton,
@@ -542,6 +529,7 @@ function App:startSession()
self:addNotification("Connecting to session...")
elseif status == ServeSession.Status.Connected then
self.knownProjects[details] = true
self:setPriorSyncInfo(host, port, details)
self:setRunningConnectionInfo(baseUrl)
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 reify = require(script.Parent.reify)
local reifyInstance, applyDeferredRefs = reify.reifyInstance, reify.applyDeferredRefs
local setProperty = require(script.Parent.setProperty)
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.
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
local removeInstanceSuccess = pcall(function()
if Types.RbxId(removedIdOrInstance) then
@@ -78,7 +84,7 @@ local function applyPatch(instanceMap, patch)
)
end
local failedToReify = reify(instanceMap, patch.added, id, parentInstance)
local failedToReify = reifyInstance(deferredRefs, instanceMap, patch.added, id, parentInstance)
if not PatchSet.isEmpty(failedToReify) then
Log.debug("Failed to reify as part of applying a patch: {:#?}", failedToReify)
@@ -143,7 +149,7 @@ local function applyPatch(instanceMap, patch)
[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]
@@ -206,6 +212,18 @@ local function applyPatch(instanceMap, patch)
if update.changedProperties ~= nil then
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)
if not decodeSuccess then
unappliedUpdate.changedProperties[propertyName] = propertyValue
@@ -230,6 +248,8 @@ local function applyPatch(instanceMap, patch)
ChangeHistoryService:FinishRecording(historyRecording, Enum.FinishRecordingOperation.Commit)
end
applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
return unappliedPatch
end

View File

@@ -151,7 +151,24 @@ local function diff(instanceMap, virtualInstances, rootId)
if getProperySuccess then
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 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 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
PatchSet, marked for addition.
@@ -40,10 +20,21 @@ local function addAllToPatch(patchSet, virtualInstances, id)
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.
]]
function reifyInner(instanceMap, virtualInstances, id, parentInstance, unappliedPatch, deferredRefs)
function reifyInstanceInner(unappliedPatch, deferredRefs, instanceMap, virtualInstances, id, parentInstance)
local virtualInstance = virtualInstances[id]
if virtualInstance == nil then
@@ -102,7 +93,7 @@ function reifyInner(instanceMap, virtualInstances, id, parentInstance, unapplied
end
for _, childId in ipairs(virtualInstance.Children) do
reifyInner(instanceMap, virtualInstances, childId, instance, unappliedPatch, deferredRefs)
reifyInstanceInner(unappliedPatch, deferredRefs, instanceMap, virtualInstances, childId, instance)
end
instance.Parent = parentInstance
@@ -143,6 +134,7 @@ function applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
end
local targetInstance = instanceMap.fromIds[refId]
if targetInstance == nil then
markFailed(entry.id, entry.propertyName, entry.virtualValue)
continue
@@ -155,4 +147,7 @@ function applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
end
end
return reify
return {
reifyInstance = reifyInstance,
applyDeferredRefs = applyDeferredRefs,
}

View File

@@ -1,5 +1,6 @@
return function()
local reify = require(script.Parent.reify)
local reifyInstance, applyDeferredRefs = reify.reifyInstance, reify.applyDeferredRefs
local PatchSet = require(script.Parent.Parent.PatchSet)
local InstanceMap = require(script.Parent.Parent.InstanceMap)
@@ -20,7 +21,11 @@ return function()
it("should throw when given a bogus ID", 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)
@@ -34,8 +39,11 @@ return function()
},
}
local deferredRefs = {}
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")
@@ -60,8 +68,11 @@ return function()
},
}
local deferredRefs = {}
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")
@@ -90,8 +101,11 @@ return function()
},
}
local deferredRefs = {}
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")
@@ -122,8 +136,11 @@ return function()
},
}
local deferredRefs = {}
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(unappliedPatch.added["CHILD"]).to.equal(virtualInstances["CHILD"])
@@ -153,8 +170,11 @@ return function()
},
}
local deferredRefs = {}
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"]
expect(instance.ClassName).to.equal("StringValue")
@@ -196,8 +216,11 @@ return function()
},
}
local deferredRefs = {}
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")
@@ -223,13 +246,16 @@ return function()
},
}
local deferredRefs = {}
local instanceMap = InstanceMap.new()
local existing = Instance.new("Folder")
existing.Name = "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")
@@ -268,8 +294,11 @@ return function()
},
}
local deferredRefs = {}
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")
@@ -307,8 +336,11 @@ return function()
},
}
local deferredRefs = {}
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")
@@ -332,8 +364,11 @@ return function()
},
}
local deferredRefs = {}
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.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": {
"Rojo": {
"$path": "default.project.json"
"$path": "../plugin.project.json"
},
"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">
<Properties>
<string name="Name">init_meta_class_name</string>
<bool name="NeedsPivotMigration">false</bool>
</Properties>
</Item>
</roblox>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
---
source: tests/tests/serve.rs
expression: "subscribe_response.intern_and_redact(&mut redactions, ())"
---
messageCursor: 1
messages:
@@ -14,8 +13,9 @@ messages:
ignoreUnknownInstances: false
Name: test
Parent: id-2
Properties: {}
Properties:
NeedsPivotMigration:
Bool: false
removed: []
updated: []
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
expression: redactions.redacted_yaml(info)
---
expectedPlaceIds: ~
gameId: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2
serverVersion: "[server-version]"
sessionId: id-1
unexpectedPlaceIds: ~

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
---
source: tests/tests/serve.rs
assertion_line: 351
expression: redactions.redacted_yaml(info)
---
expectedPlaceIds: ~
@@ -11,4 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2
serverVersion: "[server-version]"
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:
Rojo_Target_PrimaryPart:
String: project target
NeedsPivotMigration:
Bool: false
PrimaryPart:
Ref: id-9
id-2:
@@ -55,7 +57,9 @@ instances:
ignoreUnknownInstances: true
Name: Workspace
Parent: id-2
Properties: {}
Properties:
NeedsPivotMigration:
Bool: false
id-4:
Children: []
ClassName: ObjectValue
@@ -124,6 +128,8 @@ instances:
Attributes:
Rojo_Target_PrimaryPart:
String: model target 2
NeedsPivotMigration:
Bool: false
PrimaryPart:
Ref: id-7
id-9:
@@ -138,4 +144,3 @@ instances:
Properties: {}
messageCursor: 1
sessionId: id-1

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,3 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2
serverVersion: "[server-version]"
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
serverVersion: "[server-version]"
sessionId: id-1
unexpectedPlaceIds: ~

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
---
source: tests/tests/serve.rs
assertion_line: 383
expression: redactions.redacted_yaml(info)
---
expectedPlaceIds: ~
@@ -11,3 +10,4 @@ protocolVersion: 4
rootInstanceId: id-2
serverVersion: "[server-version]"
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.
let mut current_path = path.as_path();
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);

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.
#[derive(Debug, Parser)]
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 = "")]
pub project: PathBuf,

View File

@@ -8,7 +8,7 @@ use clap::Parser;
use fs_err::File;
use memofs::Vfs;
use rayon::prelude::*;
use rbx_dom_weak::types::Ref;
use rbx_dom_weak::{types::Ref, Ustr};
use serde::Serialize;
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")]
struct SourcemapNode<'a> {
name: &'a str,
class_name: &'a str,
class_name: Ustr,
#[serde(skip_serializing_if = "Vec::is_empty")]
file_paths: Vec<PathBuf>,
@@ -67,7 +67,7 @@ impl SourcemapCommand {
let vfs = Vfs::new_default();
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 filter = if self.include_non_scripts {
@@ -113,7 +113,7 @@ fn filter_nothing(_instance: &InstanceWithMeta) -> bool {
fn filter_non_scripts(instance: &InstanceWithMeta) -> bool {
matches!(
instance.class_name(),
instance.class_name().as_str(),
"Script" | "LocalScript" | "ModuleScript"
)
}

View File

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

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